diff options
467 files changed, 65754 insertions, 0 deletions
diff --git a/chrome_frame/CFInstall.js b/chrome_frame/CFInstall.js new file mode 100644 index 0000000..915f958 --- /dev/null +++ b/chrome_frame/CFInstall.js @@ -0,0 +1,222 @@ +// Copyright (c) 2009 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 CFInstall.js provides a set of utilities for managing + * the Chrome Frame detection and installation process. + * @author slightlyoff@google.com (Alex Russell) + */ + +(function(scope) { + // bail if we'd be over-writing an existing CFInstall object + if (scope['CFInstall']) { + return; + } + + /** + * returns an item based on DOM ID. Optionally a document may be provided to + * specify the scope to search in. If a node is passed, it's returned as-is. + * @param {string|Node} id The ID of the node to be located or a node + * @param {Node} doc Optional A document to search for id. + * @return {Node} + */ + var byId = function(id, doc) { + return (typeof id == 'string') ? (doc || document).getElementById(id) : id; + }; + + ///////////////////////////////////////////////////////////////////////////// + // Plugin Detection + ///////////////////////////////////////////////////////////////////////////// + + var cachedAvailable; + + /** + * Checks to find out if ChromeFrame is available as a plugin + * @return {Boolean} + */ + var isAvailable = function() { + if (typeof cachedAvailable != 'undefined') { + return cachedAvailable; + } + + cachedAvailable = false; + + // Look for CF in the User Agent before trying more expensive checks + var ua = navigator.userAgent.toLowerCase(); + if (ua.indexOf("chromeframe") >= 0 || ua.indexOf("x-clock") >= 0) { + cachedAvailable = true; + return cachedAvailable; + } + + if (typeof window['ActiveXObject'] != 'undefined') { + try { + var obj = new ActiveXObject('ChromeTab.ChromeFrame'); + if (obj) { + cachedAvailable = true; + } + } catch(e) { + // squelch + } + } + return cachedAvailable; + }; + + + /** @type {boolean} */ + var cfStyleTagInjected = false; + + /** + * Creates a style sheet in the document which provides default styling for + * ChromeFrame instances. Successive calls should have no additive effect. + */ + var injectCFStyleTag = function() { + if (cfStyleTagInjected) { + // Once and only once + return; + } + try { + var rule = '.chromeFrameInstallDefaultStyle {' + + 'width: 500px;' + + 'height: 400px;' + + 'padding: 0;' + + 'border: 1px solid #0028c4;' + + 'margin: 0;' + + '}'; + var ss = document.createElement('style'); + ss.setAttribute('type', 'text/css'); + if (ss.styleSheet) { + ss.styleSheet.cssText = rule; + } else { + ss.appendChild(document.createTextNode(rule)); + } + var h = document.getElementsByTagName('head')[0]; + var firstChild = h.firstChild; + h.insertBefore(ss, firstChild); + cfStyleTagInjected = true; + } catch (e) { + // squelch + } + }; + + + /** + * Plucks properties from the passed arguments and sets them on the passed + * DOM node + * @param {Node} node The node to set properties on + * @param {Object} args A map of user-specified properties to set + */ + var setProperties = function(node, args) { + injectCFStyleTag(); + + var srcNode = byId(args['node']); + + node.id = args['id'] || (srcNode ? srcNode['id'] || getUid(srcNode) : ''); + + // TODO(slightlyoff): Opera compat? need to test there + var cssText = args['cssText'] || ''; + node.style.cssText = ' ' + cssText; + + var classText = args['className'] || ''; + node.className = 'chromeFrameInstallDefaultStyle ' + classText; + + // default if the browser doesn't so we don't show sad-tab + var src = args['src'] || 'about:blank'; + + node.src = src; + + if (srcNode) { + srcNode.parentNode.replaceChild(node, srcNode); + } + }; + + /** + * Creates an iframe. + * @param {Object} args A bag of configuration properties, including values + * like 'node', 'cssText', 'className', 'id', 'src', etc. + * @return {Node} + */ + var makeIframe = function(args) { + var el = document.createElement('iframe'); + setProperties(el, args); + return el; + }; + + var CFInstall = {}; + /** + * Checks to see if Chrome Frame is available, if not, prompts the user to + * install. Once installation is begun, a background timer starts, + * checkinging for a successful install every 2 seconds. Upon detection of + * successful installation, the current page is reloaded, or if a + * 'destination' parameter is passed, the page navigates there instead. + * @param {Object} args A bag of configuration properties. Respected + * properties are: 'mode', 'url', 'destination', 'node', 'onmissing', + * 'preventPrompt', 'oninstall', 'preventInstallDetection', 'cssText', and + * 'className'. + * @public + */ + CFInstall.check = function(args) { + args = args || {}; + + // We currently only support CF in IE + // TODO(slightlyoff): Update this should we support other browsers! + var ieRe = /MSIE (\S+)/; + if (!ieRe.test(navigator.userAgent)) { + return; + } + + + // TODO(slightlyoff): Update this URL when a mini-installer page is + // available. + var installUrl = '//www.google.com/chromeframe'; + if (!isAvailable()) { + if (args.onmissing) { + args.onmissing(); + } + + args.src = args.url || installUrl; + var mode = args.mode || 'inline'; + var preventPrompt = args.preventPrompt || false; + + if (!preventPrompt) { + if (mode == 'inline') { + var ifr = makeIframe(args); + // TODO(slightlyoff): handle placement more elegantly! + if (!ifr.parentNode) { + var firstChild = document.body.firstChild; + document.body.insertBefore(ifr, firstChild); + } + } else { + window.open(args.src); + } + } + + if (args.preventInstallDetection) { + return; + } + + // Begin polling for install success. + var installTimer = setInterval(function() { + // every 2 seconds, look to see if CF is available, if so, proceed on + // to our destination + if (isAvailable()) { + if (args.oninstall) { + args.oninstall(); + } + + clearInterval(installTimer); + // TODO(slightlyoff): add a way to prevent navigation or make it + // contingent on oninstall? + window.location = args.destination || window.location; + } + }, 2000); + } + }; + + CFInstall.isAvailable = isAvailable; + + // expose CFInstall to the external scope. We've already checked to make + // sure we're not going to blow existing objects away. + scope.CFInstall = CFInstall; + +})(this['ChromeFrameInstallScope'] || this); diff --git a/chrome_frame/CFInstance.js b/chrome_frame/CFInstance.js new file mode 100644 index 0000000..90a28f5 --- /dev/null +++ b/chrome_frame/CFInstance.js @@ -0,0 +1,1656 @@ +// Copyright (c) 2009 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. + +// Parts Copyright 2005-2009, the Dojo Foundation. Used under the terms of the +// "New" BSD License: +// +// http://download.dojotoolkit.org/release-1.3.2/dojo-release-1.3.2/dojo/LICENSE +// + +/** + * @fileoverview CFInstance.js provides a set of utilities for managing + * ChromeFrame plugins, including creation, RPC services, and a singleton to + * use for communicating from ChromeFrame hosted content to an external + * CFInstance wrapper. CFInstance.js is stand-alone, designed to be served from + * a CDN, and built to not create side-effects for other hosted content. + * @author slightlyoff@google.com (Alex Russell) + */ + +(function(scope) { + // TODO: + // * figure out if there's any way to install w/o a browser restart, and if + // so, where and how + // * slim down Deferred and RPC scripts + // * determine what debugging APIs should be exposed and how they should be + // surfaced. What about content authoring in Chrome instances? Stubbing + // the other side of RPC's? + + // bail if we'd be over-writing an existing CFInstance object + if (scope['CFInstance']) { + return; + } + + ///////////////////////////////////////////////////////////////////////////// + // Utiliity and Cross-Browser Functions + ///////////////////////////////////////////////////////////////////////////// + + // a monotonically incrementing counter + var _counter = 0; + + var undefStr = 'undefined'; + + // + // Browser detection: ua.isIE, ua.isSafari, ua.isOpera, etc. + // + + /** + * An object for User Agent detection + * @type {!Object} + * @protected + */ + var ua = {}; + var n = navigator; + var dua = String(n.userAgent); + var dav = String(n.appVersion); + var tv = parseFloat(dav); + var duaParse = function(s){ + var c = 0; + try { + return parseFloat( + dua.split(s)[1].replace(/\./g, function() { + c++; + return (c > 1) ? '' : '.'; + } ) + ); + } catch(e) { + // squelch to intentionally return undefined + } + }; + /** @type {number} */ + ua.isOpera = dua.indexOf('Opera') >= 0 ? tv: undefined; + /** @type {number} */ + ua.isWebKit = duaParse('WebKit/'); + /** @type {number} */ + ua.isChrome = duaParse('Chrome/'); + /** @type {number} */ + ua.isKhtml = dav.indexOf('KHTML') >= 0 ? tv : undefined; + + var index = Math.max(dav.indexOf('WebKit'), dav.indexOf('Safari'), 0); + + if (index && !ua.isChrome) { + /** @type {number} */ + ua.isSafari = parseFloat(dav.split('Version/')[1]); + if(!ua.isSafari || parseFloat(dav.substr(index + 7)) <= 419.3){ + ua.isSafari = 2; + } + } + + if (dua.indexOf('Gecko') >= 0 && !ua.isKhtml) { + /** @type {number} */ + ua.isGecko = duaParse(' rv:'); + } + + if (ua.isGecko) { + /** @type {number} */ + ua.isFF = parseFloat(dua.split('Firefox/')[1]) || undefined; + } + + if (document.all && !ua.isOpera) { + /** @type {number} */ + ua.isIE = parseFloat(dav.split('MSIE ')[1]) || undefined; + } + + + /** + * Log out varargs to a browser-provided console object (if available). Else + * a no-op. + * @param {*} var_args Optional Things to log. + * @protected + **/ + var log = function() { + if (window['console']) { + try { + if (ua.isSafari || ua.isChrome) { + throw Error(); + } + console.log.apply(console, arguments); + } catch(e) { + try { + console.log(toArray(arguments).join(' ')); + } catch(e2) { + // squelch + } + } + } + }; + + // + // Language utility methods + // + + /** + * Determine if the passed item is a String + * @param {*} item Item to test. + * @protected + **/ + var isString = function(item) { + return typeof item == 'string'; + }; + + /** + * Determine if the passed item is a Function object + * @param {*} item Item to test. + * @protected + **/ + var isFunction = function(item) { + return ( + item && ( + typeof item == 'function' || item instanceof Function + ) + ); + }; + + /** + * Determine if the passed item is an array. + * @param {*} item Item to test. + * @protected + **/ + var isArray = function(item){ + return ( + item && ( + item instanceof Array || ( + typeof item == 'object' && + typeof item.length != undefStr + ) + ) + ); + }; + + /** + * A toArray version which takes advantage of builtins + * @param {*} obj The array-like object to convert to a real array. + * @param {number} opt_offset An index to being copying from in the source. + * @param {Array} opt_startWith An array to extend with elements of obj in + * lieu of creating a new array to return. + * @private + **/ + var _efficientToArray = function(obj, opt_offset, opt_startWith){ + return (opt_startWith || []).concat( + Array.prototype.slice.call(obj, opt_offset || 0 ) + ); + }; + + /** + * A version of toArray that iterates in lieu of using array generics. + * @param {*} obj The array-like object to convert to a real array. + * @param {number} opt_offset An index to being copying from in the source. + * @param {Array} opt_startWith An array to extend with elements of obj in + * @private + **/ + var _slowToArray = function(obj, opt_offset, opt_startWith){ + var arr = opt_startWith || []; + for(var x = opt_offset || 0; x < obj.length; x++){ + arr.push(obj[x]); + } + return arr; + }; + + /** + * Converts an array-like object (e.g., an "arguments" object) to a real + * Array. + * @param {*} obj The array-like object to convert to a real array. + * @param {number} opt_offset An index to being copying from in the source. + * @param {Array} opt_startWith An array to extend with elements of obj in + * @protected + */ + var toArray = ua.isIE ? + function(obj){ + return ( + obj.item ? _slowToArray : _efficientToArray + ).apply(this, arguments); + } : + _efficientToArray; + + var _getParts = function(arr, obj, cb){ + return [ + isString(arr) ? arr.split('') : arr, + obj || window, + isString(cb) ? new Function('item', 'index', 'array', cb) : cb + ]; + }; + + /** + * like JS1.6 Array.forEach() + * @param {Array} arr the array to iterate + * @param {function(Object, number, Array)} callback the method to invoke for + * each item in the array + * @param {function?} thisObject Optional a scope to use with callback + * @return {array} the original arr + * @protected + */ + var forEach = function(arr, callback, thisObject) { + if(!arr || !arr.length){ + return arr; + } + var parts = _getParts(arr, thisObject, callback); + // parts has a structure of: + // [ + // array, + // scope, + // function + // ] + arr = parts[0]; + for (var i = 0, l = arr.length; i < l; ++i) { + parts[2].call( parts[1], arr[i], i, arr ); + } + return arr; + }; + + /** + * returns a new function bound to scope with a variable number of positional + * params pre-filled + * @private + */ + var _hitchArgs = function(scope, method /*,...*/) { + var pre = toArray(arguments, 2); + var named = isString(method); + return function() { + var args = toArray(arguments); + var f = named ? (scope || window)[method] : method; + return f && f.apply(scope || this, pre.concat(args)); + } + }; + + /** + * Like goog.bind(). Hitches the method (named or provided as a function + * object) to scope, optionally partially applying positional arguments. + * @param {Object} scope the object to hitch the method to + * @param {string|function} method the method to be bound + * @return {function} The bound method + * @protected + */ + var hitch = function(scope, method){ + if (arguments.length > 2) { + return _hitchArgs.apply(window, arguments); // Function + } + + if (!method) { + method = scope; + scope = null; + } + + if (isString(method)) { + scope = scope || window; + if (!scope[method]) { + throw( + ['scope["', method, '"] is null (scope="', scope, '")'].join('') + ); + } + return function() { + return scope[method].apply(scope, arguments || []); + }; + } + + return !scope ? + method : + function() { + return method.apply(scope, arguments || []); + }; + }; + + /** + * A version of addEventListener that works on IE too. *sigh*. + * @param {!Object} obj The object to attach to + * @param {!String} type Name of the event to attach to + * @param {!Function} handler The function to connect + * @protected + */ + var listen = function(obj, type, handler) { + if (obj['attachEvent']) { + obj.attachEvent('on' + type, handler); + } else { + obj.addEventListener(type, handler, false); + } + }; + + /** + * Adds "listen" and "_dispatch" methods to the passed object, taking + * advantage of native event hanlding if it's available. + * @param {Object} instance The object to install the event system on + * @protected + */ + var installEvtSys = function(instance) { + var eventsMap = {}; + + var isNative = ( + (typeof instance.addEventListener != undefStr) && + ((instance['tagName'] || '').toLowerCase() != 'iframe') + ); + + instance.listen = function(type, func) { + var t = eventsMap[type]; + if (!t) { + t = eventsMap[type] = []; + if (isNative) { + listen(instance, type, hitch(instance, "_dispatch", type)); + } + } + t.push(func); + return t; + }; + + instance._dispatch = function(type, evt) { + var stopped = false; + var stopper = function() { + stopped = true; + }; + + forEach(eventsMap[type], function(f) { + if (!stopped) { + f(evt, stopper); + } + }); + }; + + return instance; + }; + + /** + * Deserialize the passed JSON string + * @param {!String} json A string to be deserialized + * @return {Object} + * @protected + */ + var fromJson = window['JSON'] ? function(json) { + return JSON.parse(json); + } : + function(json) { + return eval('(' + (json || undefStr) + ')'); + }; + + /** + * String escaping for use in JSON serialization + * @param {string} str The string to escape + * @return {string} + * @private + */ + var _escapeString = function(str) { + return ('"' + str.replace(/(["\\])/g, '\\$1') + '"'). + replace(/[\f]/g, '\\f'). + replace(/[\b]/g, '\\b'). + replace(/[\n]/g, '\\n'). + replace(/[\t]/g, '\\t'). + replace(/[\r]/g, '\\r'). + replace(/[\x0B]/g, '\\u000b'); // '\v' is not supported in JScript; + }; + + /** + * JSON serialization for arbitrary objects. Circular references or strong + * typing information are not handled. + * @param {Object} it Any valid JavaScript object or type + * @return {string} the serialized representation of the passed object + * @protected + */ + var toJson = window['JSON'] ? function(it) { + return JSON.stringify(it); + } : + function(it) { + + if (it === undefined) { + return undefStr; + } + + var objtype = typeof it; + if (objtype == 'number' || objtype == 'boolean') { + return it + ''; + } + + if (it === null) { + return 'null'; + } + + if (isString(it)) { + return _escapeString(it); + } + + // recurse + var recurse = arguments.callee; + + if(it.nodeType && it.cloneNode){ // isNode + // we can't seriailize DOM nodes as regular objects because they have + // cycles DOM nodes could be serialized with something like outerHTML, + // but that can be provided by users in the form of .json or .__json__ + // function. + throw new Error('Cannot serialize DOM nodes'); + } + + // array + if (isArray(it)) { + var res = []; + forEach(it, function(obj) { + var val = recurse(obj); + if (typeof val != 'string') { + val = undefStr; + } + res.push(val); + }); + return '[' + res.join(',') + ']'; + } + + if (objtype == 'function') { + return null; + } + + // generic object code path + var output = []; + for (var key in it) { + var keyStr, val; + if (typeof key == 'number') { + keyStr = '"' + key + '"'; + } else if(typeof key == 'string') { + keyStr = _escapeString(key); + } else { + // skip non-string or number keys + continue; + } + val = recurse(it[key]); + if (typeof val != 'string') { + // skip non-serializable values + continue; + } + // TODO(slightlyoff): use += on Moz since it's faster there + output.push(keyStr + ':' + val); + } + return '{' + output.join(',') + '}'; // String + }; + + // code to register with the earliest safe onload-style handler + + var _loadedListenerList = []; + var _loadedFired = false; + + /** + * a default handler for document onload. When called (the first time), + * iterates over the list of registered listeners, calling them in turn. + * @private + */ + var documentLoaded = function() { + if (!_loadedFired) { + _loadedFired = true; + forEach(_loadedListenerList, 'item();'); + } + }; + + if (document.addEventListener) { + // NOTE: + // due to a threading issue in Firefox 2.0, we can't enable + // DOMContentLoaded on that platform. For more information, see: + // http://trac.dojotoolkit.org/ticket/1704 + if (ua.isWebKit > 525 || ua.isOpera || ua.isFF >= 3) { + listen(document, 'DOMContentLoaded', documentLoaded); + } + // mainly for Opera 8.5, won't be fired if DOMContentLoaded fired already. + // also used for FF < 3.0 due to nasty DOM race condition + listen(window, 'load', documentLoaded); + } else { + // crazy hack for IE that relies on the "deferred" behavior of script + // tags + document.write( + '<scr' + 'ipt defer src="//:" ' + + 'onreadystatechange="if(this.readyState==\'complete\')' + + '{ CFInstance._documentLoaded();}">' + + '</scr' + 'ipt>' + ); + } + + // TODO(slightlyoff): known KHTML init issues are ignored for now + + // + // DOM utility methods + // + + /** + * returns an item based on DOM ID. Optionally a doucment may be provided to + * specify the scope to search in. If a node is passed, it's returned as-is. + * @param {string|Node} id The ID of the node to be located or a node + * @param {Node} doc Optional A document to search for id. + * @return {Node} + * @protected + */ + var byId = (ua.isIE || ua.isOpera) ? + function(id, doc) { + if (isString(id)) { + doc = doc || document; + var te = doc.getElementById(id); + // attributes.id.value is better than just id in case the + // user has a name=id inside a form + if (te && te.attributes.id.value == id) { + return te; + } else { + var elements = doc.all[id]; + if (!elements || !elements.length) { + return elements; + } + // if more than 1, choose first with the correct id + var i=0; + while (te = elements[i++]) { + if (te.attributes.id.value == id) { + return te; + } + } + } + } else { + return id; // DomNode + } + } : + function(id, doc) { + return isString(id) ? (doc || document).getElementById(id) : id; + }; + + + /** + * returns a unique DOM id which can be used to locate the node via byId(). + * If the node already has an ID, it's used. If not, one is generated. Like + * IE's uniqueID property. + * @param {Node} node The element to create or fetch a unique ID for + * @return {String} + * @protected + */ + var getUid = function(node) { + var u = 'cfUnique' + (_counter++); + return (!node) ? u : ( node.id || node.uniqueID || (node.id = u) ); + }; + + // + // the Deferred class, borrowed from Twisted Python and Dojo + // + + /** + * A class that models a single response (past or future) to a question. + * Multiple callbacks and error handlers may be added. If the response was + * added in the past, adding callbacks has the effect of calling them + * immediately. In this way, Deferreds simplify thinking about synchronous + * vs. asynchronous programming in languages which don't have continuations + * or generators which might otherwise provide syntax for deferring + * operations. + * @param {function} canceller Optional A function to be called when the + * Deferred is canceled. + * @param {number} timeout Optional How long to wait (in ms) before errback + * is called with a timeout error. If no timeout is passed, the default + * is 1hr. Passing -1 will disable any timeout. + * @constructor + * @public + */ + Deferred = function(/*Function?*/ canceller, timeout){ + // example: + // var deferred = new Deferred(); + // setTimeout(function(){ deferred.callback({success: true}); }, 1000); + // return deferred; + this.chain = []; + this.id = _counter++; + this.fired = -1; + this.paused = 0; + this.results = [ null, null ]; + this.canceller = canceller; + // FIXME(slightlyoff): is it really smart to be creating this many timers? + if (typeof timeout == 'number') { + if (timeout <= 0) { + timeout = 216000; // give it an hour + } + } + this._timer = setTimeout( + hitch(this, 'errback', new Error('timeout')), + (timeout || 1000) + ); + this.silentlyCancelled = false; + }; + + /** + * Cancels a Deferred that has not yet received a value, or is waiting on + * another Deferred as its value. If a canceller is defined, the canceller + * is called. If the canceller did not return an error, or there was no + * canceller, then the errback chain is started. + * @public + */ + Deferred.prototype.cancel = function() { + var err; + if (this.fired == -1) { + if (this.canceller) { + err = this.canceller(this); + } else { + this.silentlyCancelled = true; + } + if (this.fired == -1) { + if ( !(err instanceof Error) ) { + var res = err; + var msg = 'Deferred Cancelled'; + if (err && err.toString) { + msg += ': ' + err.toString(); + } + err = new Error(msg); + err.dType = 'cancel'; + err.cancelResult = res; + } + this.errback(err); + } + } else if ( + (this.fired == 0) && + (this.results[0] instanceof Deferred) + ) { + this.results[0].cancel(); + } + }; + + + /** + * internal function for providing a result. If res is an instance of Error, + * we treat it like such and start the error chain. + * @param {Object|Error} res the result + * @private + */ + Deferred.prototype._resback = function(res) { + if (this._timer) { + clearTimeout(this._timer); + } + this.fired = res instanceof Error ? 1 : 0; + this.results[this.fired] = res; + this._fire(); + }; + + /** + * determine if the deferred has already been resolved + * @return {boolean} + * @private + */ + Deferred.prototype._check = function() { + if (this.fired != -1) { + if (!this.silentlyCancelled) { + return 0; + } + this.silentlyCancelled = 0; + return 1; + } + return 0; + }; + + /** + * Begin the callback sequence with a non-error value. + * @param {Object|Error} res the result + * @public + */ + Deferred.prototype.callback = function(res) { + this._check(); + this._resback(res); + }; + + /** + * Begin the callback sequence with an error result. + * @param {Error|string} res the result. If not an Error, it's treated as the + * message for a new Error. + * @public + */ + Deferred.prototype.errback = function(res) { + this._check(); + if ( !(res instanceof Error) ) { + res = new Error(res); + } + this._resback(res); + }; + + /** + * Add a single function as the handler for both callback and errback, + * allowing you to specify a scope (unlike addCallbacks). + * @param {function|Object} cb A function. If cbfn is passed, the value of cb + * is treated as a scope + * @param {function|string} cbfn Optional A function or name of a function in + * the scope cb. + * @return {Deferred} this + * @public + */ + Deferred.prototype.addBoth = function(cb, cbfn) { + var enclosed = hitch.apply(window, arguments); + return this.addCallbacks(enclosed, enclosed); + }; + + /** + * Add a single callback to the end of the callback sequence. Add a function + * as the handler for successful resolution of the Deferred. May be called + * multiple times to register many handlers. Note that return values are + * chained if provided, so it's best for callback handlers not to return + * anything. + * @param {function|Object} cb A function. If cbfn is passed, the value of cb + * is treated as a scope + * @param {function|string} cbfn Optional A function or name of a function in + * the scope cb. + * @return {Deferred} this + * @public + */ + Deferred.prototype.addCallback = function(cb, cbfn /*...*/) { + return this.addCallbacks(hitch.apply(window, arguments)); + }; + + + /** + * Add a function as the handler for errors in the Deferred. May be called + * multiple times to add multiple error handlers. + * @param {function|Object} cb A function. If cbfn is passed, the value of cb + * is treated as a scope + * @param {function|string} cbfn Optional A function or name of a function in + * the scope cb. + * @return {Deferred} this + * @public + */ + Deferred.prototype.addErrback = function(cb, cbfn) { + return this.addCallbacks(null, hitch.apply(window, arguments)); + }; + + /** + * Add a functions as handlers for callback and errback in a single shot. + * @param {function} callback A function + * @param {function} errback A function + * @return {Deferred} this + * @public + */ + Deferred.prototype.addCallbacks = function(callback, errback) { + this.chain.push([callback, errback]); + if (this.fired >= 0) { + this._fire(); + } + return this; + }; + + /** + * when this Deferred is satisfied, pass it on to def, allowing it to run. + * @param {Deferred} def A deferred to add to the end of this Deferred in a chain + * @return {Deferred} this + * @public + */ + Deferred.prototype.chain = function(def) { + this.addCallbacks(def.callback, def.errback); + return this; + }; + + /** + * Used internally to exhaust the callback sequence when a result is + * available. + * @private + */ + Deferred.prototype._fire = function() { + var chain = this.chain; + var fired = this.fired; + var res = this.results[fired]; + var cb = null; + while ((chain.length > 0) && (this.paused == 0)) { + var f = chain.shift()[fired]; + if (!f) { + continue; + } + var func = hitch(this, function() { + var ret = f(res); + //If no response, then use previous response. + if (typeof ret != undefStr) { + res = ret; + } + fired = res instanceof Error ? 1 : 0; + if (res instanceof Deferred) { + cb = function(res) { + this._resback(res); + // inlined from _pause() + this.paused--; + if ( (this.paused == 0) && (this.fired >= 0)) { + this._fire(); + } + } + // inlined from _unpause + this.paused++; + } + }); + + try { + func.call(this); + } catch(err) { + fired = 1; + res = err; + } + } + + this.fired = fired; + this.results[fired] = res; + if (cb && this.paused ) { + // this is for "tail recursion" in case the dependent + // deferred is already fired + res.addBoth(cb); + } + }; + + ///////////////////////////////////////////////////////////////////////////// + // Plugin Initialization Class and Helper Functions + ///////////////////////////////////////////////////////////////////////////// + + var returnFalse = function() { + return false; + }; + + var cachedHasVideo; + var cachedHasAudio; + + var contentTests = { + canvas: function() { + return !!( + ua.isChrome || ua.isSafari >= 3 || ua.isFF >= 3 || ua.isOpera >= 9.2 + ); + }, + + svg: function() { + return !!(ua.isChrome || ua.isSafari || ua.isFF || ua.isOpera); + }, + + postMessage: function() { + return ( + !!window['postMessage'] || + ua.isChrome || + ua.isIE >= 8 || + ua.isSafari >= 3 || + ua.isFF >= 3 || + ua.isOpera >= 9.2 + ); + }, + + // the spec isn't settled and nothing currently supports it + websocket: returnFalse, + + 'css-anim': function() { + // pretty much limited to WebKit's special transition and animation + // properties. Need to figure out a better way to triangulate this as + // FF3.x adds more of these properties in parallel. + return ua.isWebKit > 500; + }, + + // "working" video/audio tag? + video: function() { + if (typeof cachedHasVideo != undefStr) { + return cachedHasVideo; + } + + // We haven't figured it out yet, so probe the <video> tag and cache the + // result. + var video = document.createElement('video'); + return cachedHasVideo = (typeof video['play'] != undefStr); + }, + + audio: function() { + if (typeof cachedHasAudio != undefStr) { + return cachedHasAudio; + } + + var audio = document.createElement('audio'); + return cachedHasAudio = (typeof audio['play'] != undefStr); + }, + + 'video-theora': function() { + return contentTests.video() && (ua.isChrome || ua.isFF > 3); + }, + + 'video-h264': function() { + return contentTests.video() && (ua.isChrome || ua.isSafari >= 4); + }, + + 'audio-vorbis': function() { + return contentTests.audio() && (ua.isChrome || ua.isFF > 3); + }, + + 'audio-mp3': function() { + return contentTests.audio() && (ua.isChrome || ua.isSafari >= 4); + }, + + // can we implement RPC over available primitives? + rpc: function() { + // on IE we need the src to be on the same domain or we need postMessage + // to work. Since we can't count on the src being same-domain, we look + // for things that have postMessage. We may re-visit this later and add + // same-domain checking and cross-window-call-as-postMessage-replacement + // code. + + // use "!!" to avoid null-is-an-object weirdness + return !!window['postMessage']; + }, + + sql: function() { + // HTML 5 databases + return !!window['openDatabase']; + }, + + storage: function(){ + // DOM storage + + // IE8, Safari, etc. support "localStorage", FF supported "globalStorage" + return !!window['globalStorage'] || !!window['localStorage']; + } + }; + + // isIE, isFF, isWebKit, etc. + forEach([ + 'isOpera', 'isWebKit', 'isChrome', 'isKhtml', 'isSafari', + 'isGecko', 'isFF', 'isIE' + ], + function(name) { + contentTests[name] = function() { + return !!ua[name]; + }; + } + ); + + /** + * Checks the list of requirements to determine if the current host browser + * meets them natively. Primarialy relies on the contentTests array. + * @param {Array} reqs A list of tests, either names of test functions in + * contentTests or functions to execute. + * @return {boolean} + * @private + */ + var testRequirements = function(reqs) { + // never use CF on Chrome or Safari + if (ua.isChrome || ua.isSafari) { + return true; + } + + var allMatch = true; + if (!reqs) { + return false; + } + forEach(reqs, function(i) { + var matches = false; + if (isFunction(i)) { + // support custom test functions + matches = i(); + } else { + // else it's a lookup by name + matches = (!!contentTests[i] && contentTests[i]()); + } + allMatch = allMatch && matches; + }); + return allMatch; + }; + + var cachedAvailable; + + /** + * Checks to find out if ChromeFrame is available as a plugin + * @return {Boolean} + * @private + */ + var isCfAvailable = function() { + if (typeof cachedAvailable != undefStr) { + return cachedAvailable; + } + + cachedAvailable = false; + var p = n.plugins; + if (typeof window['ActiveXObject'] != undefStr) { + try { + var i = new ActiveXObject('ChromeTab.ChromeFrame'); + if (i) { + cachedAvailable = true; + } + } catch(e) { + log('ChromeFrame not available, error:', e.message); + // squelch + } + } else { + for (var x = 0; x < p.length; x++) { + if (p[x].name.indexOf('Google Chrome Frame') == 0) { + cachedAvailable = true; + break; + } + } + } + return cachedAvailable; + }; + + /** + * Creates a <param> element with the specified name and value. If a parent + * is provided, the <param> element is appended to it. + * @param {string} name The name of the param + * @param {string} value The value + * @param {Node} parent Optional parent element + * @return {Boolean} + * @private + */ + var param = function(name, value, parent) { + var p = document.createElement('param'); + p.setAttribute('name', name); + p.setAttribute('value', value); + if (parent) { + parent.appendChild(p); + } + return p; + }; + + /** @type {boolean} */ + var cfStyleTagInjected = false; + + /** + * Creates a style sheet in the document which provides default styling for + * ChromeFrame instances. Successive calls should have no additive effect. + * @private + */ + var injectCFStyleTag = function() { + if (cfStyleTagInjected) { + // once and only once + return; + } + try { + var rule = ['.chromeFrameDefaultStyle {', + 'width: 400px;', + 'height: 300px;', + 'padding: 0;', + 'margin: 0;', + '}'].join(''); + var ss = document.createElement('style'); + ss.setAttribute('type', 'text/css'); + if (ss.styleSheet) { + ss.styleSheet.cssText = rule; + } else { + ss.appendChild(document.createTextNode(rule)); + } + var h = document.getElementsByTagName('head')[0]; + if (h.firstChild) { + h.insertBefore(ss, h.firstChild); + } else { + h.appendChild(ss); + } + cfStyleTagInjected = true; + } catch (e) { + // squelch + + // FIXME(slightlyoff): log? retry? + } + }; + + /** + * Plucks properties from the passed arguments and sets them on the passed + * DOM node + * @param {Node} node The node to set properties on + * @param {Object} args A map of user-specified properties to set + * @private + */ + var setProperties = function(node, args) { + injectCFStyleTag(); + + var srcNode = byId(args['node']); + + node.id = args['id'] || (srcNode ? srcNode['id'] || getUid(srcNode) : ''); + + // TODO(slightlyoff): Opera compat? need to test there + var cssText = args['cssText'] || ''; + node.style.cssText = ' ' + cssText; + + var classText = args['className'] || ''; + node.className = 'chromeFrameDefaultStyle ' + classText; + + // default if the browser doesn't so we don't show sad-tab + var src = args['src'] || 'about:blank'; + + if (ua.isIE || ua.isOpera) { + node.src = src; + } else { + // crazyness regarding when things are set in NPAPI + node.setAttribute('src', src); + } + + if (srcNode) { + srcNode.parentNode.replaceChild(node, srcNode); + } + }; + + /** + * Creates a plugin instance, taking named parameters from the passed args. + * @param {Object} args A bag of configuration properties, including values + * like 'node', 'cssText', 'className', 'id', 'src', etc. + * @return {Node} + * @private + */ + var makeCFPlugin = function(args) { + var el; // the element + if (!ua.isIE) { + el = document.createElement('object'); + el.setAttribute("type", "application/chromeframe"); + } else { + var dummy = document.createElement('span'); + dummy.innerHTML = [ + '<object codeBase="//www.google.com"', + "type='application/chromeframe'", + 'classid="CLSID:E0A900DF-9611-4446-86BD-4B1D47E7DB2A"></object>' + ].join(' '); + el = dummy.firstChild; + } + setProperties(el, args); + return el; + }; + + /** + * Creates an iframe in lieu of a ChromeFrame plugin, taking named parameters + * from the passed args. + * @param {Object} args A bag of configuration properties, including values + * like 'node', 'cssText', 'className', 'id', 'src', etc. + * @return {Node} + * @private + */ + var makeCFIframe = function(args) { + var el = document.createElement('iframe'); + setProperties(el, args); + // FIXME(slightlyoff): + // This is where we'll need to slot in "don't fire load events for + // fallback URL" logic. + listen(el, 'load', hitch(el, '_dispatch', 'load')); + return el; + }; + + + var msgPrefix = 'CFInstance.rpc:'; + + /** + * A class that provides the ability for widget-mode hosted content to more + * easily call hosting-page exposed APIs (and vice versa). It builds upon the + * message-passing nature of ChromeFrame to route messages to the other + * side's RPC host and coordinate events such as 'RPC readyness', buffering + * calls until both sides indicate they are ready to participate. + * @constructor + * @public + */ + var RPC = function(instance) { + this.initDeferred = new Deferred(); + + this.instance = instance; + + instance.listen('message', hitch(this, '_handleMessage')); + + this._localExposed = {}; + this._doWithAckCallbacks = {}; + + this._open = false; + this._msgBacklog = []; + + this._initialized = false; + this._exposeMsgBacklog = []; + + this._exposed = false; + this._callRemoteMsgBacklog = []; + + this._inFlight = {}; + + var sendLoadMsg = hitch(this, function(evt) { + this.doWithAck('load').addCallback(this, function() { + this._open = true; + this._postMessageBacklog(); + }); + }); + + if (instance['tagName']) { + instance.listen('load', sendLoadMsg); + } else { + sendLoadMsg(); + } + }; + + RPC.prototype._postMessageBacklog = function() { + if (this._open) { + forEach(this._msgBacklog, this._postMessage, this); + this._msgBacklog = []; + } + }; + + RPC.prototype._postMessage = function(msg, force) { + if (!force && !this._open) { + this._msgBacklog.push(msg); + } else { + // FIXME(slightlyoff): need to check domains list here! + this.instance.postMessage(msgPrefix + msg, '*'); + } + }; + + // currently no-ops. We may need them in the future + // RPC.prototype._doWithAck_load = function() { }; + // RPC.prototype._doWithAck_init = function() { }; + + RPC.prototype._doWithAck = function(what) { + var f = this['_doWithAck_' + what]; + if (f) { + f.call(this); + } + + this._postMessage('doWithAckCallback:' + what, what == 'load'); + }; + + RPC.prototype.doWithAck = function(what) { + var d = new Deferred(); + this._doWithAckCallbacks[what] = d; + this._postMessage('doWithAck:' + what, what == 'load'); + return d; + }; + + RPC.prototype._handleMessage = function(evt, stopper) { + var d = String(evt.data); + + if (d.indexOf(msgPrefix) != 0) { + // not for us, allow the event dispatch to continue... + return; + } + + // ...else we're the end of the line for this event + stopper(); + + // see if we know what type of message it is + d = d.substr(msgPrefix.length); + + var cIndex = d.indexOf(':'); + + var type = d.substr(0, cIndex); + + if (type == 'doWithAck') { + this._doWithAck(d.substr(cIndex + 1)); + return; + } + + var msgBody = d.substr(cIndex + 1); + + if (type == 'doWithAckCallback') { + this._doWithAckCallbacks[msgBody].callback(1); + return; + } + + if (type == 'init') { + return; + } + + // All the other stuff we can do uses a JSON payload. + var obj = fromJson(msgBody); + + if (type == 'callRemote') { + + if (obj.method && obj.params && obj.id) { + + var ret = { + success: 0, + returnId: obj.id + }; + + try { + // Undefined isn't valid JSON, so use null as default value. + ret.value = this._localExposed[ obj.method ](evt, obj) || null; + ret.success = 1; + } catch(e) { + ret.error = e.message; + } + + this._postMessage('callReturn:' + toJson(ret)); + } + } + + if (type == 'callReturn') { + // see if we're waiting on an outstanding RPC call, which + // would be identified by returnId. + var rid = obj['returnId']; + if (!rid) { + // throw an error? + return; + } + var callWrap = this._inFlight[rid]; + if (!callWrap) { + return; + } + + if (obj.success) { + callWrap.d.callback(obj['value'] || 1); + } else { + callWrap.d.errback(new Error(obj['error'] || 'unspecified RPC error')); + } + delete this._inFlight[rid]; + } + + }; + + /** + * Makes a method visible to be called + * @param {string} name The name to expose the method at. + * @param {Function|string} method The function (or name of the function) to + * expose. If a name is provided, it's looked up from the passed scope. + * If no scope is provided, the global scope is queried for a function + * with that name. + * @param {Object} scope Optional A scope to bind the passed method to. If + * the method parameter is specified by a string, the method is both + * located on the passed scope and bound to it. + * @param {Array} domains Optional A list of domains in + * 'http://example.com:8080' format which may call the given method. + * Currently un-implemented. + * @public + */ + RPC.prototype.expose = function(name, method, scope, domains) { + scope = scope || window; + method = isString(method) ? scope[method] : method; + + // local call proxy that the other side will hit when calling our method + this._localExposed[name] = function(evt, obj) { + return method.apply(scope, obj.params); + }; + + if (!this._initialized) { + this._exposeMsgBacklog.push(arguments); + return; + } + + var a = [name, method, scope, domains]; + this._sendExpose.apply(this, a); + }; + + RPC.prototype._sendExpose = function(name) { + // now tell the other side that we're advertising this method + this._postMessage('expose:' + toJson({ name: name })); + }; + + + /** + * Calls a remote method asynchronously and returns a Deferred object + * representing the response. + * @param {string} method Name of the method to call. Should be the same name + * which the other side has expose()'d. + * @param {Array} params Optional A list of arguments to pass to the called + * method. All elements in the list must be cleanly serializable to + * JSON. + * @param {CFInstance.Deferred} deferred Optional A Deferred to use for + * reporting the response of the call. If no deferred is passed, a new + * Deferred is created and returned. + * @return {CFInstance.Deferred} + * @public + */ + RPC.prototype.callRemote = function(method, params, timeout, deferred) { + var d = deferred || new Deferred(null, timeout || -1); + + if (!this._exposed) { + var args = toArray(arguments); + args.length = 3; + args.push(d); + this._callRemoteMsgBacklog.push(args); + return d; + } + + + if (!method) { + d.errback('no method provided!'); + return d; + } + + var id = msgPrefix + (_counter++); + + // JSON-ify the whole bundle + var callWrapper = { + method: String(method), + params: params || [], + id: id + }; + var callJson = toJson(callWrapper); + callWrapper.d = d; + this._inFlight[id] = callWrapper; + this._postMessage('callRemote:' + callJson); + return d; + }; + + + /** + * Tells the other side of the connection that we're ready to start receiving + * calls. Returns a Deferred that is called back when both sides have + * initialized and any backlogged messages have been sent. RPC users should + * generally work to make sure that they call expose() on all of the methods + * they'd like to make available to the other side *before* calling init() + * @return {CFInstance.Deferred} + * @public + */ + RPC.prototype.init = function() { + var d = this.initDeferred; + this.doWithAck('init').addCallback(this, function() { + // once the init floodgates are open, send our backlogs one at a time, + // with a little lag in the middle to prevent ordering problems + + this._initialized = true; + while (this._exposeMsgBacklog.length) { + this.expose.apply(this, this._exposeMsgBacklog.shift()); + } + + setTimeout(hitch(this, function(){ + + this._exposed = true; + while (this._callRemoteMsgBacklog.length) { + this.callRemote.apply(this, this._callRemoteMsgBacklog.shift()); + } + + d.callback(1); + + }), 30); + + }); + return d; + }; + + // CFInstance design notes: + // + // The CFInstance constructor is only ever used in host environments. In + // content pages (things hosted by a ChromeFrame instance), CFInstance + // acts as a singleton which provides services like RPC for communicating + // with it's mirror-image object in the hosting environment. We want the + // same methods and properties to be available on *instances* of + // CFInstance objects in the host env as on the singleton in the hosted + // content, despite divergent implementation. + // + // Further complicating things, CFInstance may specialize behavior + // internally based on whether or not it is communicationg with a fallback + // iframe or a 'real' ChromeFrame instance. + + var CFInstance; // forward declaration + var h = window['externalHost']; + var inIframe = (window.parent != window); + + if (inIframe) { + h = window.parent; + } + + var normalizeTarget = function(targetOrigin) { + var l = window.location; + if (!targetOrigin) { + if (l.protocol != 'file:') { + targetOrigin = l.protocol + '//' + l.host + "/"; + } else { + // TODO(slightlyoff): + // is this secure enough? Is there another way to get messages + // flowing reliably across file-hosted documents? + targetOrigin = '*'; + } + } + return targetOrigin; + }; + + var postMessageToDest = function(dest, msg, targetOrigin) { + return dest.postMessage(msg, normalizeTarget(targetOrigin)); + }; + + if (h) { + // + // We're loaded inside a ChromeFrame widget (or something that should look + // like we were). + // + + CFInstance = {}; + + installEvtSys(CFInstance); + + // FIXME(slightlyoff): + // passing a target origin to externalHost's postMessage seems b0rked + // right now, so pass null instead. Will re-enable hitch()'d variant + // once that's fixed. + + // CFInstance.postMessage = hitch(null, postMessageToDest, h); + + CFInstance.postMessage = function(msg, targetOrigin) { + return h.postMessage(msg, + (inIframe ? normalizeTarget(targetOrigin) : null) ); + }; + + // Attach to the externalHost's onmessage to proxy it in to CFInstance's + // onmessage. + var dispatchMsg = function(evt) { + try { + CFInstance._dispatch('message', evt); + } catch(e) { + log(e); + // squelch + } + }; + if (inIframe) { + listen(window, 'message', dispatchMsg); + } else { + h.onmessage = dispatchMsg; + } + + CFInstance.rpc = new RPC(CFInstance); + + _loadedListenerList.push(function(evt) { + CFInstance._dispatch('load', evt); + }); + + } else { + // + // We're the host document. + // + + var installProperties = function(instance, args) { + var s = instance.supportedEvents = ['load', 'message']; + instance._msgPrefix = 'CFMessage:'; + + installEvtSys(instance); + + instance.log = log; + + // set up an RPC instance + instance.rpc = new RPC(instance); + + forEach(s, function(evt) { + var l = args['on' + evt]; + if (l) { + instance.listen(evt, l); + } + }); + + var contentWindow = instance.contentWindow; + + // if it doesn't have a postMessage, route to/from the iframe's built-in + if (typeof instance['postMessage'] == undefStr && !!contentWindow) { + + instance.postMessage = hitch(null, postMessageToDest, contentWindow); + + listen(window, 'message', function(evt) { + if (evt.source == contentWindow) { + instance._dispatch('message', evt); + } + }); + } + + return instance; + }; + + /** + * A class whose instances correspond to ChromeFrame instances. Passing an + * arguments object to CFInstance helps parameterize the instance. + * @constructor + * @public + */ + CFInstance = function(args) { + args = args || {}; + var instance; + var success = false; + + // If we've been passed a CFInstance object as our source node, just + // re-use it. + if (args['node']) { + var n = byId(args['node']); + // Look for CF-specific properties. + if (n && n.tagName == 'OBJECT' && n.success && n.rpc) { + // Navigate, set styles, etc. + setProperties(n, args); + return n; + } + } + + var force = !!args['forcePlugin']; + + if (!force && testRequirements(args['requirements'])) { + instance = makeCFIframe(args); + success = true; + } else if (isCfAvailable()) { + instance = makeCFPlugin(args); + success = true; + } else { + // else create an iframe but load the failure content and + // not the 'normal' content + + // grab the fallback URL, and if none, use the 'click here + // to install ChromeFrame' URL. Note that we only support + // polling for install success if we're using the default + // URL + + var fallback = '//www.google.com/chromeframe'; + + args.src = args['fallback'] || fallback; + instance = makeCFIframe(args); + + if (args.src == fallback) { + // begin polling for install success. + + // TODO(slightlyoff): need to prevent firing of onload hooks! + // TODO(slightlyoff): implement polling + // TODO(slightlyoff): replacement callback? + // TODO(slightlyoff): add flag to disable this behavior + } + } + instance.success = success; + + installProperties(instance, args); + + return instance; + }; + + // compatibility shims for development-time. These mirror the methods that + // are created on the CFInstance singleton if we detect that we're running + // inside of CF. + if (!CFInstance['postMessage']) { + CFInstance.postMessage = function() { + var args = toArray(arguments); + args.unshift('CFInstance.postMessage:'); + log.apply(null, args); + }; + CFInstance.listen = function() { + // this space intentionally left blank + }; + } + } + + // expose some properties + CFInstance.ua = ua; + CFInstance._documentLoaded = documentLoaded; + CFInstance.contentTests = contentTests; + CFInstance.isAvailable = function(requirements) { + var hasCf = isCfAvailable(); + return requirements ? (hasCf || testRequirements(requirements)) : hasCf; + + }; + CFInstance.Deferred = Deferred; + CFInstance.toJson = toJson; + CFInstance.fromJson = fromJson; + CFInstance.log = log; + + // expose CFInstance to the external scope. We've already checked to make + // sure we're not going to blow existing objects away. + scope.CFInstance = CFInstance; + +})( this['ChromeFrameScope'] || this ); + +// vim: shiftwidth=2:et:ai:tabstop=2 diff --git a/chrome_frame/DEPS b/chrome_frame/DEPS new file mode 100644 index 0000000..545ddc6 --- /dev/null +++ b/chrome_frame/DEPS @@ -0,0 +1,6 @@ +deps = {
+ # TODO(slightlyoff): need to add to Chromium third_party/ !!
+ # Chrome Frame needs these gecko SDKs and internals.
+ "src/third_party/xulrunner-sdk":
+ "svn://chrome-svn/chrome/trunk/deps/third_party/xulrunner-sdk",
+}
diff --git a/chrome_frame/bho.cc b/chrome_frame/bho.cc new file mode 100644 index 0000000..e8c0374 --- /dev/null +++ b/chrome_frame/bho.cc @@ -0,0 +1,247 @@ +// Copyright (c) 2009 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. + +#include "chrome_frame/bho.h" + +#include <shlguid.h> +#include <shobjidl.h> + +#include "base/logging.h" +#include "base/registry.h" +#include "base/scoped_bstr_win.h" +#include "base/scoped_comptr_win.h" +#include "base/scoped_variant_win.h" +#include "base/string_util.h" +#include "chrome_tab.h" // NOLINT +#include "chrome_frame/protocol_sink_wrap.h" +#include "chrome_frame/utils.h" +#include "chrome_frame/vtable_patch_manager.h" + +const wchar_t kUrlMonDllName[] = L"urlmon.dll"; +const wchar_t kPatchProtocols[] = L"PatchProtocols"; +static const int kIBrowserServiceOnHttpEquivIndex = 30; + +PatchHelper g_patch_helper; + +BEGIN_VTABLE_PATCHES(IBrowserService) + VTABLE_PATCH_ENTRY(kIBrowserServiceOnHttpEquivIndex, Bho::OnHttpEquiv) +END_VTABLE_PATCHES() + +_ATL_FUNC_INFO Bho::kBeforeNavigate2Info = { + CC_STDCALL, VT_EMPTY, 7, { + VT_DISPATCH, + VT_VARIANT | VT_BYREF, + VT_VARIANT | VT_BYREF, + VT_VARIANT | VT_BYREF, + VT_VARIANT | VT_BYREF, + VT_VARIANT | VT_BYREF, + VT_BOOL | VT_BYREF + } +}; + +Bho::Bho() { +} + +STDMETHODIMP Bho::SetSite(IUnknown* site) { + HRESULT hr = S_OK; + if (site) { + ScopedComPtr<IWebBrowser2> web_browser2; + web_browser2.QueryFrom(site); + if (web_browser2) { + hr = DispEventAdvise(web_browser2, &DIID_DWebBrowserEvents2); + DCHECK(SUCCEEDED(hr)) << "DispEventAdvise failed. Error: " << hr; + } + + if (g_patch_helper.state() == PatchHelper::PATCH_IBROWSER) { + ScopedComPtr<IBrowserService> browser_service; + hr = DoQueryService(SID_SShellBrowser, site, browser_service.Receive()); + DCHECK(browser_service) << "DoQueryService - SID_SShellBrowser failed." + << " Site: " << site << " Error: " << hr; + if (browser_service) { + g_patch_helper.PatchBrowserService(browser_service); + DCHECK(SUCCEEDED(hr)) << "vtable_patch::PatchInterfaceMethods failed." + << " Site: " << site << " Error: " << hr; + } + } + } + + return IObjectWithSiteImpl<Bho>::SetSite(site); +} + +STDMETHODIMP Bho::BeforeNavigate2(IDispatch* dispatch, VARIANT* url, + VARIANT* flags, VARIANT* target_frame_name, VARIANT* post_data, + VARIANT* headers, VARIANT_BOOL* cancel) { + ScopedComPtr<IWebBrowser2> web_browser2; + if (dispatch) + web_browser2.QueryFrom(dispatch); + + if (!web_browser2) { + NOTREACHED() << "Can't find WebBrowser2 with given dispatch"; + return S_OK; // Return success, we operate on best effort basis. + } + + DLOG(INFO) << "BeforeNavigate2: " << url->bstrVal; + + if (g_patch_helper.state() == PatchHelper::PATCH_IBROWSER) { + VARIANT_BOOL is_top_level = VARIANT_FALSE; + web_browser2->get_TopLevelContainer(&is_top_level); + + std::wstring current_url; + bool is_chrome_protocol = false; + if (is_top_level && IsValidUrlScheme(url->bstrVal)) { + current_url.assign(url->bstrVal, SysStringLen(url->bstrVal)); + is_chrome_protocol = StartsWith(current_url, kChromeProtocolPrefix, + false); + + if (!is_chrome_protocol && IsOptInUrl(current_url.c_str())) { + DLOG(INFO) << "Canceling navigation and switching to cf"; + // Cancel original navigation + *cancel = VARIANT_TRUE; + + // Issue new request with 'cf:' + current_url.insert(0, kChromeProtocolPrefix); + ScopedVariant new_url(current_url.c_str()); + HRESULT hr = web_browser2->Navigate2(new_url.AsInput(), flags, + target_frame_name, post_data, + headers); + DCHECK(SUCCEEDED(hr)) << "web_browser2->Navigate2 failed. Error: " << hr + << std::endl << "Url: " << current_url + << std::endl << "flags: " << flags + << std::endl << "post data: " << post_data + << std::endl << "headers: " << headers; + } + } + } + return S_OK; +} + +HRESULT Bho::FinalConstruct() { + return S_OK; +} + +void Bho::FinalRelease() { +} + +HRESULT STDMETHODCALLTYPE Bho::OnHttpEquiv( + IBrowserService_OnHttpEquiv_Fn original_httpequiv, + IBrowserService* browser, IShellView* shell_view, BOOL done, + VARIANT* in_arg, VARIANT* out_arg) { + if (!done && in_arg && (VT_BSTR == V_VT(in_arg))) { + if (StrStrI(V_BSTR(in_arg), kChromeContentPrefix)) { + // OnHttpEquiv is invoked for meta tags within sub frames as well. + // We want to switch renderers only for the top level frame. Since + // the same |browser| and |shell_view| are passed in to OnHttpEquiv + // even for sub iframes, we determine if this is the top one by + // checking if there are any sub frames created or not. + ScopedComPtr<IWebBrowser2> web_browser2; + DoQueryService(SID_SWebBrowserApp, browser, web_browser2.Receive()); + if (web_browser2 && !HasSubFrames(web_browser2)) + SwitchRenderer(web_browser2, browser, shell_view, V_BSTR(in_arg)); + } + } + + return original_httpequiv(browser, shell_view, done, in_arg, out_arg); +} + +bool Bho::HasSubFrames(IWebBrowser2* web_browser2) { + bool has_sub_frames = false; + ScopedComPtr<IDispatch> doc_dispatch; + if (web_browser2) { + HRESULT hr = web_browser2->get_Document(doc_dispatch.Receive()); + DCHECK(SUCCEEDED(hr) && doc_dispatch) << + "web_browser2->get_Document failed. Error: " << hr; + ScopedComPtr<IOleContainer> container; + if (SUCCEEDED(hr) && doc_dispatch) { + container.QueryFrom(doc_dispatch); + ScopedComPtr<IEnumUnknown> enumerator; + if (container) { + container->EnumObjects(OLECONTF_EMBEDDINGS, enumerator.Receive()); + ScopedComPtr<IUnknown> unk; + ULONG items_retrieved = 0; + if (enumerator) + enumerator->Next(1, unk.Receive(), &items_retrieved); + has_sub_frames = (items_retrieved != 0); + } + } + } + + return has_sub_frames; +} + +HRESULT Bho::SwitchRenderer(IWebBrowser2* web_browser2, + IBrowserService* browser, IShellView* shell_view, + const wchar_t* meta_tag) { + DCHECK(web_browser2 && browser && shell_view && meta_tag); + + // Get access to the mshtml instance and the moniker + ScopedComPtr<IOleObject> mshtml_ole_object; + HRESULT hr = shell_view->GetItemObject(SVGIO_BACKGROUND, IID_IOleObject, + reinterpret_cast<void**>(mshtml_ole_object.Receive())); + if (!mshtml_ole_object) { + NOTREACHED() << "shell_view->GetItemObject failed. Error: " << hr; + return hr; + } + + std::wstring url; + ScopedComPtr<IMoniker> moniker; + hr = mshtml_ole_object->GetMoniker(OLEGETMONIKER_ONLYIFTHERE, + OLEWHICHMK_OBJFULL, moniker.Receive()); + DCHECK(moniker) << "mshtml_ole_object->GetMoniker failed. Error: " << hr; + + if (moniker) + hr = GetUrlFromMoniker(moniker, NULL, &url); + + DCHECK(!url.empty()) << "GetUrlFromMoniker failed. Error: " << hr; + DCHECK(!StartsWith(url, kChromeProtocolPrefix, false)); + + if (!url.empty()) { + url.insert(0, kChromeProtocolPrefix); + // Navigate to new url + VARIANT empty = ScopedVariant::kEmptyVariant; + VARIANT flags = { VT_I4 }; + V_I4(&flags) = 0; + ScopedVariant url_var(url.c_str()); + hr = web_browser2->Navigate2(url_var.AsInput(), &flags, &empty, &empty, + &empty); + DCHECK(SUCCEEDED(hr)) << "web_browser2->Navigate2 failed. Error: " << hr + << std::endl << "Url: " << url; + } + + return S_OK; +} + +void PatchHelper::InitializeAndPatchProtocolsIfNeeded() { + if (state_ != UNKNOWN) + return; + + bool patch_protocol = GetConfigBool(true, kPatchProtocols); + if (patch_protocol) { + ProtocolSinkWrap::PatchProtocolHandler(kUrlMonDllName, CLSID_HttpProtocol); + ProtocolSinkWrap::PatchProtocolHandler(kUrlMonDllName, CLSID_HttpSProtocol); + state_ = PATCH_PROTOCOL; + } else { + state_ = PATCH_IBROWSER; + } +} + +void PatchHelper::PatchBrowserService(IBrowserService* browser_service) { + DCHECK(state_ == PATCH_IBROWSER); + state_ = PATCH_IBROWSER_OK; + vtable_patch::PatchInterfaceMethods(browser_service, + IBrowserService_PatchInfo); +} + +extern vtable_patch::MethodPatchInfo IInternetProtocol_PatchInfo[]; +extern vtable_patch::MethodPatchInfo IInternetProtocolEx_PatchInfo[]; +void PatchHelper::UnpatchIfNeeded() { + if (state_ == PATCH_PROTOCOL) { + vtable_patch::UnpatchInterfaceMethods(IInternetProtocol_PatchInfo); + vtable_patch::UnpatchInterfaceMethods(IInternetProtocolEx_PatchInfo); + } else if (state_ == PATCH_IBROWSER_OK) { + vtable_patch::UnpatchInterfaceMethods(IBrowserService_PatchInfo); + } + + state_ = UNKNOWN; +} + diff --git a/chrome_frame/bho.h b/chrome_frame/bho.h new file mode 100644 index 0000000..ea9fe43 --- /dev/null +++ b/chrome_frame/bho.h @@ -0,0 +1,102 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_FRAME_BHO_H_ +#define CHROME_FRAME_BHO_H_ + +#include <string> + +#include <atlbase.h> +#include <atlcom.h> +#include <exdisp.h> +#include <exdispid.h> +#include <mshtml.h> +#include <shdeprecated.h> + +#include "chrome_tab.h" // NOLINT +#include "chrome_frame/resource.h" +#include "grit/chrome_frame_resources.h" + +class PatchHelper { + public: + enum State { UNKNOWN, PATCH_IBROWSER, PATCH_IBROWSER_OK, PATCH_PROTOCOL }; + PatchHelper() : state_(UNKNOWN) { + } + + State state() const { + return state_; + } + + void InitializeAndPatchProtocolsIfNeeded(); + void PatchBrowserService(IBrowserService* p); + void UnpatchIfNeeded(); + protected: + State state_; +}; + +// Single global variable +extern PatchHelper g_patch_helper; + +class ATL_NO_VTABLE Bho + : public CComObjectRootEx<CComSingleThreadModel>, + public CComCoClass<Bho, &CLSID_ChromeFrameBHO>, + public IObjectWithSiteImpl<Bho>, + public IDispEventSimpleImpl<0, Bho, &DIID_DWebBrowserEvents2> { + public: + typedef HRESULT (STDMETHODCALLTYPE* IBrowserService_OnHttpEquiv_Fn)( + IBrowserService* browser, IShellView* shell_view, BOOL done, + VARIANT* in_arg, VARIANT* out_arg); + +DECLARE_REGISTRY_RESOURCEID(IDR_BHO) +DECLARE_NOT_AGGREGATABLE(Bho) +DECLARE_PROTECT_FINAL_CONSTRUCT() + +BEGIN_COM_MAP(Bho) + COM_INTERFACE_ENTRY(IObjectWithSite) +END_COM_MAP() + +BEGIN_SINK_MAP(Bho) + SINK_ENTRY_INFO(0, DIID_DWebBrowserEvents2, DISPID_BEFORENAVIGATE2, + BeforeNavigate2, &kBeforeNavigate2Info) +END_SINK_MAP() + + // Lifetime management methods + Bho(); + + HRESULT FinalConstruct(); + void FinalRelease(); + + // IObjectWithSite + STDMETHODIMP SetSite(IUnknown* site); + STDMETHOD(BeforeNavigate2)(IDispatch* dispatch, VARIANT* url, VARIANT* flags, + VARIANT* target_frame_name, VARIANT* post_data, VARIANT* headers, + VARIANT_BOOL* cancel); + + // mshtml sends an IOleCommandTarget::Exec of OLECMDID_HTTPEQUIV + // (and OLECMDID_HTTPEQUIV_DONE) as soon as it parses a meta tag. + // It also sends contents of the meta tag as an argument. IEFrame + // handles this in IBrowserService::OnHttpEquiv. So this allows + // us to sniff the META tag by simply patching it. The renderer + // switching can be achieved by cancelling original navigation + // and issuing a new one using IWebBrowser2->Navigate2. + static HRESULT STDMETHODCALLTYPE OnHttpEquiv( + IBrowserService_OnHttpEquiv_Fn original_httpequiv, + IBrowserService* browser, IShellView* shell_view, BOOL done, + VARIANT* in_arg, VARIANT* out_arg); + + protected: + bool PatchProtocolHandler(const CLSID& handler_clsid); + static bool HasSubFrames(IWebBrowser2* web_browser2); + static HRESULT SwitchRenderer(IWebBrowser2* web_browser2, + IBrowserService* browser, IShellView* shell_view, + const wchar_t* meta_tag); + + static _ATL_FUNC_INFO kBeforeNavigate2Info; +}; + +// Add Bho to class library table so IE can CoCreate it. +OBJECT_ENTRY_AUTO(CLSID_ChromeFrameBHO, Bho); + +#endif // CHROME_FRAME_BHO_H_ + diff --git a/chrome_frame/bho.rgs b/chrome_frame/bho.rgs new file mode 100644 index 0000000..824fd7d --- /dev/null +++ b/chrome_frame/bho.rgs @@ -0,0 +1,23 @@ +HKLM {
+ NoRemove SOFTWARE {
+ NoRemove Classes {
+ ChromeFrame.Bho.1 = s 'Bho Class' {
+ CLSID = s '{ECB3C477-1A0A-44bd-BB57-78F9EFE34FA7}'
+ }
+ ChromeFrame.Bho = s 'ChromeFrame BHO' {
+ CLSID = s '{ECB3C477-1A0A-44bd-BB57-78F9EFE34FA7}'
+ CurVer = s 'ChromeFrame.Bho.1'
+ }
+ NoRemove CLSID {
+ ForceRemove {ECB3C477-1A0A-44bd-BB57-78F9EFE34FA7} = s 'ChromeFrame BHO' {
+ ProgID = s 'ChromeFrame.Bho.1'
+ VersionIndependentProgID = s 'ChromeFrame.Bho'
+ InprocServer32 = s '%MODULE%' {
+ val ThreadingModel = s 'Apartment'
+ }
+ 'TypeLib' = s '{6F2664E1-FF6E-488A-BCD1-F4CA6001DFCC}'
+ }
+ }
+ }
+ }
+}
diff --git a/chrome_frame/chrome_active_document.bmp b/chrome_frame/chrome_active_document.bmp Binary files differnew file mode 100644 index 0000000..1229764 --- /dev/null +++ b/chrome_frame/chrome_active_document.bmp diff --git a/chrome_frame/chrome_active_document.cc b/chrome_frame/chrome_active_document.cc new file mode 100644 index 0000000..bbe38b7 --- /dev/null +++ b/chrome_frame/chrome_active_document.cc @@ -0,0 +1,648 @@ +// Copyright (c) 2009 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. + +// Implementation of ChromeActiveDocument +#include "chrome_frame/chrome_active_document.h" + +#include <hlink.h> +#include <htiface.h> +#include <mshtmcid.h> +#include <shdeprecated.h> +#include <shlguid.h> +#include <shobjidl.h> +#include <tlogstg.h> +#include <urlmon.h> +#include <wininet.h> + +#include "base/command_line.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/path_service.h" +#include "base/process_util.h" +#include "base/registry.h" +#include "base/scoped_variant_win.h" +#include "base/string_tokenizer.h" +#include "base/string_util.h" +#include "base/thread.h" +#include "base/thread_local.h" + +#include "grit/generated_resources.h" +#include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/common/navigation_types.h" +#include "chrome/test/automation/browser_proxy.h" +#include "chrome/test/automation/tab_proxy.h" +#include "chrome_frame/utils.h" + +const wchar_t kChromeAttachExternalTabPrefix[] = L"attach_external_tab"; + +static const wchar_t kUseChromeNetworking[] = L"UseChromeNetworking"; +static const wchar_t kHandleTopLevelRequests[] = + L"HandleTopLevelRequests"; + +base::ThreadLocalPointer<ChromeActiveDocument> g_active_doc_cache; + +bool g_first_launch_by_process_ = true; + +ChromeActiveDocument::ChromeActiveDocument() + : first_navigation_(true), + is_automation_client_reused_(false) { +} + +HRESULT ChromeActiveDocument::FinalConstruct() { + // If we have a cached ChromeActiveDocument instance in TLS, then grab + // ownership of the cached document's automation client. This is an + // optimization to get Chrome active documents to load faster. + ChromeActiveDocument* cached_document = g_active_doc_cache.Get(); + if (cached_document) { + DCHECK(automation_client_.get() == NULL); + automation_client_.reset(cached_document->automation_client_.release()); + DLOG(INFO) << "Reusing automation client instance from " + << cached_document; + DCHECK(automation_client_.get() != NULL); + automation_client_->Reinitialize(this); + is_automation_client_reused_ = true; + } else { + // The FinalConstruct implementation in the ChromeFrameActivexBase class + // i.e. Base creates an instance of the ChromeFrameAutomationClient class + // and initializes it, which would spawn a new Chrome process, etc. + // We don't want to be doing this if we have a cached document, whose + // automation client instance can be reused. + HRESULT hr = Base::FinalConstruct(); + if (FAILED(hr)) + return hr; + } + + bool chrome_network = GetConfigBool(false, kUseChromeNetworking); + bool top_level_requests = GetConfigBool(true, kHandleTopLevelRequests); + automation_client_->set_use_chrome_network(chrome_network); + automation_client_->set_handle_top_level_requests(top_level_requests); + + find_dialog_.Init(automation_client_.get()); + + enabled_commands_map_[OLECMDID_PRINT] = true; + enabled_commands_map_[OLECMDID_FIND] = true; + enabled_commands_map_[OLECMDID_CUT] = true; + enabled_commands_map_[OLECMDID_COPY] = true; + enabled_commands_map_[OLECMDID_PASTE] = true; + enabled_commands_map_[OLECMDID_SELECTALL] = true; + return S_OK; +} + +ChromeActiveDocument::~ChromeActiveDocument() { + DLOG(INFO) << __FUNCTION__; + if (find_dialog_.IsWindow()) { + find_dialog_.DestroyWindow(); + } +} + +// Override DoVerb +STDMETHODIMP ChromeActiveDocument::DoVerb(LONG verb, + LPMSG msg, + IOleClientSite* active_site, + LONG index, + HWND parent_window, + LPCRECT pos) { + // IE will try and in-place activate us in some cases. This happens when + // the user opens a new IE window with a URL that has us as the DocObject. + // Here we refuse to be activated in-place and we will force IE to UIActivate + // us. + if (OLEIVERB_INPLACEACTIVATE == verb) { + return E_NOTIMPL; + } + // Check if we should activate as a docobject or not + // (client supports IOleDocumentSite) + if (doc_site_) { + switch (verb) { + case OLEIVERB_SHOW: + case OLEIVERB_OPEN: + case OLEIVERB_UIACTIVATE: + if (!m_bUIActive) { + return doc_site_->ActivateMe(NULL); + } + break; + } + } + return IOleObjectImpl<ChromeActiveDocument>::DoVerb(verb, + msg, + active_site, + index, + parent_window, + pos); +} + +STDMETHODIMP ChromeActiveDocument::InPlaceDeactivate(void) { + // Release the pointers we have no need for now. + doc_site_.Release(); + in_place_frame_.Release(); + return IOleInPlaceObjectWindowlessImpl<ChromeActiveDocument>:: + InPlaceDeactivate(); +} + +// Override IOleInPlaceActiveObjectImpl::OnDocWindowActivate +STDMETHODIMP ChromeActiveDocument::OnDocWindowActivate(BOOL activate) { + DLOG(INFO) << __FUNCTION__; + return S_OK; +} + +STDMETHODIMP ChromeActiveDocument::TranslateAccelerator(MSG* msg) { + DLOG(INFO) << __FUNCTION__; + if (msg == NULL) + return E_POINTER; + + if (msg->message == WM_KEYDOWN && msg->wParam == VK_TAB) { + HWND focus = ::GetFocus(); + if (focus != m_hWnd && !::IsChild(m_hWnd, focus)) { + // The call to SetFocus triggers a WM_SETFOCUS that makes the base class + // set focus to the correct element in Chrome. + ::SetFocus(m_hWnd); + return S_OK; + } + } + + return S_FALSE; +} +// Override IPersistStorageImpl::IsDirty +STDMETHODIMP ChromeActiveDocument::IsDirty() { + DLOG(INFO) << __FUNCTION__; + return S_FALSE; +} + +bool ChromeActiveDocument::is_frame_busting_enabled() { + return false; +} + +STDMETHODIMP ChromeActiveDocument::Load(BOOL fully_avalable, + IMoniker* moniker_name, + LPBC bind_context, + DWORD mode) { + if (NULL == moniker_name) { + return E_INVALIDARG; + } + CComHeapPtr<WCHAR> display_name; + moniker_name->GetDisplayName(bind_context, NULL, &display_name); + std::wstring url = display_name; + + bool is_chrome_protocol = StartsWith(url, kChromeProtocolPrefix, false); + bool is_new_navigation = true; + + if (is_chrome_protocol) { + url.erase(0, lstrlen(kChromeProtocolPrefix)); + is_new_navigation = + !StartsWith(url, kChromeAttachExternalTabPrefix, false); + } + + if (!IsValidUrlScheme(url)) { + DLOG(WARNING) << __FUNCTION__ << " Disallowing navigation to url: " + << url; + return E_INVALIDARG; + } + + if (!is_new_navigation) { + WStringTokenizer tokenizer(url, L"&"); + // Skip over kChromeAttachExternalTabPrefix + tokenizer.GetNext(); + + intptr_t external_tab_cookie = 0; + + if (tokenizer.GetNext()) + StringToInt(tokenizer.token(), + reinterpret_cast<int*>(&external_tab_cookie)); + + if (external_tab_cookie == 0) { + NOTREACHED() << "invalid url for attach tab: " << url; + return E_FAIL; + } + + automation_client_->AttachExternalTab(external_tab_cookie); + } + + // Initiate navigation before launching chrome so that the url will be + // cached and sent with launch settings. + if (is_new_navigation) { + url_.Reset(::SysAllocString(url.c_str())); + if (url_.Length()) { + std::string utf8_url; + WideToUTF8(url_, url_.Length(), &utf8_url); + if (!automation_client_->InitiateNavigation(utf8_url)) { + DLOG(ERROR) << "Invalid URL: " << url; + Error(L"Invalid URL"); + url_.Reset(); + return E_INVALIDARG; + } + + DLOG(INFO) << "Url is " << url_; + } + } + + if (!is_automation_client_reused_ && + !InitializeAutomation(GetHostProcessName(false), L"", IsIEInPrivate())) { + return E_FAIL; + } + + if (!is_chrome_protocol) { + CComObject<UrlmonUrlRequest>* new_request = NULL; + CComObject<UrlmonUrlRequest>::CreateInstance(&new_request); + new_request->AddRef(); + + if (SUCCEEDED(new_request->ConnectToExistingMoniker(moniker_name, + bind_context, + url))) { + base_url_request_.swap(&new_request); + DCHECK(new_request == NULL); + } else { + new_request->Release(); + } + } + + UMA_HISTOGRAM_CUSTOM_COUNTS("ChromeFrame.FullTabLaunchType", + is_chrome_protocol, 0, 1, 2); + return S_OK; +} + +STDMETHODIMP ChromeActiveDocument::Save(IMoniker* moniker_name, + LPBC bind_context, + BOOL remember) { + return E_NOTIMPL; +} + +STDMETHODIMP ChromeActiveDocument::SaveCompleted(IMoniker* moniker_name, + LPBC bind_context) { + return E_NOTIMPL; +} + +STDMETHODIMP ChromeActiveDocument::GetCurMoniker(IMoniker** moniker_name) { + return E_NOTIMPL; +} + +STDMETHODIMP ChromeActiveDocument::GetClassID(CLSID* class_id) { + if (NULL == class_id) { + return E_POINTER; + } + *class_id = GetObjectCLSID(); + return S_OK; +} + +STDMETHODIMP ChromeActiveDocument::QueryStatus(const GUID* cmd_group_guid, + ULONG number_of_commands, + OLECMD commands[], + OLECMDTEXT* command_text) { + DLOG(INFO) << __FUNCTION__; + for (ULONG command_index = 0; command_index < number_of_commands; + command_index++) { + DLOG(INFO) << "Command id = " << commands[command_index].cmdID; + if (enabled_commands_map_.find(commands[command_index].cmdID) != + enabled_commands_map_.end()) { + commands[command_index].cmdf = OLECMDF_ENABLED; + } + } + return S_OK; +} + +STDMETHODIMP ChromeActiveDocument::Exec(const GUID* cmd_group_guid, + DWORD command_id, + DWORD cmd_exec_opt, + VARIANT* in_args, + VARIANT* out_args) { + DLOG(INFO) << __FUNCTION__ << " Cmd id =" << command_id; + // Bail out if we have been uninitialized. + if (automation_client_.get() && automation_client_->tab()) { + return ProcessExecCommand(cmd_group_guid, command_id, cmd_exec_opt, + in_args, out_args); + } + return S_FALSE; +} + +STDMETHODIMP ChromeActiveDocument::GetUrlForEvents(BSTR* url) { + if (NULL == url) { + return E_POINTER; + } + *url = ::SysAllocString(url_); + return S_OK; +} + +HRESULT ChromeActiveDocument::IOleObject_SetClientSite( + IOleClientSite* client_site) { + if (client_site == NULL) { + ChromeActiveDocument* cached_document = g_active_doc_cache.Get(); + if (cached_document) { + DCHECK(this == cached_document); + g_active_doc_cache.Set(NULL); + cached_document->Release(); + } + } + return Base::IOleObject_SetClientSite(client_site); +} + + +HRESULT ChromeActiveDocument::ActiveXDocActivate(LONG verb) { + HRESULT hr = S_OK; + m_bNegotiatedWnd = TRUE; + if (!m_bInPlaceActive) { + hr = m_spInPlaceSite->CanInPlaceActivate(); + if (FAILED(hr)) { + return hr; + } + m_spInPlaceSite->OnInPlaceActivate(); + } + m_bInPlaceActive = TRUE; + // get location in the parent window, + // as well as some information about the parent + ScopedComPtr<IOleInPlaceUIWindow> in_place_ui_window; + frame_info_.cb = sizeof(OLEINPLACEFRAMEINFO); + HWND parent_window = NULL; + if (m_spInPlaceSite->GetWindow(&parent_window) == S_OK) { + in_place_frame_.Release(); + RECT position_rect = {0}; + RECT clip_rect = {0}; + m_spInPlaceSite->GetWindowContext(in_place_frame_.Receive(), + in_place_ui_window.Receive(), + &position_rect, + &clip_rect, + &frame_info_); + if (!m_bWndLess) { + if (IsWindow()) { + ::ShowWindow(m_hWnd, SW_SHOW); + SetFocus(); + } else { + m_hWnd = Create(parent_window, position_rect); + } + } + SetObjectRects(&position_rect, &clip_rect); + } + + ScopedComPtr<IOleInPlaceActiveObject> in_place_active_object(this); + + // Gone active by now, take care of UIACTIVATE + if (DoesVerbUIActivate(verb)) { + if (!m_bUIActive) { + m_bUIActive = TRUE; + hr = m_spInPlaceSite->OnUIActivate(); + if (FAILED(hr)) { + return hr; + } + // set ourselves up in the host + if (in_place_active_object) { + if (in_place_frame_) { + in_place_frame_->SetActiveObject(in_place_active_object, NULL); + } + if (in_place_ui_window) { + in_place_ui_window->SetActiveObject(in_place_active_object, NULL); + } + } + } + } + m_spClientSite->ShowObject(); + return S_OK; +} + +void ChromeActiveDocument::OnNavigationStateChanged(int tab_handle, int flags, + const IPC::NavigationInfo& nav_info) { + // TODO(joshia): handle INVALIDATE_TAB,INVALIDATE_LOAD etc. + DLOG(INFO) << __FUNCTION__ << std::endl << " Flags: " << flags + << "Url: " << nav_info.url << + ", Title: " << nav_info.title << + ", Type: " << nav_info.navigation_type << ", Relative Offset: " << + nav_info.relative_offset << ", Index: " << nav_info.navigation_index;; + + UpdateNavigationState(nav_info); +} + +void ChromeActiveDocument::OnUpdateTargetUrl(int tab_handle, + const std::wstring& new_target_url) { + if (in_place_frame_) { + in_place_frame_->SetStatusText(new_target_url.c_str()); + } +} + +bool IsFindAccelerator(const MSG& msg) { + // TODO(robertshield): This may not stand up to localization. Fix if this + // is the case. + return msg.message == WM_KEYDOWN && msg.wParam == 'F' && + win_util::IsCtrlPressed() && + !(win_util::IsAltPressed() || win_util::IsShiftPressed()); +} + +void ChromeActiveDocument::OnAcceleratorPressed(int tab_handle, + const MSG& accel_message) { + bool handled_accel = false; + if (in_place_frame_ != NULL) { + handled_accel = (S_OK == in_place_frame_->TranslateAcceleratorW( + const_cast<MSG*>(&accel_message), 0)); + } + + if (!handled_accel) { + if (IsFindAccelerator(accel_message)) { + // Handle the showing of the find dialog explicitly. + OnFindInPage(); + } else if (AllowFrameToTranslateAccelerator(accel_message) != S_OK) { + DLOG(INFO) << "IE DID NOT handle accel key " << accel_message.wParam; + TabProxy* tab = GetTabProxy(); + if (tab) { + tab->ProcessUnhandledAccelerator(accel_message); + } + } + } else { + DLOG(INFO) << "IE handled accel key " << accel_message.wParam; + } +} + +void ChromeActiveDocument::OnTabbedOut(int tab_handle, bool reverse) { + DLOG(INFO) << __FUNCTION__; + if (in_place_frame_) { + MSG msg = { NULL, WM_KEYDOWN, VK_TAB }; + in_place_frame_->TranslateAcceleratorW(&msg, 0); + } +} + +void ChromeActiveDocument::OnDidNavigate(int tab_handle, + const IPC::NavigationInfo& nav_info) { + DLOG(INFO) << __FUNCTION__ << std::endl << "Url: " << nav_info.url << + ", Title: " << nav_info.title << + ", Type: " << nav_info.navigation_type << ", Relative Offset: " << + nav_info.relative_offset << ", Index: " << nav_info.navigation_index; + + // This could be NULL if the active document instance is being destroyed. + if (!m_spInPlaceSite) { + DLOG(INFO) << __FUNCTION__ << "m_spInPlaceSite is NULL. Returning"; + return; + } + + UpdateNavigationState(nav_info); +} + +void ChromeActiveDocument::UpdateNavigationState( + const IPC::NavigationInfo& new_navigation_info) { + bool is_title_changed = (navigation_info_.title != new_navigation_info.title); + bool is_url_changed = (navigation_info_.url.is_valid() && + (navigation_info_.url != new_navigation_info.url)); + bool is_ssl_state_changed = + (navigation_info_.security_style != new_navigation_info.security_style) || + (navigation_info_.has_mixed_content != + new_navigation_info.has_mixed_content); + + navigation_info_ = new_navigation_info; + + if (is_title_changed) { + ScopedVariant title(navigation_info_.title.c_str()); + IEExec(NULL, OLECMDID_SETTITLE, OLECMDEXECOPT_DONTPROMPTUSER, + title.AsInput(), NULL); + } + + if (is_ssl_state_changed) { + int lock_status = SECURELOCK_SET_UNSECURE; + switch (navigation_info_.security_style) { + case SECURITY_STYLE_AUTHENTICATION_BROKEN: + lock_status = SECURELOCK_SET_SECUREUNKNOWNBIT; + break; + case SECURITY_STYLE_AUTHENTICATED: + lock_status = navigation_info_.has_mixed_content ? + SECURELOCK_SET_MIXED : SECURELOCK_SET_SECUREUNKNOWNBIT; + break; + default: + break; + } + + ScopedVariant secure_lock_status(lock_status); + IEExec(&CGID_ShellDocView, INTERNAL_CMDID_SET_SSL_LOCK, + OLECMDEXECOPT_DODEFAULT, secure_lock_status.AsInput(), NULL); + } + + if (navigation_info_.url.is_valid() && + (is_url_changed || url_.Length() == 0)) { + url_.Allocate(UTF8ToWide(navigation_info_.url.spec()).c_str()); + // Now call the FireNavigateCompleteEvent which makes IE update the text + // in the address-bar. We call the FireBeforeNavigateComplete2Event and + // FireDocumentComplete event just for completeness sake. If some BHO + // chooses to cancel the navigation in the OnBeforeNavigate2 handler + // we will ignore the cancellation request. + + // Todo(joshia): investigate if there's a better way to set URL in the + // address bar + ScopedComPtr<IWebBrowserEventsService> web_browser_events_svc; + DoQueryService(__uuidof(web_browser_events_svc), m_spClientSite, + web_browser_events_svc.Receive()); + if (web_browser_events_svc) { + // TODO(joshia): maybe we should call FireBeforeNavigate2Event in + // ChromeActiveDocument::Load and abort if cancelled. + VARIANT_BOOL should_cancel = VARIANT_FALSE; + web_browser_events_svc->FireBeforeNavigate2Event(&should_cancel); + web_browser_events_svc->FireNavigateComplete2Event(); + if (VARIANT_TRUE != should_cancel) { + web_browser_events_svc->FireDocumentCompleteEvent(); + } + } + } +} + +void ChromeActiveDocument::OnFindInPage() { + TabProxy* tab = GetTabProxy(); + if (tab) { + if (!find_dialog_.IsWindow()) { + find_dialog_.Create(m_hWnd); + } + + find_dialog_.ShowWindow(SW_SHOW); + } +} + +void ChromeActiveDocument::OnViewSource() { + DCHECK(navigation_info_.url.is_valid()); + std::string url_to_open = "view-source:"; + url_to_open += navigation_info_.url.spec(); + OnOpenURL(0, GURL(url_to_open), NEW_WINDOW); +} + +void ChromeActiveDocument::OnOpenURL(int tab_handle, const GURL& url_to_open, + int open_disposition) { + // If the disposition indicates that we should be opening the URL in the + // current tab, then we can reuse the ChromeFrameAutomationClient instance + // maintained by the current ChromeActiveDocument instance. We cache this + // instance so that it can be used by the new ChromeActiveDocument instance + // which may be instantiated for handling the new URL. + if (open_disposition == CURRENT_TAB) { + // Grab a reference to ensure that the document remains valid. + AddRef(); + g_active_doc_cache.Set(this); + } + + Base::OnOpenURL(tab_handle, url_to_open, open_disposition); +} + +void ChromeActiveDocument::OnLoad(int tab_handle, const GURL& url) { + if (ready_state_ < READYSTATE_COMPLETE) { + ready_state_ = READYSTATE_COMPLETE; + FireOnChanged(DISPID_READYSTATE); + } +} + +bool ChromeActiveDocument::PreProcessContextMenu(HMENU menu) { + ScopedComPtr<IBrowserService> browser_service; + ScopedComPtr<ITravelLog> travel_log; + + DoQueryService(SID_SShellBrowser, m_spClientSite, browser_service.Receive()); + if (!browser_service) + return true; + + browser_service->GetTravelLog(travel_log.Receive()); + if (!travel_log) + return true; + + if (SUCCEEDED(travel_log->GetTravelEntry(browser_service, TLOG_BACK, NULL))) { + EnableMenuItem(menu, IDS_CONTENT_CONTEXT_BACK, MF_BYCOMMAND | MF_ENABLED); + } else { + EnableMenuItem(menu, IDS_CONTENT_CONTEXT_BACK, MF_BYCOMMAND | MFS_DISABLED); + } + + + if (SUCCEEDED(travel_log->GetTravelEntry(browser_service, TLOG_FORE, NULL))) { + EnableMenuItem(menu, IDS_CONTENT_CONTEXT_FORWARD, + MF_BYCOMMAND | MF_ENABLED); + } else { + EnableMenuItem(menu, IDS_CONTENT_CONTEXT_FORWARD, + MF_BYCOMMAND | MFS_DISABLED); + } + + // Call base class (adds 'About' item) + return Base::PreProcessContextMenu(menu); +} + +bool ChromeActiveDocument::HandleContextMenuCommand(UINT cmd) { + ScopedComPtr<IWebBrowser2> web_browser2; + DoQueryService(SID_SWebBrowserApp, m_spClientSite, web_browser2.Receive()); + + switch (cmd) { + case IDS_CONTENT_CONTEXT_BACK: + web_browser2->GoBack(); + break; + + case IDS_CONTENT_CONTEXT_FORWARD: + web_browser2->GoForward(); + break; + + case IDS_CONTENT_CONTEXT_RELOAD: + web_browser2->Refresh(); + break; + + default: + return Base::HandleContextMenuCommand(cmd); + } + + return true; +} + +HRESULT ChromeActiveDocument::IEExec(const GUID* cmd_group_guid, + DWORD command_id, DWORD cmd_exec_opt, + VARIANT* in_args, VARIANT* out_args) { + HRESULT hr = E_FAIL; + ScopedComPtr<IOleCommandTarget> frame_cmd_target; + if (m_spInPlaceSite) + hr = frame_cmd_target.QueryFrom(m_spInPlaceSite); + + if (frame_cmd_target) + hr = frame_cmd_target->Exec(cmd_group_guid, command_id, cmd_exec_opt, + in_args, out_args); + + return hr; +} diff --git a/chrome_frame/chrome_active_document.h b/chrome_frame/chrome_active_document.h new file mode 100644 index 0000000..b488bc5 --- /dev/null +++ b/chrome_frame/chrome_active_document.h @@ -0,0 +1,258 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_FRAME_CHROME_ACTIVE_DOCUMENT_H_ +#define CHROME_FRAME_CHROME_ACTIVE_DOCUMENT_H_ + +#include <atlbase.h> +#include <atlcom.h> +#include <atlctl.h> +#include <map> + +#include "base/scoped_ptr.h" +#include "base/scoped_comptr_win.h" +#include "base/thread.h" + +#include "chrome_frame/chrome_frame_activex_base.h" +#include "chrome_frame/com_type_info_holder.h" +#include "chrome_frame/find_dialog.h" +#include "chrome_frame/in_place_menu.h" +#include "chrome_frame/ole_document_impl.h" +#include "chrome_frame/resource.h" +#include "chrome_frame/extra_system_apis.h" + +class Thread; +class TabProxy; +class ChromeActiveDocument; + +// A call to IOleCommandTarget::Exec on the webbrowser with this command id +// and a command group of CGID_EXPLORER causes IE to finalize the current +// travel log entry and move to a new location (pruning any forward entries +// if needed) +#define INTERNAL_CMDID_FINALIZE_TRAVEL_LOG (38) + +// To set the lock icon status call IOleCommandTarget::Exec on site with +// this command id and a command group of CGID_EXPLORER The in arg is one of +// the values: SECURELOCK_SET_UNSECURE, SECURELOCK_SET_MIXED, +// SECURELOCK_SET_SECURE128BIT etc declared in shdeprecated.h +#define INTERNAL_CMDID_SET_SSL_LOCK (37) + +// A call to IOleCommandTarget::Exec on the webbrowser with this command id +// and a command group of CGID_EXPLORER causes IE to replace the URL in the +// current travel log entry +#define UNDOC_CMDID_REPLACE_CURRENT_TRAVEL_LOG_ENTRY_URL (40) + +#define UNDOC_IE_CONTEXTMENU_ADDFAV (2261) +#define UNDOC_IE_CONTEXTMENU_VIEWSOURCE (2139) +#define UNDOC_IE_CONTEXTMENU_REFRESH (6042) + +// This macro should be defined in the public section of the class. +#define BEGIN_EXEC_COMMAND_MAP(theClass) \ + public: \ + HRESULT ProcessExecCommand(const GUID* cmd_group_guid, DWORD command_id, \ + DWORD cmd_exec_opt, VARIANT* in_args, \ + VARIANT* out_args) { \ + HRESULT hr = OLECMDERR_E_NOTSUPPORTED; \ + switch (command_id) { + + +#define EXEC_COMMAND_HANDLER(id, handler) \ + case id: { \ + hr = S_OK; \ + handler(cmd_group_guid, command_id, cmd_exec_opt, in_args, out_args) \ + break; \ + } + +#define EXEC_COMMAND_HANDLER_NO_ARGS(id, handler) \ + case id: { \ + hr = S_OK; \ + handler(); \ + break; \ + } + +#define EXEC_COMMAND_HANDLER_GENERIC(id, code) \ + case id: { \ + hr = S_OK; \ + code; \ + break; \ + } + +#define END_EXEC_COMMAND_MAP() \ + default: \ + break; \ + } \ + return hr; \ +} + +// ChromeActiveDocument: Implementation of the Active Document object that is +// responsible for rendering pages in Chrome. This object delegates to +// Chrome.exe (via the Chrome IPC-based automation mechanism) for the actual +// rendering +class ATL_NO_VTABLE ChromeActiveDocument + : public ChromeFrameActivexBase<ChromeActiveDocument, + CLSID_ChromeActiveDocument>, + public IOleDocumentImpl<ChromeActiveDocument>, + public IOleDocumentViewImpl<ChromeActiveDocument>, + public IPersistMoniker, + public IOleCommandTarget, + public InPlaceMenu<ChromeActiveDocument>, + public IWebBrowserEventsUrlService { + public: + typedef ChromeFrameActivexBase<ChromeActiveDocument, + CLSID_ChromeActiveDocument> Base; + ChromeActiveDocument(); + ~ChromeActiveDocument(); + + DECLARE_REGISTRY_RESOURCEID(IDR_CHROMEACTIVEDOCUMENT) + +BEGIN_COM_MAP(ChromeActiveDocument) + COM_INTERFACE_ENTRY(IOleDocument) + COM_INTERFACE_ENTRY(IOleDocumentView) + COM_INTERFACE_ENTRY(IPersistMoniker) + COM_INTERFACE_ENTRY(IOleCommandTarget) + COM_INTERFACE_ENTRY(IWebBrowserEventsUrlService) + COM_INTERFACE_ENTRY_CHAIN(Base) +END_COM_MAP() + +BEGIN_MSG_MAP(ChromeActiveDocument) + CHAIN_MSG_MAP(Base) +END_MSG_MAP() + + HRESULT FinalConstruct(); + +#define FORWARD_TAB_COMMAND(id, command) \ + EXEC_COMMAND_HANDLER_GENERIC(id, GetTabProxy() ? GetTabProxy()->command() : 1) + +BEGIN_EXEC_COMMAND_MAP(ChromeActiveDocument) + EXEC_COMMAND_HANDLER_GENERIC(OLECMDID_PRINT, automation_client_->PrintTab()) + EXEC_COMMAND_HANDLER_NO_ARGS(OLECMDID_FIND, OnFindInPage) + EXEC_COMMAND_HANDLER_NO_ARGS(UNDOC_IE_CONTEXTMENU_VIEWSOURCE, OnViewSource) + FORWARD_TAB_COMMAND(OLECMDID_SELECTALL, SelectAll) + FORWARD_TAB_COMMAND(OLECMDID_CUT, Cut) + FORWARD_TAB_COMMAND(OLECMDID_COPY, Copy) + FORWARD_TAB_COMMAND(OLECMDID_PASTE, Paste) + FORWARD_TAB_COMMAND(OLECMDID_REFRESH, ReloadAsync) + FORWARD_TAB_COMMAND(OLECMDID_STOP, StopAsync) +END_EXEC_COMMAND_MAP() + + // IPCs from automation server. + virtual void OnNavigationStateChanged(int tab_handle, int flags, + const IPC::NavigationInfo& nav_info); + virtual void OnUpdateTargetUrl(int tab_handle, + const std::wstring& new_target_url); + virtual void OnAcceleratorPressed(int tab_handle, const MSG& accel_message); + virtual void OnTabbedOut(int tab_handle, bool reverse); + virtual void OnDidNavigate(int tab_handle, + const IPC::NavigationInfo& nav_info); + + void OnFindInPage(); + + // Override DoVerb + STDMETHOD(DoVerb)(LONG verb, + LPMSG msg, + IOleClientSite* active_site, + LONG index, + HWND parent_window, + LPCRECT pos); + STDMETHOD(InPlaceDeactivate)(void); + + // Override IOleInPlaceActiveObjectImpl::OnDocWindowActivate + STDMETHOD(OnDocWindowActivate)(BOOL activate); + STDMETHOD(TranslateAccelerator)(MSG* msg); + + // IPersistMoniker methods + STDMETHOD(GetClassID)(CLSID* class_id); + STDMETHOD(IsDirty)(); + STDMETHOD(GetCurMoniker)(IMoniker** moniker_name); + STDMETHOD(Load)(BOOL fully_avalable, + IMoniker* moniker_name, + LPBC bind_context, + DWORD mode); + STDMETHOD(Save)(IMoniker* moniker_name, + LPBC bind_context, + BOOL remember); + STDMETHOD(SaveCompleted)(IMoniker* moniker_name, + LPBC bind_context); + + // IOleCommandTarget methods + STDMETHOD(QueryStatus)(const GUID* cmd_group_guid, + ULONG number_of_commands, + OLECMD commands[], + OLECMDTEXT* command_text); + STDMETHOD(Exec)(const GUID* cmd_group_guid, DWORD command_id, + DWORD cmd_exec_opt, + VARIANT* in_args, + VARIANT* out_args); + + // IWebBrowserEventsUrlService methods + STDMETHOD(GetUrlForEvents)(BSTR* url); + + // ChromeFrameActivexBase overrides + HRESULT IOleObject_SetClientSite(IOleClientSite* client_site); + + HRESULT ActiveXDocActivate(LONG verb); + + // Callbacks from ChromeFramePlugin<T> + bool PreProcessContextMenu(HMENU menu); + bool HandleContextMenuCommand(UINT cmd); + + // Should connections initiated by this class try to block + // responses served with the X-Frame-Options header? + bool is_frame_busting_enabled(); + + protected: + // ChromeFrameActivexBase overrides + virtual void OnOpenURL(int tab_handle, const GURL& url_to_open, + int open_disposition); + + virtual void OnLoad(int tab_handle, const GURL& url); + + // A helper method that updates our internal navigation state + // as well as IE's navigation state (viz Title and current URL). + // The navigation_flags is a TabContents::InvalidateTypes enum + void UpdateNavigationState(const IPC::NavigationInfo& nav_info); + + TabProxy* GetTabProxy() const { + if (automation_client_.get()) + return automation_client_->tab(); + return NULL; + } + + // Exec command handlers + void OnViewSource(); + + // Call exec on our site's command target + HRESULT IEExec(const GUID* cmd_group_guid, DWORD command_id, + DWORD cmd_exec_opt, VARIANT* in_args, VARIANT* out_args); + + protected: + typedef std::map<int, bool> EnabledCommandsMap; + + IPC::NavigationInfo navigation_info_; + bool is_doc_object_; + + // This indicates whether this is the first navigation in this + // active document. It is initalize to true and it is set to false + // after we get a navigation notification from Chrome + bool first_navigation_; + + // Our find dialog + CFFindDialog find_dialog_; + + // Contains the list of enabled commands ids. + EnabledCommandsMap enabled_commands_map_; + + // Set to true if the automation_client_ member is initialized from + // an existing ChromeActiveDocument instance which is going away and + // a new ChromeActiveDocument instance is taking its place. + bool is_automation_client_reused_; + + public: + ScopedComPtr<IOleInPlaceFrame> in_place_frame_; + OLEINPLACEFRAMEINFO frame_info_; +}; + +OBJECT_ENTRY_AUTO(__uuidof(ChromeActiveDocument), ChromeActiveDocument) + +#endif // CHROME_FRAME_CHROME_ACTIVE_DOCUMENT_H_ diff --git a/chrome_frame/chrome_active_document.rgs b/chrome_frame/chrome_active_document.rgs new file mode 100644 index 0000000..2fae04b --- /dev/null +++ b/chrome_frame/chrome_active_document.rgs @@ -0,0 +1,67 @@ +HKLM {
+ NoRemove Software {
+ NoRemove Classes {
+ ChromeTab.ChromeActiveDocument.1 = s 'ChromeActiveDocument Class' {
+ CLSID = s '{3E1D0E7F-F5E3-44CC-AA6A-C0A637619AB8}'
+ 'DocObject' = s '0'
+ val EditFlags = d '65536'
+ }
+ ChromeTab.ChromeActiveDocument = s 'ChromeActiveDocument Class' {
+ CLSID = s '{3E1D0E7F-F5E3-44CC-AA6A-C0A637619AB8}'
+ CurVer = s 'ChromeTab.ChromeActiveDocument.1'
+ }
+ NoRemove CLSID {
+ ForceRemove {3E1D0E7F-F5E3-44CC-AA6A-C0A637619AB8} = s 'ChromeActiveDocument Class' {
+ ProgID = s 'ChromeTab.ChromeActiveDocument.1'
+ VersionIndependentProgID = s 'ChromeTab.ChromeActiveDocument'
+ ForceRemove 'Programmable'
+ InprocServer32 = s '%MODULE%' {
+ val ThreadingModel = s 'Apartment'
+ }
+ val AppID = s '%APPID%'
+ ForceRemove 'Control'
+ ForceRemove 'ToolboxBitmap32' = s '%MODULE%, 104'
+ 'DocObject' = s '0'
+ 'MiscStatus' = s '0' {
+ '1' = s '%OLEMISC%'
+ }
+ 'TypeLib' = s '{6F2664E1-FF6E-488A-BCD1-F4CA6001DFCC}'
+ 'Version' = s '1.0'
+ }
+ }
+ }
+ }
+}
+
+HKLM {
+ NoRemove Software {
+ NoRemove Classes {
+ NoRemove MIME {
+ NoRemove Database {
+ NoRemove s 'Content Type' {
+ 'application/chromepage' {
+ val CLSID = s '{3E1D0E7F-F5E3-44CC-AA6A-C0A637619AB8}'
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+HKCU {
+ NoRemove Software {
+ NoRemove Microsoft {
+ NoRemove Windows {
+ NoRemove CurrentVersion {
+ NoRemove 'Internet Settings' {
+ NoRemove 'Secure Mime Handlers' {
+ val ChromeTab.ChromeActiveDocument.1 = d '1'
+ val ChromeTab.ChromeActiveDocument = d '1'
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/chrome_frame/chrome_frame.gyp b/chrome_frame/chrome_frame.gyp new file mode 100644 index 0000000..8782076 --- /dev/null +++ b/chrome_frame/chrome_frame.gyp @@ -0,0 +1,725 @@ +# Copyright (c) 2009 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. + +{ + 'variables': { + 'chromium_code': 1, + 'xul_sdk_dir': '../third_party/xulrunner-sdk/<(OS)', + + # Keep the archive builder happy. + 'chrome_personalization%': 1, + 'use_syncapi_stub%': 0, + + # Deps info. + 'xul_include_directories': [ + # TODO(slightlyoff): pare these down. This makes it too easy to + # regress to using unfrozen FF interfaces. + '<(xul_sdk_dir)/include', + '<(xul_sdk_dir)/include/accessibility', + '<(xul_sdk_dir)/include/alerts', + '<(xul_sdk_dir)/include/appcomps', + '<(xul_sdk_dir)/include/appshell', + '<(xul_sdk_dir)/include/autocomplete', + '<(xul_sdk_dir)/include/autoconfig', + '<(xul_sdk_dir)/include/ax_common', + '<(xul_sdk_dir)/include/browser', + '<(xul_sdk_dir)/include/cairo', + '<(xul_sdk_dir)/include/caps', + '<(xul_sdk_dir)/include/chardet', + '<(xul_sdk_dir)/include/chrome', + '<(xul_sdk_dir)/include/commandhandler', + '<(xul_sdk_dir)/include/composer', + '<(xul_sdk_dir)/include/content', + '<(xul_sdk_dir)/include/contentprefs', + '<(xul_sdk_dir)/include/cookie', + '<(xul_sdk_dir)/include/crashreporter', + '<(xul_sdk_dir)/include/docshell', + '<(xul_sdk_dir)/include/dom', + '<(xul_sdk_dir)/include/downloads', + '<(xul_sdk_dir)/include/editor', + '<(xul_sdk_dir)/include/embed_base', + '<(xul_sdk_dir)/include/embedcomponents', + '<(xul_sdk_dir)/include/expat', + '<(xul_sdk_dir)/include/extensions', + '<(xul_sdk_dir)/include/exthandler', + '<(xul_sdk_dir)/include/exthelper', + '<(xul_sdk_dir)/include/fastfind', + '<(xul_sdk_dir)/include/feeds', + '<(xul_sdk_dir)/include/find', + '<(xul_sdk_dir)/include/gfx', + '<(xul_sdk_dir)/include/htmlparser', + '<(xul_sdk_dir)/include/imgicon', + '<(xul_sdk_dir)/include/imglib2', + '<(xul_sdk_dir)/include/inspector', + '<(xul_sdk_dir)/include/intl', + '<(xul_sdk_dir)/include/jar', + '<(xul_sdk_dir)/include/java', + '<(xul_sdk_dir)/include/jpeg', + '<(xul_sdk_dir)/include/js', + '<(xul_sdk_dir)/include/jsdebug', + '<(xul_sdk_dir)/include/jsurl', + '<(xul_sdk_dir)/include/layout', + '<(xul_sdk_dir)/include/lcms', + '<(xul_sdk_dir)/include/libbz2', + '<(xul_sdk_dir)/include/libmar', + '<(xul_sdk_dir)/include/libpixman', + '<(xul_sdk_dir)/include/libreg', + '<(xul_sdk_dir)/include/liveconnect', + '<(xul_sdk_dir)/include/locale', + '<(xul_sdk_dir)/include/loginmgr', + '<(xul_sdk_dir)/include/lwbrk', + '<(xul_sdk_dir)/include/mimetype', + '<(xul_sdk_dir)/include/morkreader', + '<(xul_sdk_dir)/include/necko', + '<(xul_sdk_dir)/include/nkcache', + '<(xul_sdk_dir)/include/nspr', + '<(xul_sdk_dir)/include/nss', + '<(xul_sdk_dir)/include/oji', + '<(xul_sdk_dir)/include/parentalcontrols', + '<(xul_sdk_dir)/include/pipboot', + '<(xul_sdk_dir)/include/pipnss', + '<(xul_sdk_dir)/include/pippki', + '<(xul_sdk_dir)/include/places', + '<(xul_sdk_dir)/include/plugin', + '<(xul_sdk_dir)/include/png', + '<(xul_sdk_dir)/include/pref', + '<(xul_sdk_dir)/include/prefetch', + '<(xul_sdk_dir)/include/profdirserviceprovider', + '<(xul_sdk_dir)/include/profile', + '<(xul_sdk_dir)/include/rdf', + '<(xul_sdk_dir)/include/rdfutil', + '<(xul_sdk_dir)/include/satchel', + '<(xul_sdk_dir)/include/shistory', + '<(xul_sdk_dir)/include/simple', + '<(xul_sdk_dir)/include/spellchecker', + '<(xul_sdk_dir)/include/sqlite3', + '<(xul_sdk_dir)/include/storage', + '<(xul_sdk_dir)/include/string', + '<(xul_sdk_dir)/include/thebes', + '<(xul_sdk_dir)/include/toolkitcomps', + '<(xul_sdk_dir)/include/txmgr', + '<(xul_sdk_dir)/include/txtsvc', + '<(xul_sdk_dir)/include/uconv', + '<(xul_sdk_dir)/include/ucvcn', + '<(xul_sdk_dir)/include/ucvibm', + '<(xul_sdk_dir)/include/ucvja', + '<(xul_sdk_dir)/include/ucvko', + '<(xul_sdk_dir)/include/ucvlatin', + '<(xul_sdk_dir)/include/ucvmath', + '<(xul_sdk_dir)/include/ucvtw', + '<(xul_sdk_dir)/include/ucvtw2', + '<(xul_sdk_dir)/include/unicharutil', + '<(xul_sdk_dir)/include/update', + '<(xul_sdk_dir)/include/uriloader', + '<(xul_sdk_dir)/include/urlformatter', + '<(xul_sdk_dir)/include/util', + '<(xul_sdk_dir)/include/view', + '<(xul_sdk_dir)/include/webbrowserpersist', + '<(xul_sdk_dir)/include/webbrwsr', + '<(xul_sdk_dir)/include/webshell', + '<(xul_sdk_dir)/include/widget', + '<(xul_sdk_dir)/include/windowwatcher', + '<(xul_sdk_dir)/include/xml', + '<(xul_sdk_dir)/include/xml-rpc', + '<(xul_sdk_dir)/include/xpcom', + '<(xul_sdk_dir)/include/xpconnect', + '<(xul_sdk_dir)/include/xpinstall', + '<(xul_sdk_dir)/include/xulapp', + '<(xul_sdk_dir)/include/xuldoc', + '<(xul_sdk_dir)/include/xulrunner', + '<(xul_sdk_dir)/include/xultmpl', + '<(xul_sdk_dir)/include/zipwriter', + '<(xul_sdk_dir)/include/zlib', + '<(xul_sdk_dir)/sdk/include', + ], + }, + 'includes': [ + '../build/common.gypi', + ], + 'target_defaults': { + 'dependencies': [ + '../chrome/chrome.gyp:chrome_resources', + '../chrome/chrome.gyp:chrome_strings', + '../chrome/chrome.gyp:theme_resources', + '../skia/skia.gyp:skia', + '../third_party/npapi/npapi.gyp:npapi', + ], + 'include_dirs': [ + # all our own includes are relative to src/ + '..', + ], + }, + 'targets': [ + { + # TODO(slightlyoff): de-win23-ify + 'target_name': 'xulrunner_sdk', + 'type': 'none', + 'conditions': [ + ['OS=="win"', { + 'direct_dependent_settings': { + 'include_dirs': [ + '<@(xul_include_directories)', + ], + 'libraries': [ + '../third_party/xulrunner-sdk/win/lib/xpcomglue_s.lib', + '../third_party/xulrunner-sdk/win/lib/xpcom.lib', + '../third_party/xulrunner-sdk/win/lib/nspr4.lib', + ], + }, + },], + ], + }, + { + # build the ICU stubs + 'target_name': 'icu_stubs', + 'type': 'static_library', + 'dependencies': [ + '../base/base.gyp:base', + ], + 'sources': [ + 'icu_stubs.cc' + ], + }, + { + # TODO(slightlyoff): de-win32-ify + # + # build the base_noicu.lib. + 'target_name': 'base_noicu', + 'type': 'none', + 'dependencies': [ + '../base/base.gyp:base', + 'icu_stubs', + ], + 'actions': [ + { + 'action_name': 'combine_libs', + 'msvs_cygwin_shell': 0, + 'inputs': [ + '<(PRODUCT_DIR)/lib/base.lib', + '<(PRODUCT_DIR)/lib/icu_stubs.lib', + ], + 'outputs': [ + '<(PRODUCT_DIR)/lib/base_noicu.lib', + ], + 'action': [ + 'python', + 'combine_libs.py', + '-o <(_outputs)', + '-r (icu_|_icu.obj)', + '<@(_inputs)'], + }, + ], + 'direct_dependent_settings': { + # linker_settings + 'libraries': [ + '<(PRODUCT_DIR)/lib/base_noicu.lib', + ], + }, + }, + { + 'target_name': 'chrome_frame_unittests', + 'type': 'executable', + 'dependencies': [ + '../build/temp_gyp/googleurl.gyp:googleurl', + '../chrome/chrome.gyp:common', + '../chrome/chrome.gyp:utility', + '../testing/gmock.gyp:gmock', + '../testing/gtest.gyp:gtest', + 'base_noicu', + 'icu_stubs', + 'chrome_frame_npapi', + 'chrome_frame_strings', + 'xulrunner_sdk', + ], + 'sources': [ + 'chrome_frame_npapi_unittest.cc', + 'chrome_frame_unittest_main.cc', + 'chrome_launcher_unittest.cc', + 'unittest_precompile.h', + 'unittest_precompile.cc', + 'urlmon_upload_data_stream.cc', + 'urlmon_upload_data_stream_unittest.cc', + 'chrome_frame_histograms.h', + 'chrome_frame_histograms.cc', + ], + 'include_dirs': [ + # To allow including "chrome_tab.h" + '<(INTERMEDIATE_DIR)', + ], + 'resource_include_dirs': [ + '<(INTERMEDIATE_DIR)', + ], + 'conditions': [ + ['OS=="win"', { + 'msvs_settings': { + 'VCLinkerTool': { + 'DelayLoadDLLs': ['xpcom.dll', 'nspr4.dll'], + }, + }, + 'sources': [ + '<(SHARED_INTERMEDIATE_DIR)/ie_alt_tab/chrome_frame_resources.rc', + '<(SHARED_INTERMEDIATE_DIR)/ie_alt_tab/chrome_frame_strings.rc', + ], + 'dependencies': [ + # TODO(slightlyoff): Get automation targets working on OS X + '../chrome/chrome.gyp:automation', + '../chrome/installer/installer.gyp:installer_util', + '../google_update/google_update.gyp:google_update', + ] + }], + ], + }, + { + 'target_name': 'chrome_frame_tests', + 'type': 'executable', + 'dependencies': [ + # 'base_noicu', + '../build/temp_gyp/googleurl.gyp:googleurl', + '../chrome/chrome.gyp:common', + '../chrome/chrome.gyp:utility', + '../testing/gmock.gyp:gmock', + '../testing/gtest.gyp:gtest', + '../third_party/libxml/libxml.gyp:libxml', + '../third_party/libxslt/libxslt.gyp:libxslt', + 'chrome_frame_strings', + 'chrome_frame_npapi', + # 'npchrome_tab', + 'xulrunner_sdk', + ], + 'sources': [ + '../base/test_suite.h', + 'test/chrome_frame_test_utils.cc', + 'test/chrome_frame_test_utils.h', + 'test/chrome_frame_automation_mock.cc', + 'test/chrome_frame_automation_mock.h', + 'test/chrome_frame_unittests.cc', + 'test/chrome_frame_unittests.h', + 'test/com_message_event_unittest.cc', + 'test/function_stub_unittest.cc', + 'test/html_util_unittests.cc', + 'test/http_server.cc', + 'test/http_server.h', + 'test/icu_stubs_unittests.cc', + 'test/run_all_unittests.cc', + 'test/test_server.cc', + 'test/test_server.h', + 'test/test_server_test.cc', + 'test/util_unittests.cc', + 'chrome_frame_automation.cc', + 'chrome_tab.h', + 'chrome_tab.idl', + 'com_message_event.cc', + 'html_utils.cc', + 'sync_msg_reply_dispatcher.cc', + 'sync_msg_reply_dispatcher.h', + 'test_utils.cc', + 'test_utils.h', + 'utils.cc', + 'utils.h', + 'chrome_frame_histograms.h', + 'chrome_frame_histograms.cc', + ], + 'include_dirs': [ + '<@(xul_include_directories)', + '../chrome/third_party/wtl/include', + # To allow including "chrome_tab.h" + '<(INTERMEDIATE_DIR)', + ], + 'resource_include_dirs': [ + '<(INTERMEDIATE_DIR)', + ], + 'conditions': [ + ['OS=="win"', { + 'msvs_settings': { + 'VCLinkerTool': { + 'DelayLoadDLLs': ['xpcom.dll', 'nspr4.dll'], + }, + }, + 'sources': [ + '<(SHARED_INTERMEDIATE_DIR)/ie_alt_tab/chrome_frame_resources.rc', + '<(SHARED_INTERMEDIATE_DIR)/ie_alt_tab/chrome_frame_strings.rc', + ], + 'dependencies': [ + '../chrome/chrome.gyp:automation', + '../chrome/installer/installer.gyp:installer_util', + '../google_update/google_update.gyp:google_update', + ] + }], + ], + }, + { + 'target_name': 'chrome_frame_perftests', + 'type': 'executable', + 'dependencies': [ + '../base/base.gyp:base_gfx', + '../base/base.gyp:test_support_base', + '../build/temp_gyp/googleurl.gyp:googleurl', + '../chrome/chrome.gyp:common', + '../chrome/chrome.gyp:utility', + '../testing/gmock.gyp:gmock', + '../testing/gtest.gyp:gtest', + '../third_party/libxml/libxml.gyp:libxml', + '../third_party/libxslt/libxslt.gyp:libxslt', + 'chrome_frame_strings', + 'xulrunner_sdk', + ], + 'sources': [ + '../base/perf_test_suite.h', + '../base/perftimer.cc', + '../base/test_file_util.h', + '../chrome/test/chrome_process_util.cc', + '../chrome/test/chrome_process_util.h', + '../chrome/test/ui/ui_test.cc', + 'chrome_tab.h', + 'chrome_tab.idl', + 'html_utils.cc', + 'test/perf/chrome_frame_perftest.cc', + 'test/perf/chrome_frame_perftest.h', + 'test/perf/run_all.cc', + 'test/perf/silverlight.cc', + 'test_utils.cc', + 'test_utils.h', + 'utils.cc', + 'utils.h', + ], + 'include_dirs': [ + '<@(xul_include_directories)', + '../chrome/third_party/wtl/include', + # To allow including "chrome_tab.h" + '<(INTERMEDIATE_DIR)', + ], + 'conditions': [ + ['OS=="win"', { + 'dependencies': [ + '../chrome/chrome.gyp:automation', + '../breakpad/breakpad.gyp:breakpad_handler', + '../chrome/installer/installer.gyp:installer_util', + '../google_update/google_update.gyp:google_update', + '../chrome/installer/installer.gyp:installer_util', + ], + 'sources': [ + '../chrome/test/perf/mem_usage_win.cc', + '../chrome/test/chrome_process_util_win.cc', + '../base/test_file_util_win.cc', + ] + }], + ], + }, + + { + 'target_name': 'chrome_frame_net_tests', + 'type': 'executable', + 'dependencies': [ + '../base/base.gyp:test_support_base', + '../chrome/chrome.gyp:browser', + '../chrome/chrome.gyp:chrome_dll_version', + '../chrome/chrome.gyp:chrome_resources', + '../chrome/chrome.gyp:debugger', + '../chrome/chrome.gyp:renderer', + '../chrome/chrome.gyp:syncapi', + '../skia/skia.gyp:skia', + '../testing/gtest.gyp:gtest', + '../third_party/icu/icu.gyp:icui18n', + '../third_party/icu/icu.gyp:icuuc', + 'chrome_frame_npapi', + ], + 'sources': [ + '../net/url_request/url_request_unittest.cc', + '../net/url_request/url_request_unittest.h', + 'test/chrome_frame_test_utils.cc', + 'test/chrome_frame_test_utils.h', + 'test/test_server.cc', + 'test/test_server.h', + 'test/net/dialog_watchdog.cc', + 'test/net/dialog_watchdog.h', + 'test/net/fake_external_tab.cc', + 'test/net/fake_external_tab.h', + 'test/net/process_singleton_subclass.cc', + 'test/net/process_singleton_subclass.h', + 'test/net/test_automation_provider.cc', + 'test/net/test_automation_provider.h', + 'test/net/test_automation_resource_message_filter.cc', + 'test/net/test_automation_resource_message_filter.h', + ], + 'include_dirs': [ + # To allow including "chrome_tab.h" + '<(INTERMEDIATE_DIR)', + ], + 'conditions': [ + ['OS=="win"', { + 'dependencies': [ + '../chrome/chrome.gyp:automation', + '../breakpad/breakpad.gyp:breakpad_handler', + '../chrome/installer/installer.gyp:installer_util', + '../google_update/google_update.gyp:google_update', + '../chrome/installer/installer.gyp:installer_util', + ] + }], + ], + }, + + { + 'target_name': 'chrome_frame_npapi', + 'type': 'static_library', + 'dependencies': [ + 'chrome_frame_strings', + '../chrome/chrome.gyp:common', + 'xulrunner_sdk', + ], + 'sources': [ + 'chrome_frame_automation.cc', + 'chrome_frame_automation.h', + 'chrome_frame_delegate.cc', + 'chrome_frame_delegate.h', + 'chrome_frame_plugin.h', + 'chrome_frame_npapi.cc', + 'chrome_frame_npapi.h', + 'chrome_launcher.cc', + 'chrome_launcher.h', + 'html_utils.cc', + 'html_utils.h', + 'np_browser_functions.cc', + 'np_browser_functions.h', + 'np_event_listener.cc', + 'np_event_listener.h', + 'np_proxy_service.cc', + 'np_proxy_service.h', + 'npapi_url_request.cc', + 'npapi_url_request.h', + 'ns_associate_iid_win.h', + 'ns_isupports_impl.h', + 'plugin_url_request.cc', + 'plugin_url_request.h', + 'scoped_ns_ptr_win.h', + 'sync_msg_reply_dispatcher.cc', + 'sync_msg_reply_dispatcher.h', + 'utils.cc', + 'utils.h', + ], + }, + { + 'target_name': 'chrome_launcher', + 'type': 'executable', + 'msvs_guid': 'B7E540C1-49D9-4350-ACBC-FB8306316D16', + 'dependencies': [], + 'sources': [ + 'chrome_launcher_main.cc', + ], + 'msvs_settings': { + 'VCLinkerTool': { + 'OutputFile': + '..\\chrome\\$(ConfigurationName)\\servers\\$(ProjectName).exe', + # Set /SUBSYSTEM:WINDOWS since this is not a command-line program. + 'SubSystem': '2', + # We're going for minimal size, so no standard library (in release + # builds). + 'IgnoreAllDefaultLibraries': "true", + }, + 'VCCLCompilerTool': { + # Requires standard library, so disable it. + 'BufferSecurityCheck': "false", + }, + }, + 'configurations': { + # Bring back the standard library in debug buidls. + 'Debug': { + 'msvs_settings': { + 'VCLinkerTool': { + 'IgnoreAllDefaultLibraries': "false", + }, + }, + }, + }, + }, + { + 'target_name': 'chrome_frame_strings', + 'type': 'none', + 'rules': [ + { + 'rule_name': 'grit', + 'extension': 'grd', + 'inputs': [ + '../tools/grit/grit.py', + ], + 'variables': { + 'grit_out_dir': '<(SHARED_INTERMEDIATE_DIR)/ie_alt_tab', + }, + 'outputs': [ + '<(SHARED_INTERMEDIATE_DIR)/ie_alt_tab/grit/<(RULE_INPUT_ROOT).h', + '<(SHARED_INTERMEDIATE_DIR)/ie_alt_tab/<(RULE_INPUT_ROOT).pak', + ], + 'action': ['python', '<@(_inputs)', '-i', + '<(RULE_INPUT_PATH)', + 'build', '-o', '<(grit_out_dir)' + ], + 'message': 'Generating resources from <(RULE_INPUT_PATH)', + }, + ], + 'sources': [ + # Localizable resources. + 'resources/chrome_frame_strings.grd', + 'resources/chrome_frame_resources.grd', + ], + 'direct_dependent_settings': { + 'include_dirs': [ + '<(SHARED_INTERMEDIATE_DIR)/ie_alt_tab', + ], + }, + 'conditions': [ + ['OS=="win"', { + 'dependencies': ['../build/win/system.gyp:cygwin'], + }], + ], + }, + { + 'target_name': 'npchrome_tab', + 'type': 'shared_library', + 'msvs_guid': 'E3DE7E63-D3B6-4A9F-BCC4-5C8169E9C9F2', + 'dependencies': [ + 'base_noicu', + 'chrome_frame_npapi', + 'chrome_frame_strings', + 'chrome_launcher', + 'xulrunner_sdk', + '../chrome/chrome.gyp:common', + '../chrome/chrome.gyp:utility', + '../build/temp_gyp/googleurl.gyp:googleurl', + # FIXME(slightlyoff): + # gigantic hack to get these to build from main Chrome sln. + 'chrome_frame_perftests', + 'chrome_frame_tests', + 'chrome_frame_unittests', + 'chrome_frame_net_tests', + ], + 'sources': [ + 'bho.cc', + 'bho.h', + 'bho.rgs', + 'chrome_active_document.bmp', + 'chrome_active_document.cc', + 'chrome_active_document.h', + 'chrome_active_document.rgs', + 'chrome_frame_activex.cc', + 'chrome_frame_activex.h', + 'chrome_frame_activex_base.h', + 'chrome_frame_activex.rgs', + 'chrome_frame_npapi.rgs', + 'chrome_frame_npapi_entrypoints.cc', + 'chrome_protocol.cc', + 'chrome_protocol.h', + 'chrome_protocol.rgs', + 'chrome_tab.cc', + 'chrome_tab.def', + 'chrome_tab.h', + 'chrome_tab.idl', + # FIXME(slightlyoff): For chrome_tab.tlb. Giant hack until we can + # figure out something more gyp-ish. + 'resources/tlb_resource.rc', + 'chrome_tab.rgs', + 'chrome_tab_version.rc.version', + 'com_message_event.cc', + 'com_message_event.h', + 'com_type_info_holder.cc', + 'com_type_info_holder.h', + 'crash_report.cc', + 'crash_report.h', + 'ff_30_privilege_check.cc', + 'ff_privilege_check.h', + 'find_dialog.cc', + 'find_dialog.h', + 'function_stub.h', + 'icu_stubs.cc', + 'iids.cc', + 'in_place_menu.h', + 'ole_document_impl.h', + 'protocol_sink_wrap.cc', + 'protocol_sink_wrap.h', + 'resource.h', + 'script_security_manager.h', + 'sync_msg_reply_dispatcher.cc', + 'sync_msg_reply_dispatcher.h', + 'extra_system_apis.h', + 'urlmon_url_request.cc', + 'urlmon_url_request.h', + 'urlmon_upload_data_stream.cc', + 'urlmon_upload_data_stream.h', + 'vectored_handler-impl.h', + 'vectored_handler.h', + 'vtable_patch_manager.cc', + 'vtable_patch_manager.h', + 'chrome_frame_histograms.h', + 'chrome_frame_histograms.cc', + ], + 'include_dirs': [ + # To allow including "chrome_tab.h" + '<(INTERMEDIATE_DIR)', + '<(INTERMEDIATE_DIR)/../npchrome_tab', + ], + 'conditions': [ + ['OS=="win"', { + # NOTE(slightlyoff): + # this is a fix for the include dirs length limit on the resource + # compiler, tickled by the xul_include_dirs variable + 'resource_include_dirs': [ + '<(INTERMEDIATE_DIR)' + ], + 'sources': [ + '<(SHARED_INTERMEDIATE_DIR)/ie_alt_tab/chrome_frame_resources.rc', + '<(SHARED_INTERMEDIATE_DIR)/ie_alt_tab/chrome_frame_strings.rc', + ], + 'dependencies': [ + '../breakpad/breakpad.gyp:breakpad_handler', + '../chrome/chrome.gyp:automation', + # Make the archive build happy. + '../chrome/chrome.gyp:syncapi', + # Installer + '../chrome/installer/installer.gyp:installer_util', + '../google_update/google_update.gyp:google_update', + ], + 'msvs_settings': { + 'VCLinkerTool': { + 'OutputFile': + '..\\chrome\\$(ConfigurationName)\\servers\\$(ProjectName).dll', + 'DelayLoadDLLs': ['xpcom.dll', 'nspr4.dll'], + 'BaseAddress': '0x33000000', + # Set /SUBSYSTEM:WINDOWS (for consistency). + 'SubSystem': '2', + }, + }, + }], + ], + 'rules': [ + # Borrowed from chrome.gyp:chrome_dll_version, branding references + # removed + { + 'rule_name': 'version', + 'extension': 'version', + 'variables': { + 'version_py': '../chrome/tools/build/version.py', + 'version_path': '../chrome/VERSION', + 'template_input_path': 'chrome_tab_version.rc.version', + }, + 'inputs': [ + '<(template_input_path)', + '<(version_path)', + ], + 'outputs': [ + 'chrome_tab_version.rc', + ], + 'action': [ + 'python', + '<(version_py)', + '-f', '<(version_path)', + '<(template_input_path)', + '<@(_outputs)', + ], + 'process_outputs_as_sources': 1, + 'message': 'Generating version information in <(_outputs)' + }, + ], + }, + ], +} + +# vim: shiftwidth=2:et:ai:tabstop=2 diff --git a/chrome_frame/chrome_frame_activex.cc b/chrome_frame/chrome_frame_activex.cc new file mode 100644 index 0000000..e58a961 --- /dev/null +++ b/chrome_frame/chrome_frame_activex.cc @@ -0,0 +1,505 @@ +// Copyright (c) 2009 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. + +#include "chrome_frame/chrome_frame_activex.h" + +#include <shdeprecated.h> // for IBrowserService2 +#include <wininet.h> + +#include <algorithm> + +#include "base/basictypes.h" +#include "base/command_line.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/path_service.h" +#include "base/process_util.h" +#include "base/scoped_bstr_win.h" +#include "base/string_util.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/test/automation/tab_proxy.h" +#include "googleurl/src/gurl.h" +#include "chrome_frame/com_message_event.h" +#include "chrome_frame/utils.h" + +ChromeFrameActivex::ChromeFrameActivex() { +} + +HRESULT ChromeFrameActivex::FinalConstruct() { + HRESULT hr = Base::FinalConstruct(); + if (FAILED(hr)) + return hr; + + // No need to call FireOnChanged at this point since nobody will be listening. + ready_state_ = READYSTATE_LOADING; + return S_OK; +} + +ChromeFrameActivex::~ChromeFrameActivex() { + // We expect these to be released during a call to SetClientSite(NULL). + DCHECK(onmessage_.size() == 0); + DCHECK(onloaderror_.size() == 0); + DCHECK(onload_.size() == 0); + DCHECK(onreadystatechanged_.size() == 0); +} + +LRESULT ChromeFrameActivex::OnCreate(UINT message, WPARAM wparam, LPARAM lparam, + BOOL& handled) { + Base::OnCreate(message, wparam, lparam, handled); + return 0; +} + +void ChromeFrameActivex::OnAcceleratorPressed(int tab_handle, + const MSG& accel_message) { + DCHECK(m_spInPlaceSite != NULL); + // Allow our host a chance to handle the accelerator. + // This catches things like Ctrl+F, Ctrl+O etc, but not browser + // accelerators such as F11, Ctrl+T etc. + // (see AllowFrameToTranslateAccelerator for those). + HRESULT hr = TranslateAccelerator(const_cast<MSG*>(&accel_message)); + if (hr != S_OK) + hr = AllowFrameToTranslateAccelerator(accel_message); + + DLOG(INFO) << __FUNCTION__ << " browser response: " + << StringPrintf("0x%08x", hr); + + // Last chance to handle the keystroke is to pass it to chromium. + // We do this last partially because there's no way for us to tell if + // chromium actually handled the keystroke, but also since the browser + // should have first dibs anyway. + if (hr != S_OK && automation_client_.get()) { + TabProxy* tab = automation_client_->tab(); + if (tab) { + tab->ProcessUnhandledAccelerator(accel_message); + } + } +} + +HRESULT ChromeFrameActivex::GetContainingDocument(IHTMLDocument2** doc) { + ScopedComPtr<IOleContainer> container; + HRESULT hr = m_spClientSite->GetContainer(container.Receive()); + if (container) + hr = container.QueryInterface(doc); + return hr; +} + +HRESULT ChromeFrameActivex::GetDocumentWindow(IHTMLWindow2** window) { + ScopedComPtr<IHTMLDocument2> document; + HRESULT hr = GetContainingDocument(document.Receive()); + if (document) + hr = document->get_parentWindow(window); + return hr; +} + +void ChromeFrameActivex::OnLoad(int tab_handle, const GURL& gurl) { + ScopedComPtr<IDispatch> event; + std::string url = gurl.spec(); + if (SUCCEEDED(CreateDomEvent("event", url, "", event.Receive()))) + Fire_onload(event); + + FireEvent(onload_, url); + + HRESULT hr = InvokeScriptFunction(onload_handler_, url); + + if (ready_state_ < READYSTATE_COMPLETE) { + ready_state_ = READYSTATE_COMPLETE; + FireOnChanged(DISPID_READYSTATE); + } +} + +void ChromeFrameActivex::OnLoadFailed(int error_code, const std::string& url) { + ScopedComPtr<IDispatch> event; + if (SUCCEEDED(CreateDomEvent("event", url, "", event.Receive()))) + Fire_onloaderror(event); + + FireEvent(onloaderror_, url); + + HRESULT hr = InvokeScriptFunction(onerror_handler_, url); +} + +void ChromeFrameActivex::OnMessageFromChromeFrame(int tab_handle, + const std::string& message, + const std::string& origin, + const std::string& target) { + DLOG(INFO) << __FUNCTION__; + + if (target.compare("*") != 0) { + bool drop = true; + + if (is_privileged_) { + // Forward messages if the control is in privileged mode. + ScopedComPtr<IDispatch> message_event; + if (SUCCEEDED(CreateDomEvent("message", message, origin, + message_event.Receive()))) { + ScopedBstr target_bstr(UTF8ToWide(target).c_str()); + Fire_onprivatemessage(message_event, target_bstr); + + FireEvent(onprivatemessage_, message_event, target_bstr); + } + } else { + if (HaveSameOrigin(target, document_url_)) { + drop = false; + } else { + DLOG(WARNING) << "Dropping posted message since target doesn't match " + "the current document's origin. target=" << target; + } + } + + if (drop) + return; + } + + ScopedComPtr<IDispatch> message_event; + if (SUCCEEDED(CreateDomEvent("message", message, origin, + message_event.Receive()))) { + Fire_onmessage(message_event); + + FireEvent(onmessage_, message_event); + + ScopedVariant event_var; + event_var.Set(static_cast<IDispatch*>(message_event)); + InvokeScriptFunction(onmessage_handler_, event_var.AsInput()); + } +} + +void ChromeFrameActivex::OnAutomationServerLaunchFailed( + AutomationLaunchResult reason, const std::string& server_version) { + Base::OnAutomationServerLaunchFailed(reason, server_version); + + if (reason == AUTOMATION_VERSION_MISMATCH) { + DisplayVersionMismatchWarning(m_hWnd, server_version); + } +} + +HRESULT ChromeFrameActivex::InvokeScriptFunction(const VARIANT& script_object, + const std::string& param) { + ScopedVariant script_arg(UTF8ToWide(param.c_str()).c_str()); + return InvokeScriptFunction(script_object, script_arg.AsInput()); +} + +HRESULT ChromeFrameActivex::InvokeScriptFunction(const VARIANT& script_object, + VARIANT* param) { + if (V_VT(&script_object) != VT_DISPATCH) { + return S_FALSE; + } + + CComPtr<IDispatch> script(script_object.pdispVal); + HRESULT hr = script.Invoke1(static_cast<DISPID>(DISPID_VALUE), param); + // 0x80020101 == SCRIPT_E_REPORTED. + // When the script we're invoking has an error, we get this error back. + DLOG_IF(ERROR, FAILED(hr) && hr != 0x80020101) << "Failed to invoke script"; + + return hr; +} + +HRESULT ChromeFrameActivex::OnDraw(ATL_DRAWINFO& draw_info) { // NO_LINT + HRESULT hr = S_OK; + int dc_type = ::GetObjectType(draw_info.hicTargetDev); + if (dc_type == OBJ_ENHMETADC) { + RECT print_bounds = {0}; + print_bounds.left = draw_info.prcBounds->left; + print_bounds.right = draw_info.prcBounds->right; + print_bounds.top = draw_info.prcBounds->top; + print_bounds.bottom = draw_info.prcBounds->bottom; + + automation_client_->Print(draw_info.hdcDraw, print_bounds); + } else { + hr = Base::OnDraw(draw_info); + } + + return hr; +} + +STDMETHODIMP ChromeFrameActivex::Load(IPropertyBag* bag, IErrorLog* error_log) { + DCHECK(bag); + + const wchar_t* event_props[] = { + (L"onload"), + (L"onloaderror"), + (L"onmessage"), + (L"onreadystatechanged"), + }; + + ScopedComPtr<IHTMLObjectElement> obj_element; + GetObjectElement(obj_element.Receive()); + + ScopedBstr object_id; + GetObjectScriptId(obj_element, object_id.Receive()); + + ScopedComPtr<IHTMLElement2> element; + element.QueryFrom(obj_element); + HRESULT hr = S_OK; + + for (int i = 0; SUCCEEDED(hr) && i < arraysize(event_props); ++i) { + ScopedBstr prop(event_props[i]); + ScopedVariant value; + if (SUCCEEDED(bag->Read(prop, value.Receive(), error_log))) { + if (value.type() != VT_BSTR || + FAILED(hr = CreateScriptBlockForEvent(element, object_id, + V_BSTR(&value), prop))) { + DLOG(ERROR) << "Failed to create script block for " << prop + << StringPrintf(L"hr=0x%08X, vt=%i", hr, value.type()); + } else { + DLOG(INFO) << "script block created for event " << prop << + StringPrintf(" (0x%08X)", hr) << " connections: " << + ProxyDIChromeFrameEvents<ChromeFrameActivex>::m_vec.GetSize(); + } + } else { + DLOG(INFO) << "event property " << prop << " not in property bag"; + } + } + + ScopedVariant src; + if (SUCCEEDED(bag->Read(StackBstr(L"src"), src.Receive(), error_log))) { + if (src.type() == VT_BSTR) { + hr = put_src(V_BSTR(&src)); + DCHECK(hr != E_UNEXPECTED); + } + } + + ScopedVariant use_chrome_network; + if (SUCCEEDED(bag->Read(StackBstr(L"useChromeNetwork"), + use_chrome_network.Receive(), error_log))) { + VariantChangeType(use_chrome_network.AsInput(), + use_chrome_network.AsInput(), + 0, VT_BOOL); + if (use_chrome_network.type() == VT_BOOL) { + hr = put_useChromeNetwork(V_BOOL(&use_chrome_network)); + DCHECK(hr != E_UNEXPECTED); + } + } + + DLOG_IF(ERROR, FAILED(hr)) + << StringPrintf("Failed to load property bag: 0x%08X", hr); + + return hr; +} + +const wchar_t g_activex_mixed_content_error[] = { + L"data:text/html,<html><body><b>ChromeFrame Security Error<br><br>" + L"Cannot navigate to HTTP url when document URL is HTTPS</body></html>"}; + +STDMETHODIMP ChromeFrameActivex::put_src(BSTR src) { + GURL document_url(GetDocumentUrl()); + if (document_url.SchemeIsSecure()) { + GURL source_url(src); + if (!source_url.SchemeIsSecure()) { + Base::put_src(ScopedBstr(g_activex_mixed_content_error)); + return E_ACCESSDENIED; + } + } + return Base::put_src(src); +} + +HRESULT ChromeFrameActivex::IOleObject_SetClientSite( + IOleClientSite* client_site) { + HRESULT hr = Base::IOleObject_SetClientSite(client_site); + if (FAILED(hr) || !client_site) { + EventHandlers* handlers[] = { + &onmessage_, + &onloaderror_, + &onload_, + &onreadystatechanged_, + }; + + for (int i = 0; i < arraysize(handlers); ++i) + handlers[i]->clear(); + + // Drop privileged mode on uninitialization. + is_privileged_ = false; + } else { + ScopedComPtr<IHTMLDocument2> document; + GetContainingDocument(document.Receive()); + if (document) { + ScopedBstr url; + if (SUCCEEDED(document->get_URL(url.Receive()))) + WideToUTF8(url, url.Length(), &document_url_); + } + + // Probe to see whether the host implements the privileged service. + ScopedComPtr<IChromeFramePrivileged> service; + HRESULT service_hr = DoQueryService(SID_ChromeFramePrivileged, client_site, + service.Receive()); + if (SUCCEEDED(service_hr) && service) { + // Does the host want privileged mode? + boolean wants_privileged = false; + service_hr = service->GetWantsPrivileged(&wants_privileged); + + if (SUCCEEDED(service_hr) && wants_privileged) + is_privileged_ = true; + } + + std::wstring chrome_extra_arguments; + std::wstring profile_name(GetHostProcessName(false)); + if (is_privileged_) { + // Does the host want to provide extra arguments? + ScopedBstr extra_arguments_arg; + service_hr = service->GetChromeExtraArguments( + extra_arguments_arg.Receive()); + if (S_OK == service_hr && extra_arguments_arg) + chrome_extra_arguments.assign(extra_arguments_arg, + extra_arguments_arg.Length()); + + ScopedBstr profile_name_arg; + service_hr = service->GetChromeProfileName(profile_name_arg.Receive()); + if (S_OK == service_hr && profile_name_arg) + profile_name.assign(profile_name_arg, profile_name_arg.Length()); + } + + if (!InitializeAutomation(profile_name, chrome_extra_arguments, + IsIEInPrivate())) { + return E_FAIL; + } + } + + return hr; +} + +HRESULT ChromeFrameActivex::GetObjectScriptId(IHTMLObjectElement* object_elem, + BSTR* id) { + DCHECK(object_elem != NULL); + DCHECK(id != NULL); + + HRESULT hr = E_FAIL; + if (object_elem) { + ScopedComPtr<IHTMLElement> elem; + hr = elem.QueryFrom(object_elem); + if (elem) { + hr = elem->get_id(id); + } + } + + return hr; +} + +HRESULT ChromeFrameActivex::GetObjectElement(IHTMLObjectElement** element) { + DCHECK(m_spClientSite); + if (!m_spClientSite) + return E_UNEXPECTED; + + ScopedComPtr<IOleControlSite> site; + HRESULT hr = site.QueryFrom(m_spClientSite); + if (site) { + ScopedComPtr<IDispatch> disp; + hr = site->GetExtendedControl(disp.Receive()); + if (disp) { + hr = disp.QueryInterface(element); + } else { + DCHECK(FAILED(hr)); + } + } + + return hr; +} + +HRESULT ChromeFrameActivex::CreateScriptBlockForEvent( + IHTMLElement2* insert_after, BSTR instance_id, BSTR script, + BSTR event_name) { + DCHECK(insert_after); + DCHECK(::SysStringLen(event_name) > 0); // should always have this + + // This might be 0 if not specified in the HTML document. + if (!::SysStringLen(instance_id)) { + // TODO(tommi): Should we give ourselves an ID if this happens? + NOTREACHED() << "Need to handle this"; + return E_INVALIDARG; + } + + ScopedComPtr<IHTMLDocument2> document; + HRESULT hr = GetContainingDocument(document.Receive()); + if (SUCCEEDED(hr)) { + ScopedComPtr<IHTMLElement> element, new_element; + document->createElement(StackBstr(L"script"), element.Receive()); + if (element) { + ScopedComPtr<IHTMLScriptElement> script_element; + if (SUCCEEDED(hr = script_element.QueryFrom(element))) { + script_element->put_htmlFor(instance_id); + script_element->put_event(event_name); + script_element->put_text(script); + + hr = insert_after->insertAdjacentElement(StackBstr(L"afterEnd"), + element, + new_element.Receive()); + } + } + } + + return hr; +} + +HRESULT ChromeFrameActivex::CreateDomEvent(const std::string& event_type, + const std::string& data, + const std::string& origin, + IDispatch** event) { + DCHECK(event_type.length() > 0); + DCHECK(event != NULL); + + CComObject<ComMessageEvent>* ev = NULL; + HRESULT hr = CComObject<ComMessageEvent>::CreateInstance(&ev); + if (SUCCEEDED(hr)) { + ev->AddRef(); + + ScopedComPtr<IOleContainer> container; + m_spClientSite->GetContainer(container.Receive()); + if (ev->Initialize(container, data, origin, event_type)) { + *event = ev; + } else { + NOTREACHED() << "event->Initialize"; + ev->Release(); + hr = E_UNEXPECTED; + } + } + + return hr; +} + +void ChromeFrameActivex::FireEvent(const EventHandlers& handlers, + const std::string& arg) { + if (handlers.size()) { + ScopedComPtr<IDispatch> event; + if (SUCCEEDED(CreateDomEvent("event", arg, "", event.Receive()))) { + FireEvent(handlers, event); + } + } +} + +void ChromeFrameActivex::FireEvent(const EventHandlers& handlers, + IDispatch* event) { + DCHECK(event != NULL); + VARIANT arg = { VT_DISPATCH }; + arg.pdispVal = event; + DISPPARAMS params = { &arg, NULL, 1, 0 }; + for (EventHandlers::const_iterator it = handlers.begin(); + it != handlers.end(); + ++it) { + HRESULT hr = (*it)->Invoke(DISPID_VALUE, IID_NULL, LOCALE_USER_DEFAULT, + DISPATCH_METHOD, ¶ms, NULL, NULL, NULL); + // 0x80020101 == SCRIPT_E_REPORTED. + // When the script we're invoking has an error, we get this error back. + DLOG_IF(ERROR, FAILED(hr) && hr != 0x80020101) + << StringPrintf(L"Failed to invoke script: 0x%08X", hr); + } +} + +void ChromeFrameActivex::FireEvent(const EventHandlers& handlers, + IDispatch* event, BSTR target) { + DCHECK(event != NULL); + // Arguments in reverse order to event handler function declaration, + // because that's what DISPPARAMS requires. + VARIANT args[2] = { { VT_BSTR }, { VT_DISPATCH }, }; + args[0].bstrVal = target; + args[1].pdispVal = event; + DISPPARAMS params = { args, NULL, arraysize(args), 0 }; + for (EventHandlers::const_iterator it = handlers.begin(); + it != handlers.end(); + ++it) { + HRESULT hr = (*it)->Invoke(DISPID_VALUE, IID_NULL, LOCALE_USER_DEFAULT, + DISPATCH_METHOD, ¶ms, NULL, NULL, NULL); + // 0x80020101 == SCRIPT_E_REPORTED. + // When the script we're invoking has an error, we get this error back. + DLOG_IF(ERROR, FAILED(hr) && hr != 0x80020101) + << StringPrintf(L"Failed to invoke script: 0x%08X", hr); + } +} diff --git a/chrome_frame/chrome_frame_activex.h b/chrome_frame/chrome_frame_activex.h new file mode 100644 index 0000000..07b8122 --- /dev/null +++ b/chrome_frame/chrome_frame_activex.h @@ -0,0 +1,140 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_FRAME_CHROME_FRAME_ACTIVEX_H_ +#define CHROME_FRAME_CHROME_FRAME_ACTIVEX_H_ + +#include <atlbase.h> +#include <atlcom.h> +#include <atlctl.h> + +#include <set> +#include <string> + +#include "base/scoped_bstr_win.h" +#include "base/scoped_comptr_win.h" +#include "base/scoped_variant_win.h" +#include "chrome_frame/chrome_frame_activex_base.h" +#include "chrome_frame/com_type_info_holder.h" +#include "grit/chrome_frame_resources.h" + +// Include without path to make GYP build see it. +#include "chrome_tab.h" // NOLINT + +// ChromeFrameActivex: Implementation of the ActiveX control that is +// responsible for hosting a chrome frame, i.e. an iframe like widget which +// hosts the the chrome window. This object delegates to Chrome.exe +// (via the Chrome IPC-based automation mechanism) for the actual rendering. +class ATL_NO_VTABLE ChromeFrameActivex + : public ChromeFrameActivexBase<ChromeFrameActivex, CLSID_ChromeFrame>, + public IObjectSafetyImpl<ChromeFrameActivex, + INTERFACESAFE_FOR_UNTRUSTED_CALLER | + INTERFACESAFE_FOR_UNTRUSTED_DATA>, + public IPersistPropertyBag { + public: + typedef ChromeFrameActivexBase<ChromeFrameActivex, CLSID_ChromeFrame> Base; + ChromeFrameActivex(); + ~ChromeFrameActivex(); + +DECLARE_REGISTRY_RESOURCEID(IDR_CHROMEFRAME) + +BEGIN_COM_MAP(ChromeFrameActivex) + COM_INTERFACE_ENTRY(IObjectSafety) + COM_INTERFACE_ENTRY(IPersistPropertyBag) + COM_INTERFACE_ENTRY(IConnectionPointContainer) + COM_INTERFACE_ENTRY_CHAIN(Base) +END_COM_MAP() + +BEGIN_MSG_MAP(ChromeFrameActivex) + MESSAGE_HANDLER(WM_CREATE, OnCreate) + CHAIN_MSG_MAP(Base) +END_MSG_MAP() + + HRESULT FinalConstruct(); + + virtual HRESULT OnDraw(ATL_DRAWINFO& draw_info); + + // IPersistPropertyBag implementation + STDMETHOD(GetClassID)(CLSID* class_id) { + if (class_id != NULL) + *class_id = GetObjectCLSID(); + return S_OK; + } + + STDMETHOD(InitNew)() { + return S_OK; + } + + STDMETHOD(Load)(IPropertyBag* bag, IErrorLog* error_log); + + STDMETHOD(Save)(IPropertyBag* bag, BOOL clear_dirty, BOOL save_all) { + return E_NOTIMPL; + } + + // Used to setup the document_url_ member needed for completing navigation. + // Create external tab (possibly in incognito mode). + HRESULT IOleObject_SetClientSite(IOleClientSite *pClientSite); + + // Overridden to perform security checks. + STDMETHOD(put_src)(BSTR src); + + protected: + virtual void OnAcceleratorPressed(int tab_handle, const MSG& accel_message); + virtual void OnLoad(int tab_handle, const GURL& url); + virtual void OnMessageFromChromeFrame(int tab_handle, + const std::string& message, + const std::string& origin, + const std::string& target); + + private: + + LRESULT OnCreate(UINT message, WPARAM wparam, LPARAM lparam, + BOOL& handled); // NO_LINT + + // ChromeFrameDelegate overrides + virtual void ChromeFrameActivex::OnAutomationServerLaunchFailed( + AutomationLaunchResult reason, const std::string& server_version); + virtual void OnLoadFailed(int error_code, const std::string& url); + + // Helper function to execute a function on a script IDispatch interface. + HRESULT InvokeScriptFunction(const VARIANT& script, const std::string& param); + HRESULT InvokeScriptFunction(const VARIANT& script, VARIANT* param); + HRESULT GetContainingDocument(IHTMLDocument2** doc); + HRESULT GetDocumentWindow(IHTMLWindow2** window); + + // Gets the value of the 'id' attribute of the object element. + HRESULT GetObjectScriptId(IHTMLObjectElement* object_elem, BSTR* id); + + // Returns the object element in the HTML page. + // Note that if we're not being hosted inside an HTML + // document, then this call will fail. + HRESULT GetObjectElement(IHTMLObjectElement** element); + + HRESULT CreateScriptBlockForEvent(IHTMLElement2* insert_after, + BSTR instance_id, BSTR script, + BSTR event_name); + + // Creates a new event object that supports the |data| property. + // Note: you should supply an empty string for |origin| unless you're + // creating a "message" event. + HRESULT CreateDomEvent(const std::string& event_type, const std::string& data, + const std::string& origin, IDispatch** event); + + // Utility function that checks the size of the vector and if > 0 creates + // a variant for the string argument and forwards the call to the other + // FireEvent method. + void FireEvent(const EventHandlers& handlers, const std::string& arg); + + // Invokes all registered handlers in a vector of event handlers. + void FireEvent(const EventHandlers& handlers, IDispatch* event); + + // This variant is used for the privatemessage handler only. + void FireEvent(const EventHandlers& handlers, IDispatch* event, + BSTR target); + +}; + +OBJECT_ENTRY_AUTO(__uuidof(ChromeFrame), ChromeFrameActivex) + +#endif // CHROME_FRAME_CHROME_FRAME_ACTIVEX_H_ diff --git a/chrome_frame/chrome_frame_activex.rgs b/chrome_frame/chrome_frame_activex.rgs new file mode 100644 index 0000000..27edbcf --- /dev/null +++ b/chrome_frame/chrome_frame_activex.rgs @@ -0,0 +1,79 @@ +HKLM {
+ NoRemove Software {
+ NoRemove Classes {
+ ChromeTab.ChromeFrame.1 = s 'Chrome Frame' {
+ CLSID = s '{E0A900DF-9611-4446-86BD-4B1D47E7DB2A}'
+ }
+ ChromeTab.ChromeFrame = s 'Chrome Frame' {
+ CLSID = s '{E0A900DF-9611-4446-86BD-4B1D47E7DB2A}'
+ CurVer = s 'ChromeTab.ChromeFrame.1'
+ }
+ NoRemove CLSID {
+ ForceRemove {E0A900DF-9611-4446-86BD-4B1D47E7DB2A} = s 'Chrome Frame' {
+ ProgID = s 'ChromeTab.ChromeFrame.1'
+ VersionIndependentProgID = s 'ChromeTab.ChromeFrame'
+ ForceRemove 'Programmable'
+ InprocServer32 = s '%MODULE%' {
+ val ThreadingModel = s 'Apartment'
+ }
+ val AppID = s '%APPID%'
+ ForceRemove 'Control'
+ ForceRemove 'Programmable'
+ ForceRemove 'Insertable'
+ ForceRemove 'ToolboxBitmap32' = s '%MODULE%, 1'
+ 'MiscStatus' = s '0'
+ {
+ '1' = s '%OLEMISC%'
+ }
+ 'TypeLib' = s '{6F2664E1-FF6E-488A-BCD1-F4CA6001DFCC}'
+ 'Version' = s '1.0'
+ }
+ }
+ }
+
+ NoRemove Microsoft {
+ NoRemove Windows {
+ NoRemove CurrentVersion {
+ NoRemove Ext {
+ NoRemove PreApproved {
+ ForceRemove '{E0A900DF-9611-4446-86BD-4B1D47E7DB2A}' = s '' {
+ }
+ }
+ NoRemove Stats {
+ ForceRemove {E0A900DF-9611-4446-86BD-4B1D47E7DB2A} {
+ ForceRemove 'iexplore' {
+ val Type = d '1'
+ val Flags = d '4'
+ val Count = d '0'
+ val Time = b '%SYSTIME%'
+ ForceRemove AllowedDomains {
+ ForceRemove '*' {
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ NoRemove 'Internet Explorer' {
+ NoRemove 'Low Rights' {
+ NoRemove ElevationPolicy {
+ ForceRemove '{E0A900DF-9611-4446-86BD-4B1D47E7DB2A}' = s '' {
+ val Policy = d '3'
+ val AppName = s '%CHROME_LAUNCHER_APPNAME%'
+ val AppPath = s '%CHROME_LAUNCHER_APPPATH%'
+ }
+ }
+ NoRemove DragDrop {
+ ForceRemove '{E0A900DF-9611-4446-86BD-4B1D47E7DB2A}' = s '' {
+ val Policy = d '3'
+ val AppName = s '%CHROME_APPNAME%'
+ val AppPath = s '%CHROME_APPPATH%'
+ }
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file diff --git a/chrome_frame/chrome_frame_activex_base.h b/chrome_frame/chrome_frame_activex_base.h new file mode 100644 index 0000000..7a1993a --- /dev/null +++ b/chrome_frame/chrome_frame_activex_base.h @@ -0,0 +1,848 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_FRAME_CHROME_FRAME_ACTIVEX_BASE_H_ +#define CHROME_FRAME_CHROME_FRAME_ACTIVEX_BASE_H_ + +#include <atlbase.h> +#include <atlcom.h> +#include <atlctl.h> + +// Copied min/max defs from windows headers to appease atlimage.h. +// TODO(slightlyoff): Figure out of more recent platform SDK's (> 6.1) +// undo the janky "#define NOMINMAX" train wreck. See: +// http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=100703 +#ifndef max +#define max(a,b) (((a) > (b)) ? (a) : (b)) +#endif +#ifndef min +#define min(a,b) (((a) < (b)) ? (a) : (b)) +#endif +#include <atlimage.h> +#undef max +#undef min + +#include <shdeprecated.h> // for IBrowserService2 +#include <shlguid.h> + +#include <set> +#include <string> + +#include "base/histogram.h" +#include "base/scoped_bstr_win.h" +#include "base/scoped_comptr_win.h" +#include "base/scoped_variant_win.h" +#include "base/string_util.h" +#include "grit/chrome_frame_resources.h" +#include "grit/chrome_frame_strings.h" +#include "chrome_frame/chrome_frame_plugin.h" +#include "chrome_frame/com_type_info_holder.h" +#include "chrome_frame/urlmon_url_request.h" + +// Include without path to make GYP build see it. +#include "chrome_tab.h" // NOLINT + +// Connection point class to support firing IChromeFrameEvents (dispinterface). +template<class T> +class ATL_NO_VTABLE ProxyDIChromeFrameEvents + : public IConnectionPointImpl<T, &DIID_DIChromeFrameEvents> { + public: + void FireMethodWithParams(ChromeFrameEventDispId dispid, + const VARIANT* params, size_t num_params) { + T* me = static_cast<T*>(this); + int connections = m_vec.GetSize(); + + for (int connection = 0; connection < connections; ++connection) { + me->Lock(); + CComPtr<IUnknown> sink(m_vec.GetAt(connection)); + me->Unlock(); + + DIChromeFrameEvents* events = static_cast<DIChromeFrameEvents*>(sink.p); + if (events) { + DISPPARAMS disp_params = { + const_cast<VARIANT*>(params), + NULL, + num_params, + 0}; + HRESULT hr = events->Invoke(static_cast<DISPID>(dispid), + DIID_DIChromeFrameEvents, + LOCALE_USER_DEFAULT, DISPATCH_METHOD, + &disp_params, NULL, NULL, NULL); + DLOG_IF(ERROR, FAILED(hr)) << "invoke(" << dispid << ") failed" << + StringPrintf("0x%08X", hr); + } + } + } + + void FireMethodWithParam(ChromeFrameEventDispId dispid, + const VARIANT& param) { + FireMethodWithParams(dispid, ¶m, 1); + } + + void Fire_onload(IDispatch* event) { + VARIANT var = { VT_DISPATCH }; + var.pdispVal = event; + FireMethodWithParam(CF_EVENT_DISPID_ONLOAD, var); + } + + void Fire_onloaderror(IDispatch* event) { + VARIANT var = { VT_DISPATCH }; + var.pdispVal = event; + FireMethodWithParam(CF_EVENT_DISPID_ONLOADERROR, var); + } + + void Fire_onmessage(IDispatch* event) { + VARIANT var = { VT_DISPATCH }; + var.pdispVal = event; + FireMethodWithParam(CF_EVENT_DISPID_ONMESSAGE, var); + } + + void Fire_onreadystatechanged(long readystate) { + VARIANT var = { VT_I4 }; + var.lVal = readystate; + FireMethodWithParam(CF_EVENT_DISPID_ONREADYSTATECHANGED, var); + } + + void Fire_onprivatemessage(IDispatch* event, BSTR target) { + // Arguments in reverse order to the function declaration, because + // that's what DISPPARAMS requires. + VARIANT args[2] = { { VT_BSTR, }, {VT_DISPATCH, } }; + args[0].bstrVal = target; + args[1].pdispVal = event; + + FireMethodWithParams(CF_EVENT_DISPID_ONPRIVATEMESSAGE, + args, + arraysize(args)); + } +}; + +extern bool g_first_launch_by_process_; + +// Common implementation for ActiveX and Active Document +template <class T, const CLSID& class_id> +class ATL_NO_VTABLE ChromeFrameActivexBase : + public CComObjectRootEx<CComSingleThreadModel>, + public IOleControlImpl<T>, + public IOleObjectImpl<T>, + public IOleInPlaceActiveObjectImpl<T>, + public IViewObjectExImpl<T>, + public IOleInPlaceObjectWindowlessImpl<T>, + public ISupportErrorInfo, + public IQuickActivateImpl<T>, + public com_util::IProvideClassInfo2Impl<class_id, + DIID_DIChromeFrameEvents>, + public com_util::IDispatchImpl<IChromeFrame>, + public IConnectionPointContainerImpl<T>, + public ProxyDIChromeFrameEvents<T>, + public IPropertyNotifySinkCP<T>, + public CComCoClass<T, &class_id>, + public CComControl<T>, + public ChromeFramePlugin<T> { + protected: + typedef std::set<ScopedComPtr<IDispatch> > EventHandlers; + + public: + ChromeFrameActivexBase() + : ready_state_(READYSTATE_UNINITIALIZED) { + m_bWindowOnly = TRUE; + } + + ~ChromeFrameActivexBase() { + } + +DECLARE_OLEMISC_STATUS(OLEMISC_RECOMPOSEONRESIZE | OLEMISC_CANTLINKINSIDE | + OLEMISC_INSIDEOUT | OLEMISC_ACTIVATEWHENVISIBLE | + OLEMISC_SETCLIENTSITEFIRST) + +DECLARE_NOT_AGGREGATABLE(T) + +BEGIN_COM_MAP(ChromeFrameActivexBase) + COM_INTERFACE_ENTRY(IChromeFrame) + COM_INTERFACE_ENTRY(IDispatch) + COM_INTERFACE_ENTRY(IViewObjectEx) + COM_INTERFACE_ENTRY(IViewObject2) + COM_INTERFACE_ENTRY(IViewObject) + COM_INTERFACE_ENTRY(IOleInPlaceObjectWindowless) + COM_INTERFACE_ENTRY(IOleInPlaceObject) + COM_INTERFACE_ENTRY2(IOleWindow, IOleInPlaceObjectWindowless) + COM_INTERFACE_ENTRY(IOleInPlaceActiveObject) + COM_INTERFACE_ENTRY(IOleControl) + COM_INTERFACE_ENTRY(IOleObject) + COM_INTERFACE_ENTRY(ISupportErrorInfo) + COM_INTERFACE_ENTRY(IQuickActivate) + COM_INTERFACE_ENTRY(IProvideClassInfo) + COM_INTERFACE_ENTRY(IProvideClassInfo2) + COM_INTERFACE_ENTRY_FUNC_BLIND(0, InterfaceNotSupported) +END_COM_MAP() + +BEGIN_CONNECTION_POINT_MAP(T) + CONNECTION_POINT_ENTRY(IID_IPropertyNotifySink) + CONNECTION_POINT_ENTRY(DIID_DIChromeFrameEvents) +END_CONNECTION_POINT_MAP() + +BEGIN_MSG_MAP(ChromeFrameActivexBase) + MESSAGE_HANDLER(WM_CREATE, OnCreate) + CHAIN_MSG_MAP(ChromeFramePlugin<T>) + CHAIN_MSG_MAP(CComControl<T>) + DEFAULT_REFLECTION_HANDLER() +END_MSG_MAP() + + // IViewObjectEx + DECLARE_VIEW_STATUS(VIEWSTATUS_SOLIDBKGND | VIEWSTATUS_OPAQUE) + + inline HRESULT IViewObject_Draw(DWORD draw_aspect, LONG index,
+ void* aspect_info, DVTARGETDEVICE* ptd, HDC info_dc, HDC dc,
+ LPCRECTL bounds, LPCRECTL win_bounds) {
+ // ATL ASSERTs if dwDrawAspect is DVASPECT_DOCPRINT, so we cheat.
+ DWORD aspect = draw_aspect;
+ if (aspect == DVASPECT_DOCPRINT)
+ aspect = DVASPECT_CONTENT;
+
+ return CComControl<T>::IViewObject_Draw(aspect, index, aspect_info, ptd,
+ info_dc, dc, bounds, win_bounds);
+ }
+ + DECLARE_PROTECT_FINAL_CONSTRUCT() + + HRESULT FinalConstruct() { + if (!Initialize()) + return E_OUTOFMEMORY; + + // Set to true if this is the first launch by this process. + // Used to perform one time tasks. + if (g_first_launch_by_process_) { + g_first_launch_by_process_ = false; + UMA_HISTOGRAM_CUSTOM_COUNTS("ChromeFrame.IEVersion", + GetIEVersion(), + IE_INVALID, + IE_8, + IE_8 + 1); + } + return S_OK; + } + + void FinalRelease() { + } + + static HRESULT WINAPI InterfaceNotSupported(void* pv, REFIID riid, void** ppv, + DWORD dw) { +#ifndef NDEBUG + wchar_t buffer[64] = {0}; + ::StringFromGUID2(riid, buffer, arraysize(buffer)); + DLOG(INFO) << "E_NOINTERFACE: " << buffer; +#endif + return E_NOINTERFACE; + } + + // ISupportsErrorInfo + STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid) { + static const IID* interfaces[] = { + &IID_IChromeFrame, + &IID_IDispatch + }; + + for (int i = 0; i < arraysize(interfaces); ++i) { + if (InlineIsEqualGUID(*interfaces[i], riid)) + return S_OK; + } + return S_FALSE; + } + + // Called to draw our control when chrome hasn't been initialized. + virtual HRESULT OnDraw(ATL_DRAWINFO& draw_info) { // NO_LINT + if (NULL == draw_info.prcBounds) { + NOTREACHED(); + return E_FAIL; + } + // Don't draw anything. + return S_OK; + } + + + // Used to setup the document_url_ member needed for completing navigation. + // Create external tab (possibly in incognito mode). + HRESULT IOleObject_SetClientSite(IOleClientSite* client_site) { + // If we currently have a document site pointer, release it. + doc_site_.Release(); + if (client_site) { + doc_site_.QueryFrom(client_site); + } + + return CComControlBase::IOleObject_SetClientSite(client_site); + } + + bool HandleContextMenuCommand(UINT cmd) { + if (cmd == IDC_ABOUT_CHROME_FRAME) { + int tab_handle = automation_client_->tab()->handle(); + OnOpenURL(tab_handle, GURL("about:version"), NEW_WINDOW); + return true; + } + + return false; + } + + // Should connections initiated by this class try to block + // responses served with the X-Frame-Options header? + // ActiveX controls genereally will want to do this, + // returning true, while true top-level documents + // (ActiveDocument servers) will not. Your specialization + // of this template should implement this method based on how + // it "feels" from a security perspective. If it's hosted in another + // scriptable document, return true, else false. + virtual bool is_frame_busting_enabled() const { + return true; + } + + protected: + virtual void OnTabbedOut(int tab_handle, bool reverse) { + DCHECK(m_bInPlaceActive); + + HWND parent = ::GetParent(m_hWnd); + ::SetFocus(parent); + ScopedComPtr<IOleControlSite> control_site; + control_site.QueryFrom(m_spClientSite); + if (control_site) + control_site->OnFocus(FALSE); + } + + virtual void OnOpenURL(int tab_handle, const GURL& url_to_open, + int open_disposition) { + ScopedComPtr<IWebBrowser2> web_browser2; + DoQueryService(SID_SWebBrowserApp, m_spClientSite, web_browser2.Receive()); + DCHECK(web_browser2); + + ScopedVariant url; + // Check to see if the URL uses a "view-source:" prefix, if so, open it + // using chrome frame full tab mode by using 'cf:' protocol handler. + // Also change the disposition to NEW_WINDOW since IE6 doesn't have tabs. + if (url_to_open.has_scheme() && (url_to_open.SchemeIs("view-source") || + url_to_open.SchemeIs("about"))) { + std::string chrome_url; + chrome_url.append("cf:"); + chrome_url.append(url_to_open.spec()); + url.Set(UTF8ToWide(chrome_url).c_str()); + open_disposition = NEW_WINDOW; + } else { + url.Set(UTF8ToWide(url_to_open.spec()).c_str()); + } + + VARIANT flags = { VT_I4 }; + V_I4(&flags) = 0; + + IEVersion ie_version = GetIEVersion(); + DCHECK(ie_version != NON_IE && ie_version != IE_UNSUPPORTED); + // Since IE6 doesn't support tabs, so we just use window instead. + if (ie_version == IE_6) { + if (open_disposition == NEW_FOREGROUND_TAB || + open_disposition == NEW_BACKGROUND_TAB || + open_disposition == NEW_WINDOW) { + V_I4(&flags) = navOpenInNewWindow; + } else if (open_disposition != CURRENT_TAB) { + NOTREACHED() << "Unsupported open disposition in IE6"; + } + } else { + switch (open_disposition) { + case NEW_FOREGROUND_TAB: + V_I4(&flags) = navOpenInNewTab; + break; + case NEW_BACKGROUND_TAB: + V_I4(&flags) = navOpenInBackgroundTab; + break; + case NEW_WINDOW: + V_I4(&flags) = navOpenInNewWindow; + break; + default: + break; + } + } + + // TODO(sanjeevr): The navOpenInNewWindow flag causes IE to open this + // in a new window ONLY if the user has specified + // "Always open popups in a new window". Otherwise it opens in a new tab. + // We need to investigate more and see if we can force IE to display the + // link in a new window. MSHTML uses the below code to force an open in a + // new window. But this logic also fails for us. Perhaps this flag is not + // honoured if the ActiveDoc is not MSHTML. + // Even the HLNF_DISABLEWINDOWRESTRICTIONS flag did not work. + // Start of MSHTML-like logic. + // CComQIPtr<ITargetFramePriv2> target_frame = web_browser2; + // if (target_frame) { + // CComPtr<IUri> uri; + // CreateUri(UTF8ToWide(open_url_command->url_.spec()).c_str(), + // Uri_CREATE_IE_SETTINGS, 0, &uri); + // CComPtr<IBindCtx> bind_ctx; + // CreateBindCtx(0, &bind_ctx); + // target_frame->AggregatedNavigation2( + // HLNF_TRUSTFIRSTDOWNLOAD|HLNF_OPENINNEWWINDOW, bind_ctx, NULL, + // L"No_Name", uri, L""); + // } + // End of MSHTML-like logic + VARIANT empty = ScopedVariant::kEmptyVariant; + web_browser2->Navigate2(url.AsInput(), &flags, &empty, &empty, &empty); + web_browser2->put_Visible(VARIANT_TRUE); + } + + virtual void OnRequestStart(int tab_handle, int request_id, + const IPC::AutomationURLRequest& request_info) { + scoped_refptr<CComObject<UrlmonUrlRequest> > request; + if (base_url_request_.get() && + GURL(base_url_request_->url()) == GURL(request_info.url)) { + request.swap(base_url_request_); + } else { + CComObject<UrlmonUrlRequest>* new_request = NULL; + CComObject<UrlmonUrlRequest>::CreateInstance(&new_request); + request = new_request; + } + + DCHECK(request.get() != NULL); + + if (request->Initialize(automation_client_.get(), tab_handle, request_id, + request_info.url, request_info.method, + request_info.referrer, + request_info.extra_request_headers, + request_info.upload_data.get(), + static_cast<T*>(this)->is_frame_busting_enabled())) { + // If Start is successful, it will add a self reference. + request->Start(); + request->set_parent_window(m_hWnd); + } + } + + virtual void OnRequestRead(int tab_handle, int request_id, + int bytes_to_read) { + automation_client_->ReadRequest(request_id, bytes_to_read); + } + + virtual void OnRequestEnd(int tab_handle, int request_id, + const URLRequestStatus& status) { + automation_client_->RemoveRequest(request_id, status.status(), true); + } + + virtual void OnSetCookieAsync(int tab_handle, const GURL& url, + const std::string& cookie) { + std::string name; + std::string data; + size_t name_end = cookie.find('='); + if (std::string::npos != name_end) { + name = cookie.substr(0, name_end); + data = cookie.substr(name_end + 1); + } else { + data = cookie; + } + + BOOL ret = InternetSetCookieA(url.spec().c_str(), name.c_str(), + data.c_str()); + DCHECK(ret) << "InternetSetcookie failed. Error: " << GetLastError(); + } + + virtual void OnAttachExternalTab(int tab_handle, + intptr_t cookie, + int disposition) { + std::string url; + url = StringPrintf("cf:attach_external_tab&%d&%d", + cookie, disposition); + OnOpenURL(tab_handle, GURL(url), disposition); + } + + LRESULT OnCreate(UINT message, WPARAM wparam, LPARAM lparam, + BOOL& handled) { // NO_LINT + ModifyStyle(0, WS_CLIPCHILDREN | WS_CLIPSIBLINGS, 0); + automation_client_->SetParentWindow(m_hWnd); + // Only fire the 'interactive' ready state if we aren't there already. + if (ready_state_ < READYSTATE_INTERACTIVE) { + ready_state_ = READYSTATE_INTERACTIVE; + FireOnChanged(DISPID_READYSTATE); + } + return 0; + } + + // ChromeFrameDelegate override + virtual void OnAutomationServerReady() { + ChromeFramePlugin<T>::OnAutomationServerReady(); + + ready_state_ = READYSTATE_COMPLETE; + FireOnChanged(DISPID_READYSTATE); + } + + // ChromeFrameDelegate override + virtual void OnAutomationServerLaunchFailed( + AutomationLaunchResult reason, const std::string& server_version) { + ready_state_ = READYSTATE_UNINITIALIZED; + FireOnChanged(DISPID_READYSTATE); + } + + // Overridden to take advantage of readystate prop changes and send those + // to potential listeners. + HRESULT FireOnChanged(DISPID dispid) { + if (dispid == DISPID_READYSTATE) { + Fire_onreadystatechanged(ready_state_); + } + return __super::FireOnChanged(dispid); + } + + // IChromeFrame + // Property getter/setters for the src attribute, which contains a URL. + // The ChromeFrameActivex control initiates navigation to this URL + // when instantiated. + STDMETHOD(get_src)(BSTR* src) { + if (NULL == src) { + return E_POINTER; + } + + *src = SysAllocString(url_); + return S_OK; + } + + STDMETHOD(put_src)(BSTR src) { + if (src == NULL) + return E_INVALIDARG; + + // Switch the src to UTF8 and try to expand to full URL + std::string src_utf8; + WideToUTF8(src, SysStringLen(src), &src_utf8); + std::string full_url = ResolveURL(GetDocumentUrl(), src_utf8); + if (full_url.empty()) { + return E_INVALIDARG; + } + + // We can initiate navigation here even if ready_state is not complete. + // We do not have to set proxy, and AutomationClient will take care + // of navigation just after CreateExternalTab is done. + if (!automation_client_->InitiateNavigation(full_url)) { + // TODO(robertshield): Make InitiateNavigation return more useful + // error information. + return E_INVALIDARG; + } + + // Save full URL in BSTR member + url_.Reset(::SysAllocString(UTF8ToWide(full_url).c_str())); + + return S_OK; + } + + STDMETHOD(get_onload)(VARIANT* onload_handler) { + if (NULL == onload_handler) + return E_INVALIDARG; + + *onload_handler = onload_handler_.Copy(); + + return S_OK; + } + + // Property setter for the onload attribute, which contains a + // javascript function to be invoked on successful navigation. + STDMETHOD(put_onload)(VARIANT onload_handler) { + if (V_VT(&onload_handler) != VT_DISPATCH) { + DLOG(WARNING) << "Invalid onload handler type: " + << onload_handler.vt + << " specified"; + return E_INVALIDARG; + } + + onload_handler_ = onload_handler; + + return S_OK; + } + + // Property getter/setters for the onloaderror attribute, which contains a + // javascript function to be invoked on navigation failure. + STDMETHOD(get_onloaderror)(VARIANT* onerror_handler) { + if (NULL == onerror_handler) + return E_INVALIDARG; + + *onerror_handler = onerror_handler_.Copy(); + + return S_OK; + } + + STDMETHOD(put_onloaderror)(VARIANT onerror_handler) { + if (V_VT(&onerror_handler) != VT_DISPATCH) { + DLOG(WARNING) << "Invalid onloaderror handler type: " + << onerror_handler.vt + << " specified"; + return E_INVALIDARG; + } + + onerror_handler_ = onerror_handler; + + return S_OK; + } + + // Property getter/setters for the onmessage attribute, which contains a + // javascript function to be invoked when we receive a message from the + // chrome frame. + STDMETHOD(put_onmessage)(VARIANT onmessage_handler) { + if (V_VT(&onmessage_handler) != VT_DISPATCH) { + DLOG(WARNING) << "Invalid onmessage handler type: " + << onmessage_handler.vt + << " specified"; + return E_INVALIDARG; + } + + onmessage_handler_ = onmessage_handler; + + return S_OK; + } + + STDMETHOD(get_onmessage)(VARIANT* onmessage_handler) { + if (NULL == onmessage_handler) + return E_INVALIDARG; + + *onmessage_handler = onmessage_handler_.Copy(); + + return S_OK; + } + + STDMETHOD(get_readyState)(long* ready_state) { // NOLINT + DLOG(INFO) << __FUNCTION__; + DCHECK(ready_state); + + if (!ready_state) + return E_INVALIDARG; + + *ready_state = ready_state_; + + return S_OK; + } + + // Property getter/setters for use_chrome_network flag. This flag + // indicates if chrome network stack is to be used for fetching + // network requests. + STDMETHOD(get_useChromeNetwork)(VARIANT_BOOL* use_chrome_network) { + if (!use_chrome_network) + return E_INVALIDARG; + + *use_chrome_network = + automation_client_->use_chrome_network() ? VARIANT_TRUE : VARIANT_FALSE; + return S_OK; + } + + STDMETHOD(put_useChromeNetwork)(VARIANT_BOOL use_chrome_network) { + if (!is_privileged_) { + DLOG(ERROR) << "Attempt to set useChromeNetwork in non-privileged mode"; + return E_ACCESSDENIED; + } + + automation_client_->set_use_chrome_network( + (VARIANT_FALSE != use_chrome_network)); + return S_OK; + } + + // Posts a message to the chrome frame. + STDMETHOD(postMessage)(BSTR message, VARIANT target) { + if (NULL == message) { + return E_INVALIDARG; + } + + if (!automation_client_.get()) + return E_FAIL; + + std::string utf8_target; + if (target.vt == VT_BSTR) { + int len = ::SysStringLen(target.bstrVal); + if (len == 1 && target.bstrVal[0] == L'*') { + utf8_target = "*"; + } else { + GURL resolved(target.bstrVal); + if (!resolved.is_valid()) { + Error(L"Unable to parse the specified target URL."); + return E_INVALIDARG; + } + + utf8_target = resolved.spec(); + } + } else { + utf8_target = "*"; + } + + std::string utf8_message; + WideToUTF8(message, ::SysStringLen(message), &utf8_message); + + GURL url(GURL(document_url_).GetOrigin()); + std::string origin(url.is_empty() ? "null" : url.spec()); + if (!automation_client_->ForwardMessageFromExternalHost(utf8_message, + origin, + utf8_target)) { + Error(L"Failed to post message to chrome frame"); + return E_FAIL; + } + + return S_OK; + } + + STDMETHOD(addEventListener)(BSTR event_type, IDispatch* listener, + VARIANT use_capture) { + EventHandlers* handlers = NULL; + HRESULT hr = GetHandlersForEvent(event_type, &handlers); + if (FAILED(hr)) + return hr; + + DCHECK(handlers != NULL); + + handlers->insert(ScopedComPtr<IDispatch>(listener)); + + return hr; + } + + STDMETHOD(removeEventListener)(BSTR event_type, IDispatch* listener, + VARIANT use_capture) { + EventHandlers* handlers = NULL; + HRESULT hr = GetHandlersForEvent(event_type, &handlers); + if (FAILED(hr)) + return hr; + + DCHECK(handlers != NULL); + std::remove(handlers->begin(), handlers->end(), listener); + + return hr; + } + + STDMETHOD(get_version)(BSTR* version) { + if (!automation_client_.get()) { + NOTREACHED(); + return E_FAIL; + } + + if (version == NULL) { + return E_INVALIDARG; + } + + *version = SysAllocString(automation_client_->GetVersion().c_str()); + return S_OK; + } + + STDMETHOD(postPrivateMessage)(BSTR message, BSTR origin, BSTR target) { + if (NULL == message) + return E_INVALIDARG; + + if (!is_privileged_) { + DLOG(ERROR) << "Attempt to postPrivateMessage in non-privileged mode"; + return E_ACCESSDENIED; + } + + DCHECK(automation_client_.get()); + std::string utf8_message, utf8_origin, utf8_target; + WideToUTF8(message, ::SysStringLen(message), &utf8_message); + WideToUTF8(origin, ::SysStringLen(origin), &utf8_origin); + WideToUTF8(target, ::SysStringLen(target), &utf8_target); + + if (!automation_client_->ForwardMessageFromExternalHost(utf8_message, + utf8_origin, + utf8_target)) { + Error(L"Failed to post message to chrome frame"); + return E_FAIL; + } + + return S_OK; + } + + // Returns the vector of event handlers for a given event (e.g. "load"). + // If the event type isn't recognized, the function fills in a descriptive + // error (IErrorInfo) and returns E_INVALIDARG. + HRESULT GetHandlersForEvent(BSTR event_type, EventHandlers** handlers) { + DCHECK(handlers != NULL); + + HRESULT hr = S_OK; + const wchar_t* event_type_end = event_type + ::SysStringLen(event_type); + if (LowerCaseEqualsASCII(event_type, event_type_end, "message")) { + *handlers = &onmessage_; + } else if (LowerCaseEqualsASCII(event_type, event_type_end, "load")) { + *handlers = &onload_; + } else if (LowerCaseEqualsASCII(event_type, event_type_end, "loaderror")) { + *handlers = &onloaderror_; + } else if (LowerCaseEqualsASCII(event_type, event_type_end, + "readystatechanged")) { + *handlers = &onreadystatechanged_; + } else if (LowerCaseEqualsASCII(event_type, event_type_end, + "privatemessage")) { + // This event handler is only available in privileged mode. + if (!is_privileged_) { + Error("Event type 'privatemessage' is privileged"); + return E_ACCESSDENIED; + } + *handlers = &onprivatemessage_; + } else { + Error(StringPrintf("Event type '%ls' not found", event_type).c_str()); + hr = E_INVALIDARG; + } + + return hr; + } + + // Gives the browser a chance to handle an accelerator that was + // sent to the out of proc chromium instance. + // Returns S_OK iff the accelerator was handled by the browser. + HRESULT AllowFrameToTranslateAccelerator(const MSG& msg) { + // Although IBrowserService2 is officially deprecated, it's still alive + // and well in IE7 and earlier. We have to use it here to correctly give + // the browser a chance to handle keyboard shortcuts. + // This happens automatically for activex components that have windows that + // belong to the current thread. In that circumstance IE owns the message + // loop and can walk the line of components allowing each participant the + // chance to handle the keystroke and eventually falls back to + // v_MayTranslateAccelerator. However in our case, the message loop is + // owned by the out-of-proc chromium instance so IE doesn't have a chance to + // fall back on its default behavior. Instead we give IE a chance to + // handle the shortcut here. + + HRESULT hr = S_FALSE; + ScopedComPtr<IBrowserService2> bs2; + if (S_OK == DoQueryService(SID_STopLevelBrowser, m_spInPlaceSite, + bs2.Receive())) { + hr = bs2->v_MayTranslateAccelerator(const_cast<MSG*>(&msg)); + } else { + // IE8 doesn't support IBrowserService2 unless you enable a special, + // undocumented flag with CoInternetSetFeatureEnabled and even then, + // the object you get back implements only a couple of methods of + // that interface... all the other entries in the vtable are NULL. + // In addition, the class that implements it is called + // ImpostorBrowserService2 :) + // IE8 does have a new interface though, presumably called + // ITabBrowserService or something that can be abbreviated to TBS. + // That interface has a method, TranslateAcceleratorTBS that does + // call the root MayTranslateAccelerator function, but alas the + // first argument to MayTranslateAccelerator is hard coded to FALSE + // which means that global accelerators are not handled and we're + // out of luck. + // A third thing that's notable with regards to IE8 is that + // none of the *MayTranslate* functions exist in a vtable inside + // ieframe.dll. I checked this by scanning for the address of + // those functions inside the dll and found none, which means that + // all calls to those functions are relative. + // So, for IE8, our approach is very simple. Just post the message + // to our parent window and IE will pick it up if it's an + // accelerator. We won't know for sure if the browser handled the + // keystroke or not. + ::PostMessage(::GetParent(m_hWnd), msg.message, msg.wParam, + msg.lParam); + } + + return hr; + } + + protected: + ScopedBstr url_; + ScopedComPtr<IOleDocumentSite> doc_site_; + + // For more information on the ready_state_ property see: + // http://msdn.microsoft.com/en-us/library/aa768179(VS.85).aspx# + READYSTATE ready_state_; + + // The following members contain IDispatch interfaces representing the + // onload/onerror/onmessage handlers on the page. + ScopedVariant onload_handler_; + ScopedVariant onerror_handler_; + ScopedVariant onmessage_handler_; + + EventHandlers onmessage_; + EventHandlers onloaderror_; + EventHandlers onload_; + EventHandlers onreadystatechanged_; + EventHandlers onprivatemessage_; + + // The UrlmonUrlRequest instance instantiated for downloading the base URL. + scoped_refptr<CComObject<UrlmonUrlRequest> > base_url_request_; +}; + +#endif // CHROME_FRAME_CHROME_FRAME_ACTIVEX_BASE_H_ diff --git a/chrome_frame/chrome_frame_automation.cc b/chrome_frame/chrome_frame_automation.cc new file mode 100644 index 0000000..9da5e43 --- /dev/null +++ b/chrome_frame/chrome_frame_automation.cc @@ -0,0 +1,975 @@ +// Copyright (c) 2009 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. + +#include "chrome_frame/chrome_frame_automation.h" + +#include "base/command_line.h" +#include "base/compiler_specific.h" +#include "base/file_util.h" +#include "base/file_version_info.h" +#include "base/logging.h" +#include "base/path_service.h" +#include "base/process_util.h" +#include "base/singleton.h" +#include "base/string_util.h" +#include "base/sys_info.h" +#include "chrome/app/client_util.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/test/automation/tab_proxy.h" +#include "chrome_frame/chrome_launcher.h" +#include "chrome_frame/utils.h" +#include "chrome_frame/sync_msg_reply_dispatcher.h" + +#ifdef NDEBUG +int64 kAutomationServerReasonableLaunchDelay = 1000; // in milliseconds +#else +int64 kAutomationServerReasonableLaunchDelay = 1000 * 10; +#endif + +int kDefaultSendUMADataInterval = 20000; // in milliseconds. + +static const wchar_t kUmaSendIntervalValue[] = L"UmaSendInterval"; + +class TabProxyNotificationMessageFilter + : public IPC::ChannelProxy::MessageFilter { + public: + explicit TabProxyNotificationMessageFilter(AutomationHandleTracker* tracker) + : tracker_(tracker) { + } + + virtual bool OnMessageReceived(const IPC::Message& message) { + if (message.is_reply()) + return false; + + int tab_handle = 0; + if (!ChromeFrameDelegateImpl::IsTabMessage(message, &tab_handle)) + return false; + + // Get AddRef-ed pointer to corresponding TabProxy object + TabProxy* tab = static_cast<TabProxy*>(tracker_->GetResource(tab_handle)); + if (tab) { + tab->OnMessageReceived(message); + tab->Release(); + } + return true; + } + + + private: + AutomationHandleTracker* tracker_; +}; + +class ChromeFrameAutomationProxyImpl::CFMsgDispatcher + : public SyncMessageReplyDispatcher { + public: + CFMsgDispatcher() : SyncMessageReplyDispatcher() {} + protected: + virtual bool HandleMessageType(const IPC::Message& msg, + const MessageSent& origin) { + switch (origin.type) { + case AutomationMsg_CreateExternalTab::ID: + case AutomationMsg_ConnectExternalTab::ID: + InvokeCallback<Tuple3<HWND, HWND, int> >(msg, origin); + break; + case AutomationMsg_NavigateInExternalTab::ID: + InvokeCallback<Tuple1<AutomationMsg_NavigationResponseValues> >(msg, + origin); + break; + default: + NOTREACHED(); + } + return true; + } +}; + +ChromeFrameAutomationProxyImpl::ChromeFrameAutomationProxyImpl( + int launch_timeout) + : AutomationProxy(launch_timeout) { + sync_ = new CFMsgDispatcher(); + // Order of filters is not important. + channel_->AddFilter(new TabProxyNotificationMessageFilter(tracker_.get())); + channel_->AddFilter(sync_.get()); +} + +ChromeFrameAutomationProxyImpl::~ChromeFrameAutomationProxyImpl() { +} + +void ChromeFrameAutomationProxyImpl::SendAsAsync(IPC::SyncMessage* msg, + void* callback, void* key) { + sync_->Push(msg, callback, key); + channel_->ChannelProxy::Send(msg); +} + +void ChromeFrameAutomationProxyImpl::CancelAsync(void* key) { + sync_->Cancel(key); +} + +scoped_refptr<TabProxy> ChromeFrameAutomationProxyImpl::CreateTabProxy( + int handle) { + DCHECK(tracker_->GetResource(handle) == NULL); + return new TabProxy(this, tracker_.get(), handle); +} + +struct LaunchTimeStats { +#ifndef NDEBUG + LaunchTimeStats() { + launch_time_begin_ = base::Time::Now(); + } + + void Dump() { + base::TimeDelta launch_time = base::Time::Now() - launch_time_begin_; + HISTOGRAM_TIMES("ChromeFrame.AutomationServerLaunchTime", launch_time); + const int64 launch_milliseconds = launch_time.InMilliseconds(); + if (launch_milliseconds > kAutomationServerReasonableLaunchDelay) { + LOG(WARNING) << "Automation server launch took longer than expected: " << + launch_milliseconds << " ms."; + } + } + + base::Time launch_time_begin_; +#else + void Dump() {} +#endif +}; + + +ProxyFactory::ProxyCacheEntry::ProxyCacheEntry(const std::wstring& profile) + : proxy(NULL), profile_name(profile), ref_count(1), + launch_result(AutomationLaunchResult(-1)) { + thread.reset(new base::Thread(WideToASCII(profile_name).c_str())); + thread->Start(); +} + +template <> struct RunnableMethodTraits<ProxyFactory> { + static void RetainCallee(ProxyFactory* obj) {} + static void ReleaseCallee(ProxyFactory* obj) {} +}; + +ProxyFactory::ProxyFactory() + : uma_send_interval_(0) { + uma_send_interval_ = GetConfigInt(kDefaultSendUMADataInterval, + kUmaSendIntervalValue); +} + +ProxyFactory::~ProxyFactory() { + DCHECK_EQ(proxies_.container().size(), 0); +} + +void* ProxyFactory::GetAutomationServer(int launch_timeout, + const std::wstring& profile_name, + const std::wstring& extra_argument, + bool perform_version_check, + LaunchDelegate* delegate) { + ProxyCacheEntry* entry = NULL; + // Find already existing launcher thread for given profile + AutoLock lock(lock_); + for (size_t i = 0; i < proxies_.container().size(); ++i) { + if (!lstrcmpiW(proxies_[i]->profile_name.c_str(), profile_name.c_str())) { + entry = proxies_[i]; + DCHECK(entry->thread.get() != NULL); + break; + } + } + + if (entry == NULL) { + entry = new ProxyCacheEntry(profile_name); + proxies_.container().push_back(entry); + } else { + entry->ref_count++; + } + + + // Note we always queue request to the launch thread, even if we already + // have established proxy object. A simple lock around entry->proxy = proxy + // would allow calling LaunchDelegate directly from here if + // entry->proxy != NULL. Drawback is that callback may be invoked either in + // main thread or in background thread, which may confuse the client. + entry->thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(this, + &ProxyFactory::CreateProxy, entry, + launch_timeout, extra_argument, + perform_version_check, delegate)); + + entry->thread->message_loop()->PostDelayedTask(FROM_HERE, + NewRunnableMethod(this, &ProxyFactory::SendUMAData, entry), + uma_send_interval_); + + return entry; +} + +void ProxyFactory::CreateProxy(ProxyFactory::ProxyCacheEntry* entry, + int launch_timeout, + const std::wstring& extra_chrome_arguments, + bool perform_version_check, + LaunchDelegate* delegate) { + DCHECK(entry->thread->thread_id() == PlatformThread::CurrentId()); + if (entry->proxy) { + delegate->LaunchComplete(entry->proxy, entry->launch_result); + return; + } + + // We *must* create automationproxy in a thread that has message loop, + // since SyncChannel::Context construction registers event to be watched + // through ObjectWatcher which subscribes for the current thread message loop + // destruction notification. + + // At same time we must destroy/stop the thread from another thread. + ChromeFrameAutomationProxyImpl* proxy = + new ChromeFrameAutomationProxyImpl(launch_timeout); + + // Launch browser + scoped_ptr<CommandLine> command_line( + chrome_launcher::CreateLaunchCommandLine()); + command_line->AppendSwitchWithValue(switches::kAutomationClientChannelID, + ASCIIToWide(proxy->channel_id())); + + // The metrics bug out because they attempt to use URLFetcher with a + // null URLRequestContext::default_request_context_. Turn them off for now. + // TODO(robertshield): Figure out why this is. It appears to have something + // to do with an improperly set up profile... + command_line->AppendSwitch(switches::kDisableMetrics); + + // Chrome Frame never wants Chrome to start up with a First Run UI. + command_line->AppendSwitch(switches::kNoFirstRun); + + // Place the profile directory in + // "<chrome_exe_path>\..\User Data\<profile-name>" + if (!entry->profile_name.empty()) { + std::wstring profile_path; + if (GetUserProfileBaseDirectory(&profile_path)) { + file_util::AppendToPath(&profile_path, entry->profile_name); + command_line->AppendSwitchWithValue(switches::kUserDataDir, + profile_path); + } else { + // Can't get the profile dir :-( We need one to work, so fail. + // We have no code for launch failure. + entry->launch_result = AutomationLaunchResult(-1); + } + } + + std::wstring command_line_string(command_line->command_line_string()); + // If there are any extra arguments, append them to the command line. + if (!extra_chrome_arguments.empty()) { + command_line_string += L' ' + extra_chrome_arguments; + } + + automation_server_launch_start_time_ = base::TimeTicks::Now(); + + if (!base::LaunchApp(command_line_string, false, false, NULL)) { + // We have no code for launch failure. + entry->launch_result = AutomationLaunchResult(-1); + } else { + // Launch timeout may happen if the new instance tries to communicate + // with an existing Chrome instance that is hung and displays msgbox + // asking to kill the previous one. This could be easily observed if the + // already running Chrome instance is running as high-integrity process + // (started with "Run as Administrator" or launched by another high + // integrity process) hence our medium-integrity process + // cannot SendMessage to it with request to activate itself. + + // TODO(stoyan) AutomationProxy eats Hello message, hence installing + // message filter is pointless, we can leverage ObjectWatcher and use + // system thread pool to notify us when proxy->AppLaunch event is signaled. + LaunchTimeStats launch_stats; + // Wait for the automation server launch result, then stash away the + // version string it reported. + entry->launch_result = proxy->WaitForAppLaunch(); + launch_stats.Dump(); + + base::TimeDelta delta = + base::TimeTicks::Now() - automation_server_launch_start_time_; + + if (entry->launch_result == AUTOMATION_SUCCESS) { + UMA_HISTOGRAM_TIMES("ChromeFrame.AutomationServerLaunchSuccessTime", + delta); + } else { + UMA_HISTOGRAM_TIMES("ChromeFrame.AutomationServerLaunchFailedTime", + delta); + } + + UMA_HISTOGRAM_CUSTOM_COUNTS("ChromeFrame.LaunchResult", + entry->launch_result, + AUTOMATION_SUCCESS, + AUTOMATION_CREATE_TAB_FAILED, + AUTOMATION_CREATE_TAB_FAILED + 1); + } + + // Finally set the proxy. + entry->proxy = proxy; + delegate->LaunchComplete(proxy, entry->launch_result); +} + +bool ProxyFactory::ReleaseAutomationServer(void* server_id) { + DLOG(INFO) << __FUNCTION__; + + if (!server_id) { + NOTREACHED(); + return false; + } + + ProxyCacheEntry* entry = reinterpret_cast<ProxyCacheEntry*>(server_id); + + lock_.Acquire(); + Vector::ContainerType::iterator it = std::find(proxies_.container().begin(), + proxies_.container().end(), + entry); + DCHECK(it != proxies_.container().end()); + DCHECK(entry->thread->thread_id() != PlatformThread::CurrentId()); + if (--entry->ref_count == 0) { + proxies_.container().erase(it); + } + + lock_.Release(); + + // Destroy it. + if (entry->ref_count == 0) { + entry->thread->message_loop()->PostTask(FROM_HERE, NewRunnableMethod(this, + &ProxyFactory::DestroyProxy, entry)); + // Wait until thread exits + entry->thread.reset(); + DCHECK(entry->proxy == NULL); + delete entry; + } + + return true; +} + +void ProxyFactory::DestroyProxy(ProxyCacheEntry* entry) { + DCHECK(entry->thread->thread_id() == PlatformThread::CurrentId()); + // Send pending UMA data if any. + SendUMAData(entry); + delete entry->proxy; + entry->proxy = NULL; +} + +Singleton<ProxyFactory> g_proxy_factory; + +void ProxyFactory::SendUMAData(ProxyCacheEntry* proxy_entry) { + if (!proxy_entry) { + NOTREACHED() << __FUNCTION__ << " Invalid proxy entry"; + return; + } + + DCHECK(proxy_entry->thread->thread_id() == PlatformThread::CurrentId()); + + if (proxy_entry->proxy) { + ChromeFrameHistogramSnapshots::HistogramPickledList histograms = + chrome_frame_histograms_.GatherAllHistograms(); + + if (!histograms.empty()) { + proxy_entry->proxy->Send( + new AutomationMsg_RecordHistograms(0, histograms)); + } + } else { + DLOG(INFO) << __FUNCTION__ << " No proxy available to service the request"; + return; + } + + MessageLoop::current()->PostDelayedTask(FROM_HERE, NewRunnableMethod( + this, &ProxyFactory::SendUMAData, proxy_entry), uma_send_interval_); +} + +template <> struct RunnableMethodTraits<ChromeFrameAutomationClient> { + static void RetainCallee(ChromeFrameAutomationClient* obj) {} + static void ReleaseCallee(ChromeFrameAutomationClient* obj) {} +}; + +ChromeFrameAutomationClient::ChromeFrameAutomationClient() + : chrome_frame_delegate_(NULL), + chrome_window_(NULL), + tab_window_(NULL), + parent_window_(NULL), + automation_server_(NULL), + automation_server_id_(NULL), + ui_thread_id_(NULL), + incognito_(false), + init_state_(UNINITIALIZED), + use_chrome_network_(false), + proxy_factory_(g_proxy_factory.get()), + handle_top_level_requests_(false), + tab_handle_(-1), + external_tab_cookie_(NULL) { +} + +ChromeFrameAutomationClient::~ChromeFrameAutomationClient() { + // Uninitialize must be called prior to the destructor + DCHECK(automation_server_ == NULL); +} + +bool ChromeFrameAutomationClient::Initialize( + ChromeFrameDelegate* chrome_frame_delegate, + int automation_server_launch_timeout, + bool perform_version_check, + const std::wstring& profile_name, + const std::wstring& extra_chrome_arguments, + bool incognito) { + DCHECK(!IsWindow()); + chrome_frame_delegate_ = chrome_frame_delegate; + incognito_ = incognito; + ui_thread_id_ = PlatformThread::CurrentId(); + +#ifndef NDEBUG + // In debug mode give more time to work with a debugger. + if (automation_server_launch_timeout != INFINITE) + automation_server_launch_timeout *= 2; +#endif // NDEBUG + + // Create a window on the UI thread for marshaling messages back and forth + // from the IPC thread. This window cannot be a message only window as the + // external chrome tab window is created as a child of this window. This + // window is eventually reparented to the ActiveX/NPAPI plugin window. + if (!Create(GetDesktopWindow(), NULL, NULL, + WS_CHILDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, + WS_EX_TOOLWINDOW)) { + NOTREACHED(); + return false; + } + + // Mark our state as initializing. We'll reach initialized once + // InitializeComplete is called successfully. + init_state_ = INITIALIZING; + + automation_server_id_ = proxy_factory_->GetAutomationServer( + automation_server_launch_timeout, + profile_name, extra_chrome_arguments, perform_version_check, + static_cast<ProxyFactory::LaunchDelegate*>(this)); + + return true; +} + +void ChromeFrameAutomationClient::Uninitialize() { + DLOG(INFO) << __FUNCTION__; + + init_state_ = UNINITIALIZING; + + // Called from client's FinalRelease() / destructor + // ChromeFrameAutomationClient may wait for the initialization (launch) + // to complete while Uninitialize is called. + // We either have to: + // 1) Make ReleaseAutomationServer blocking call (wait until thread exits) + // 2) Behave like a COM object i.e. increase module lock count. + // Otherwise the DLL may get unloaded while we have running threads. + // Unfortunately in NPAPI case we cannot increase module lock count, hence + // we stick with blocking/waiting + if (tab_.get()) { + tab_->RemoveObserver(this); + tab_ = NULL; // scoped_refptr::Release + } + + // Clean up any outstanding requests + CleanupRequests(); + + // Wait for the background thread to exit. + ReleaseAutomationServer(); + + // We must destroy the window, since if there are pending tasks + // window procedure may be invoked after DLL is unloaded. + // Unfortunately pending tasks are leaked. + if (m_hWnd) + DestroyWindow(); + + chrome_frame_delegate_ = NULL; + init_state_ = UNINITIALIZED; +} + +bool ChromeFrameAutomationClient::InitiateNavigation(const std::string& url) { + if (url.empty()) + return false; + + url_ = GURL(url); + + // Catch invalid URLs early. + if (!url_.is_valid()) { + DLOG(ERROR) << "Invalid URL passed to InitiateNavigation: " << url; + return false; + } + + if (is_initialized()) { + BeginNavigate(GURL(url)); + } + + return true; +} + +bool ChromeFrameAutomationClient::NavigateToIndex(int index) { + // Could be NULL if we failed to launch Chrome in LaunchAutomationServer() + if (!automation_server_ || !tab_.get() || !tab_->is_valid()) { + return false; + } + + DCHECK(::IsWindow(chrome_window_)); + + IPC::SyncMessage* msg = new AutomationMsg_NavigateExternalTabAtIndex( + 0, tab_->handle(), index, NULL); + automation_server_->SendAsAsync(msg, NewCallback(this, + &ChromeFrameAutomationClient::BeginNavigateCompleted), this); + return true; +} + +bool ChromeFrameAutomationClient::ForwardMessageFromExternalHost( + const std::string& message, const std::string& origin, + const std::string& target) { + // Could be NULL if we failed to launch Chrome in LaunchAutomationServer() + if (!is_initialized()) + return false; + + tab_->HandleMessageFromExternalHost(message, origin, target); + return true; +} + +bool ChromeFrameAutomationClient::SetProxySettings( + const std::string& json_encoded_proxy_settings) { + if (!is_initialized()) + return false; + automation_server_->SendProxyConfig(json_encoded_proxy_settings); + return true; +} + +void ChromeFrameAutomationClient::BeginNavigate(const GURL& url) { + // Could be NULL if we failed to launch Chrome in LaunchAutomationServer() + if (!automation_server_ || !tab_.get()) { + DLOG(WARNING) << "BeginNavigate - can't navigate."; + ReportNavigationError(AUTOMATION_MSG_NAVIGATION_ERROR, url_.spec()); + return; + } + + DCHECK(::IsWindow(chrome_window_)); + + if (!tab_->is_valid()) { + DLOG(WARNING) << "BeginNavigate - tab isn't valid."; + return; + } + + IPC::SyncMessage* msg = + new AutomationMsg_NavigateInExternalTab(0, tab_->handle(), url, NULL); + automation_server_->SendAsAsync(msg, NewCallback(this, + &ChromeFrameAutomationClient::BeginNavigateCompleted), this); + + RECT client_rect = {0}; + chrome_frame_delegate_->GetBounds(&client_rect); + Resize(client_rect.right - client_rect.left, + client_rect.bottom - client_rect.top, + SWP_NOACTIVATE | SWP_NOZORDER); +} + +void ChromeFrameAutomationClient::BeginNavigateCompleted( + AutomationMsg_NavigationResponseValues result) { + if (result == AUTOMATION_MSG_NAVIGATION_ERROR) + ReportNavigationError(AUTOMATION_MSG_NAVIGATION_ERROR, url_.spec()); +} + +void ChromeFrameAutomationClient::FindInPage(const std::wstring& search_string, + FindInPageDirection forward, + FindInPageCase match_case, + bool find_next) { + DCHECK(tab_.get()); + + // What follows is quite similar to TabProxy::FindInPage() but uses + // the SyncMessageReplyDispatcher to avoid concerns about blocking + // synchronous messages. + AutomationMsg_Find_Params params; + params.unused = 0; + params.search_string = WideToUTF16Hack(search_string); + params.find_next = find_next; + params.match_case = (match_case == CASE_SENSITIVE); + params.forward = (forward == FWD); + + IPC::SyncMessage* msg = + new AutomationMsg_Find(0, tab_->handle(), params, NULL, NULL); + automation_server_->SendAsAsync(msg, NULL, this); +} + +void ChromeFrameAutomationClient::CreateExternalTab() { + AutomationLaunchResult launch_result = AUTOMATION_SUCCESS; + DCHECK(IsWindow()); + DCHECK(automation_server_ != NULL); + + const IPC::ExternalTabSettings settings = { + m_hWnd, + gfx::Rect(), + WS_CHILD, + incognito_, + !use_chrome_network_, + handle_top_level_requests_, + GURL(url_) + }; + + UMA_HISTOGRAM_CUSTOM_COUNTS( + "ChromeFrame.HostNetworking", !use_chrome_network_, 0, 1, 2); + + UMA_HISTOGRAM_CUSTOM_COUNTS( + "ChromeFrame.HandleTopLevelRequests", handle_top_level_requests_, 0, 1, + 2); + + IPC::SyncMessage* message = + new AutomationMsg_CreateExternalTab(0, settings, NULL, NULL, NULL); + automation_server_->SendAsAsync(message, NewCallback(this, + &ChromeFrameAutomationClient::CreateExternalTabComplete), this); +} + +void ChromeFrameAutomationClient::CreateExternalTabComplete(HWND chrome_window, + HWND tab_window, int tab_handle) { + if (!automation_server_) { + // If we receive this notification while shutting down, do nothing. + DLOG(ERROR) << "CreateExternalTabComplete called when automation server " + << "was null!"; + return; + } + + AutomationLaunchResult launch_result = AUTOMATION_SUCCESS; + if (tab_handle == 0 || !::IsWindow(chrome_window) || + !::IsWindow(chrome_window)) { + launch_result = AUTOMATION_CREATE_TAB_FAILED; + } else { + chrome_window_ = chrome_window; + tab_window_ = tab_window; + tab_ = automation_server_->CreateTabProxy(tab_handle); + tab_->AddObserver(this); + tab_handle_ = tab_handle; + } + + PostTask(FROM_HERE, NewRunnableMethod(this, + &ChromeFrameAutomationClient::InitializeComplete, launch_result)); +} + +void ChromeFrameAutomationClient::SetEnableExtensionAutomation( + bool enable_automation) { + if (!is_initialized()) + return; + + automation_server_->SetEnableExtensionAutomation(enable_automation); +} + +// Invoked in launch background thread. +void ChromeFrameAutomationClient::LaunchComplete( + ChromeFrameAutomationProxy* proxy, + AutomationLaunchResult result) { + // If we're shutting down we don't keep a pointer to the automation server. + if (init_state_ != UNINITIALIZING) { + DCHECK(init_state_ == INITIALIZING); + automation_server_ = proxy; + } else { + DLOG(INFO) << "Not storing automation server pointer due to shutting down"; + } + + if (result == AUTOMATION_SUCCESS) { + // NOTE: A potential problem here is that Uninitialize() may just have + // been called so we need to be careful and check the automation_server_ + // pointer. + if (automation_server_ != NULL) { + // If we have a valid tab_handle here it means that we are attaching to + // an existing ExternalTabContainer instance, in which case we don't + // want to create an external tab instance in Chrome. + if (external_tab_cookie_ == NULL) { + // Continue with Initialization - Create external tab + CreateExternalTab(); + } else { + // Send a notification to Chrome that we are ready to connect to the + // ExternalTab. + IPC::SyncMessage* message = + new AutomationMsg_ConnectExternalTab(0, external_tab_cookie_, NULL, + NULL, NULL); + automation_server_->SendAsAsync(message, NewCallback(this, + &ChromeFrameAutomationClient::CreateExternalTabComplete), this); + } + } + } else { + // Launch failed. Note, we cannot delete proxy here. + PostTask(FROM_HERE, NewRunnableMethod(this, + &ChromeFrameAutomationClient::InitializeComplete, result)); + } +} + +void ChromeFrameAutomationClient::InitializeComplete( + AutomationLaunchResult result) { + DCHECK(PlatformThread::CurrentId() == ui_thread_id_); + std::string version = automation_server_->server_version(); + + if (result != AUTOMATION_SUCCESS) { + DLOG(WARNING) << "InitializeComplete: failure " << result; + ReleaseAutomationServer(); + } else { + init_state_ = INITIALIZED; + + // If the host already have a window, ask Chrome to re-parent. + if (parent_window_) + SetParentWindow(parent_window_); + } + + if (chrome_frame_delegate_) { + if (result == AUTOMATION_SUCCESS) { + chrome_frame_delegate_->OnAutomationServerReady(); + } else { + chrome_frame_delegate_->OnAutomationServerLaunchFailed(result, version); + } + } +} + +// This is invoked in channel's background thread. +// Cannot call any method of the activex/npapi here since they are STA +// kind of beings. +// By default we marshal the IPC message to the main/GUI thread and from there +// we safely invoke chrome_frame_delegate_->OnMessageReceived(msg). +void ChromeFrameAutomationClient::OnMessageReceived(TabProxy* tab, + const IPC::Message& msg) { + DCHECK(tab == tab_.get()); + + // Early check to avoid needless marshaling + if (chrome_frame_delegate_ == NULL) + return; + + CallDelegate(FROM_HERE, NewRunnableMethod(chrome_frame_delegate_, + &ChromeFrameDelegate::OnMessageReceived, msg)); +} + +void ChromeFrameAutomationClient::ReportNavigationError( + AutomationMsg_NavigationResponseValues error_code, + const std::string& url) { + CallDelegate(FROM_HERE, NewRunnableMethod(chrome_frame_delegate_, + &ChromeFrameDelegate::OnLoadFailed, + error_code, + url)); +} + +void ChromeFrameAutomationClient::Resize(int width, int height, + int flags) { + if (tab_.get() && ::IsWindow(chrome_window())) { + SetWindowPos(HWND_TOP, 0, 0, width, height, flags); + tab_->Reposition(chrome_window(), HWND_TOP, 0, 0, width, height, + flags, m_hWnd); + } +} + +void ChromeFrameAutomationClient::SetParentWindow(HWND parent_window) { + parent_window_ = parent_window; + // If we're done with the initialization step, go ahead + if (is_initialized()) { + if (parent_window == NULL) { + // Hide and reparent the automation window. This window will get + // reparented to the new ActiveX/Active document window when it gets + // created. + ShowWindow(SW_HIDE); + SetParent(GetDesktopWindow()); + } else { + if (!::IsWindow(chrome_window())) { + DLOG(WARNING) << "Invalid Chrome Window handle in SetParentWindow"; + return; + } + + if (!SetParent(parent_window)) { + NOTREACHED(); + DLOG(WARNING) << "Failed to set parent window for automation window. " + << "Error = " + << GetLastError(); + return; + } + + RECT parent_client_rect = {0}; + ::GetClientRect(parent_window, &parent_client_rect); + int width = parent_client_rect.right - parent_client_rect.left; + int height = parent_client_rect.bottom - parent_client_rect.top; + + Resize(width, height, SWP_SHOWWINDOW | SWP_NOZORDER); + } + } +} + +void ChromeFrameAutomationClient::ReleaseAutomationServer() { + DLOG(INFO) << __FUNCTION__; + if (automation_server_id_) { + // Cache the server id and clear the automation_server_id_ before + // calling ReleaseAutomationServer. The reason we do this is that + // we must cancel pending messages before we release the automation server. + // Furthermore, while ReleaseAutomationServer is running, we could get + // a callback to LaunchComplete which is where we normally get our pointer + // to the automation server and there we check the server id for NULLness + // and do nothing if it is NULL. + void* server_id = automation_server_id_; + automation_server_id_ = NULL; + + if (automation_server_) { + // Make sure to clean up any pending sync messages before we go away. + automation_server_->CancelAsync(this); + automation_server_ = NULL; + } + + proxy_factory_->ReleaseAutomationServer(server_id); + + // automation_server_ must not have been set to non NULL. + // (if this regresses, start by looking at LaunchComplete()). + DCHECK(automation_server_ == NULL); + } else { + DCHECK(automation_server_ == NULL); + } +} + +void ChromeFrameAutomationClient::SendContextMenuCommandToChromeFrame( + int selected_command) { + DCHECK(tab_ != NULL); + tab_->SendContextMenuCommand(selected_command); +} + +std::wstring ChromeFrameAutomationClient::GetVersion() const { + static FileVersionInfo* version_info = + FileVersionInfo::CreateFileVersionInfoForCurrentModule(); + + std::wstring version; + if (version_info) + version = version_info->product_version(); + + return version; +} + +void ChromeFrameAutomationClient::CallDelegate( + const tracked_objects::Location& from_here, Task* delegate_task ) { + delegate_task->SetBirthPlace(from_here); + PostTask(FROM_HERE, NewRunnableMethod(this, + &ChromeFrameAutomationClient::CallDelegateImpl, + delegate_task)); +} + +void ChromeFrameAutomationClient::CallDelegateImpl(Task* delegate_task) { + if (chrome_frame_delegate_) { + // task's object should be == chrome_frame_delegate_ + delegate_task->Run(); + } + + delete delegate_task; +} + +void ChromeFrameAutomationClient::Print(HDC print_dc, + const RECT& print_bounds) { + if (!tab_window_) { + NOTREACHED(); + return; + } + + HDC window_dc = ::GetDC(tab_window_); + + BitBlt(print_dc, print_bounds.left, print_bounds.top, + print_bounds.right - print_bounds.left, + print_bounds.bottom - print_bounds.top, + window_dc, print_bounds.left, print_bounds.top, + SRCCOPY); + + ::ReleaseDC(tab_window_, window_dc); +} + +void ChromeFrameAutomationClient::PrintTab() { + tab_->PrintAsync(); +} + +// IPC:Message::Sender implementation +bool ChromeFrameAutomationClient::Send(IPC::Message* msg) { + return automation_server_->Send(msg); +} + +bool ChromeFrameAutomationClient::AddRequest(PluginUrlRequest* request) { + if (!request) { + NOTREACHED(); + return false; + } + + DCHECK(request_map_.end() == request_map_.find(request->id())); + request_map_[request->id()] = request; + return true; +} + +bool ChromeFrameAutomationClient::ReadRequest( + int request_id, int bytes_to_read) { + bool result = false; + PluginUrlRequest* request = LookupRequest(request_id); + if (request) + result = request->Read(bytes_to_read); + + return result; +} + +void ChromeFrameAutomationClient::RemoveRequest(PluginUrlRequest* request) { + DCHECK(request_map_.end() != request_map_.find(request->id())); + request_map_.erase(request->id()); +} + +void ChromeFrameAutomationClient::RemoveRequest( + int request_id, int reason, bool abort) { + PluginUrlRequest* request = LookupRequest(request_id); + if (request) { + if (abort) { + request->Stop(); + DCHECK(request_map_.end() == request_map_.find(request_id)); + } else { + request_map_.erase(request_id); + } + } +} + +PluginUrlRequest* ChromeFrameAutomationClient::LookupRequest( + int request_id) const { + PluginUrlRequest* request = NULL; + RequestMap::const_iterator it = request_map_.find(request_id); + if (request_map_.end() != it) + request = (*it).second; + return request; +} + +bool ChromeFrameAutomationClient::IsValidRequest( + PluginUrlRequest* request) const { + bool is_valid = false; + // if request is invalid then request->id() won't work + // hence perform reverse map lookup for validity of the + // request pointer. + if (request) { + for (RequestMap::const_iterator it = request_map_.begin(); + it != request_map_.end(); it++) { + if (request == (*it).second) { + is_valid = true; + break; + } + } + } + + return is_valid; +} + +void ChromeFrameAutomationClient::CleanupRequests() { + while (request_map_.size()) { + PluginUrlRequest* request = request_map_.begin()->second; + if (request) { + int request_id = request->id(); + request->Stop(); + DCHECK(request_map_.end() == request_map_.find(request_id)); + } + } + + DCHECK(request_map_.empty()); + request_map_.clear(); +} + +bool ChromeFrameAutomationClient::Reinitialize( + ChromeFrameDelegate* delegate) { + if (!tab_.get() || !::IsWindow(chrome_window_)) { + NOTREACHED(); + DLOG(WARNING) << "ChromeFrameAutomationClient instance reused " + << "with invalid tab"; + return false; + } + + if (!delegate) { + NOTREACHED(); + return false; + } + + chrome_frame_delegate_ = delegate; + SetParentWindow(NULL); + return true; +} + +void ChromeFrameAutomationClient::AttachExternalTab( + intptr_t external_tab_cookie) { + DCHECK(tab_.get() == NULL); + DCHECK(tab_handle_ == -1); + + external_tab_cookie_ = external_tab_cookie; +} diff --git a/chrome_frame/chrome_frame_automation.h b/chrome_frame/chrome_frame_automation.h new file mode 100644 index 0000000..796facb --- /dev/null +++ b/chrome_frame/chrome_frame_automation.h @@ -0,0 +1,356 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_FRAME_CHROME_FRAME_AUTOMATION_H_ +#define CHROME_FRAME_CHROME_FRAME_AUTOMATION_H_ + +#include <atlbase.h> +#include <atlwin.h> +#include <string> +#include <map> + +#include "base/lock.h" +#include "base/ref_counted.h" +#include "base/scoped_handle.h" +#include "base/stack_container.h" +#include "base/task.h" +#include "base/timer.h" +#include "base/thread.h" +#include "chrome/test/automation/automation_proxy.h" +#include "chrome/test/automation/tab_proxy.h" +#include "chrome_frame/chrome_frame_delegate.h" +#include "chrome_frame/chrome_frame_histograms.h" +#include "chrome_frame/plugin_url_request.h" + +const unsigned long kCommandExecutionTimeout = 4000; // NOLINT, 4 seconds + +class ProxyFactory; + +struct DECLSPEC_NOVTABLE ChromeFrameAutomationProxy { + virtual bool Send(IPC::Message* msg) = 0; + + virtual void SendAsAsync(IPC::SyncMessage* msg, void* callback, + void* key) = 0; + virtual void CancelAsync(void* key) = 0; + virtual scoped_refptr<TabProxy> CreateTabProxy(int handle) = 0; + virtual std::string server_version() = 0; + + virtual void SendProxyConfig(const std::string&) = 0; + virtual void SetEnableExtensionAutomation(bool enable) = 0; + protected: + ~ChromeFrameAutomationProxy() {} +}; + +// We extend the AutomationProxy class to handle our custom +// IPC messages +class ChromeFrameAutomationProxyImpl : public ChromeFrameAutomationProxy, + // We have to derive from automationproxy since we want access to some members + // (tracker_ & channel_) - simple aggregation wont work; + // .. and non-public inheritance is verboten. + public AutomationProxy { + public: + virtual void SendAsAsync(IPC::SyncMessage* msg, void* callback, void* key); + + virtual void CancelAsync(void* key); + + virtual scoped_refptr<TabProxy> CreateTabProxy(int handle); + virtual std::string server_version() { + return AutomationProxy::server_version(); + } + + + virtual bool Send(IPC::Message* msg) { + return AutomationProxy::Send(msg); + } + + virtual void SendProxyConfig(const std::string& p) { + AutomationProxy::SendProxyConfig(p); + } + + virtual void SetEnableExtensionAutomation(bool e) { + AutomationProxy::SetEnableExtensionAutomation(e); + } + + protected: + explicit ChromeFrameAutomationProxyImpl(int launch_timeout); + ~ChromeFrameAutomationProxyImpl(); + class CFMsgDispatcher; + scoped_refptr<CFMsgDispatcher> sync_; + friend class ProxyFactory; +}; + +// We must create and destroy automation proxy in a thread with a message loop. +// Hence thread cannot be a member of the proxy. +class ProxyFactory { + public: + // Callback when chrome process launch is complete and automation handshake + // (Hello message) is established. + struct DECLSPEC_NOVTABLE LaunchDelegate { + virtual void LaunchComplete(ChromeFrameAutomationProxy* proxy, + AutomationLaunchResult result) = 0; + }; + + ProxyFactory(); + ~ProxyFactory(); + // FIXME: we should pass the result as output parameter, not as return value + // since, LaunchDelegate can be invoked before this function returns. + virtual void* GetAutomationServer(int launch_timeout, + const std::wstring& profile_name, + // Extra command line argument when launching Chrome + const std::wstring& extra_argument, + bool perform_version_check, + LaunchDelegate* delegate); + virtual bool ReleaseAutomationServer(void* server_id); + + private: + struct ProxyCacheEntry { + std::wstring profile_name; + int ref_count; + scoped_ptr<base::Thread> thread; + ChromeFrameAutomationProxyImpl* proxy; + AutomationLaunchResult launch_result; + explicit ProxyCacheEntry(const std::wstring& profile); + }; + + void CreateProxy(ProxyCacheEntry* entry, + int launch_timeout, + const std::wstring& extra_chrome_arguments, + bool perform_version_check, + LaunchDelegate* delegate); + void DestroyProxy(ProxyCacheEntry* entry); + + void SendUMAData(ProxyCacheEntry* proxy_entry); + + typedef StackVector<ProxyCacheEntry*, 4> Vector; + Vector proxies_; + // Lock if we are going to call GetAutomationServer from more than one thread. + Lock lock_; + + // Used for UMA histogram logging to measure the time for the chrome + // automation server to start; + base::TimeTicks automation_server_launch_start_time_; + + // Gathers histograms to be sent to Chrome. + ChromeFrameHistogramSnapshots chrome_frame_histograms_; + + // Interval for sending UMA data + int uma_send_interval_; +}; + +// T is expected to be something CWindowImpl derived, or at least to have +// PostMessage(UINT, WPARAM) method. Do not forget to CHAIN_MSG_MAP +template <class T> class TaskMarshallerThroughWindowsMessages { + public: + void PostTask(const tracked_objects::Location& from_here, Task* task) { + task->SetBirthPlace(from_here); + T* this_ptr = static_cast<T*>(this); + if (this_ptr->IsWindow()) { + this_ptr->PostMessage(MSG_EXECUTE_TASK, reinterpret_cast<WPARAM>(task)); + } else { + DLOG(INFO) << "Dropping MSG_EXECUTE_TASK message for destroyed window."; + } + } + + BEGIN_MSG_MAP(PostMessageMarshaller) + MESSAGE_HANDLER(MSG_EXECUTE_TASK, ExecuteTask) + END_MSG_MAP() + + private: + enum { MSG_EXECUTE_TASK = WM_APP + 6 }; + inline LRESULT ExecuteTask(UINT, WPARAM wparam, LPARAM, + BOOL& handled) { // NOLINT + Task* task = reinterpret_cast<Task*>(wparam); + task->Run(); + delete task; + return 0; + } +}; + +// Handles all automation requests initiated from the chrome frame objects. +// These include the chrome tab/chrome frame activex/chrome frame npapi +// plugin objects. +class ChromeFrameAutomationClient + : public CWindowImpl<ChromeFrameAutomationClient>, + public TaskMarshallerThroughWindowsMessages<ChromeFrameAutomationClient>, + public PluginRequestHandler, + public TabProxy::TabProxyDelegate, + public ProxyFactory::LaunchDelegate { + public: + ChromeFrameAutomationClient(); + ~ChromeFrameAutomationClient(); + + // Called from UI thread. + virtual bool Initialize(ChromeFrameDelegate* chrome_frame_delegate, + int automation_server_launch_timeout, + bool perform_version_check, + const std::wstring& profile_name, + const std::wstring& extra_chrome_arguments, + bool incognito); + void Uninitialize(); + + virtual bool InitiateNavigation(const std::string& url); + virtual bool NavigateToIndex(int index); + bool ForwardMessageFromExternalHost(const std::string& message, + const std::string& origin, + const std::string& target); + bool SetProxySettings(const std::string& json_encoded_proxy_settings); + + virtual void SetEnableExtensionAutomation(bool enable_automation); + + void FindInPage(const std::wstring& search_string, + FindInPageDirection forward, + FindInPageCase match_case, + bool find_next); + + TabProxy* tab() const { return tab_.get(); } + + BEGIN_MSG_MAP(ChromeFrameAutomationClient) + CHAIN_MSG_MAP( + TaskMarshallerThroughWindowsMessages<ChromeFrameAutomationClient>) + END_MSG_MAP() + + void set_delegate(ChromeFrameDelegate* d) { + chrome_frame_delegate_ = d; + } + + // Resizes the hosted chrome window. This is brokered to the chrome + // automation instance as the host browser could be running under low IL, + // which would cause the SetWindowPos call to fail. + void Resize(int width, int height, int flags); + + // Sets the passed in window as the parent of the external tab. + void SetParentWindow(HWND parent_window); + + void SendContextMenuCommandToChromeFrame(int selected_command); + + HWND tab_window() const { + return tab_window_; + } + + void ReleaseAutomationServer(); + + // Returns the version number of plugin dll. + std::wstring GetVersion() const; + + // BitBlts the contents of the chrome window to the print dc. + void Print(HDC print_dc, const RECT& print_bounds); + + // Called in full tab mode and indicates a request to chrome to print + // the whole tab. + void PrintTab(); + + // PluginRequestHandler + bool AddRequest(PluginUrlRequest* request); + void RemoveRequest(PluginUrlRequest* request); + virtual bool Send(IPC::Message* msg); + + // URL request related + bool ReadRequest(int request_id, int bytes_to_read); + void RemoveRequest(int request_id, int reason, bool abort); + PluginUrlRequest* LookupRequest(int request_id) const; + bool IsValidRequest(PluginUrlRequest* request) const; + void CleanupRequests(); + + void set_use_chrome_network(bool use_chrome_network) { + use_chrome_network_ = use_chrome_network; + } + bool use_chrome_network() const { + return use_chrome_network_; + } + +#ifdef UNIT_TEST + void set_proxy_factory(ProxyFactory* factory) { + proxy_factory_ = factory; + } +#endif + + void set_handle_top_level_requests(bool handle_top_level_requests) { + handle_top_level_requests_ = handle_top_level_requests; + } + + // Called if the same instance of the ChromeFrameAutomationClient object + // is reused. + bool Reinitialize(ChromeFrameDelegate* chrome_frame_delegate); + + // Attaches an existing external tab to this automation client instance. + void AttachExternalTab(intptr_t external_tab_cookie); + + protected: + // ChromeFrameAutomationProxy::LaunchDelegate implementation. + virtual void LaunchComplete(ChromeFrameAutomationProxy* proxy, + AutomationLaunchResult result); + // TabProxyDelegate implementation + virtual void OnMessageReceived(TabProxy* tab, const IPC::Message& msg); + + void CreateExternalTab(); + void CreateExternalTabComplete(HWND chrome_window, HWND tab_window, + int tab_handle); + // Called in UI thread. Here we fire event to the client notifying for + // the result of Initialize() method call. + void InitializeComplete(AutomationLaunchResult result); + + private: + typedef std::map<int, scoped_refptr<PluginUrlRequest> > RequestMap; + + // Usage: From bkgnd thread invoke: + // CallDelegate(FROM_HERE, NewRunnableMethod(chrome_frame_delegate_, + // ChromeFrameDelegate::Something, + // param1, + // param2)); + void CallDelegate(const tracked_objects::Location& from_here, + Task* delegate_task); + // The workhorse method called in main/GUI thread which is going to + // execute ChromeFrameDelegate method encapsulated in delegate_task. + void CallDelegateImpl(Task* delegate_task); + + HWND chrome_window() const { return chrome_window_; } + void BeginNavigate(const GURL& url); + void BeginNavigateCompleted(AutomationMsg_NavigationResponseValues result); + + // Helpers + void ReportNavigationError(AutomationMsg_NavigationResponseValues error_code, + const std::string& url); + + bool is_initialized() const { + return init_state_ == INITIALIZED; + } + + bool incognito_; + HWND parent_window_; + PlatformThreadId ui_thread_id_; + + void* automation_server_id_; + ChromeFrameAutomationProxy* automation_server_; + HWND chrome_window_; + scoped_refptr<TabProxy> tab_; + ChromeFrameDelegate* chrome_frame_delegate_; + GURL url_; + + // Handle to the underlying chrome window. This is a child of the external + // tab window. + HWND tab_window_; + + // Keeps track of the version of Chrome we're talking to. + std::string automation_server_version_; + + // Map of outstanding requests + RequestMap request_map_; + + typedef enum InitializationState { + UNINITIALIZED = 0, + INITIALIZING, + INITIALIZED, + UNINITIALIZING, + }; + + InitializationState init_state_; + bool use_chrome_network_; + bool handle_top_level_requests_; + ProxyFactory* proxy_factory_; + int tab_handle_; + // Only used if we attach to an existing tab. + intptr_t external_tab_cookie_; +}; + +#endif // CHROME_FRAME_CHROME_FRAME_AUTOMATION_H_ diff --git a/chrome_frame/chrome_frame_delegate.cc b/chrome_frame/chrome_frame_delegate.cc new file mode 100644 index 0000000..9a1069b --- /dev/null +++ b/chrome_frame/chrome_frame_delegate.cc @@ -0,0 +1,67 @@ +// Copyright (c) 2009 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. + +#include "chrome_frame/chrome_frame_delegate.h" + +bool ChromeFrameDelegateImpl::IsTabMessage(const IPC::Message& message, + int* tab_handle) { + bool is_tab_message = true; + IPC_BEGIN_MESSAGE_MAP(ChromeFrameDelegateImpl, message) + IPC_MESSAGE_HANDLER_GENERIC(AutomationMsg_NavigationStateChanged, ) + IPC_MESSAGE_HANDLER_GENERIC(AutomationMsg_UpdateTargetUrl, ) + IPC_MESSAGE_HANDLER_GENERIC(AutomationMsg_HandleAccelerator, ) + IPC_MESSAGE_HANDLER_GENERIC(AutomationMsg_TabbedOut, ) + IPC_MESSAGE_HANDLER_GENERIC(AutomationMsg_OpenURL, ) + IPC_MESSAGE_HANDLER_GENERIC(AutomationMsg_NavigationFailed, ) + IPC_MESSAGE_HANDLER_GENERIC(AutomationMsg_DidNavigate, ) + IPC_MESSAGE_HANDLER_GENERIC(AutomationMsg_TabLoaded, ) + IPC_MESSAGE_HANDLER_GENERIC(AutomationMsg_ForwardMessageToExternalHost, ) + IPC_MESSAGE_HANDLER_GENERIC( + AutomationMsg_ForwardContextMenuToExternalHost, ) + IPC_MESSAGE_HANDLER_GENERIC(AutomationMsg_RequestStart, ) + IPC_MESSAGE_HANDLER_GENERIC(AutomationMsg_RequestRead, ) + IPC_MESSAGE_HANDLER_GENERIC(AutomationMsg_RequestEnd, ) + IPC_MESSAGE_HANDLER_GENERIC(AutomationMsg_SetCookieAsync, ) + IPC_MESSAGE_HANDLER_GENERIC(AutomationMsg_AttachExternalTab, ) + IPC_MESSAGE_UNHANDLED(is_tab_message = false); + IPC_END_MESSAGE_MAP() + + if (is_tab_message) { + // Read tab handle from the message. + void* iter = NULL; + is_tab_message = message.ReadInt(&iter, tab_handle); + } + + return is_tab_message; +} + +void ChromeFrameDelegateImpl::OnMessageReceived(const IPC::Message& msg) { + if (!IsValid()) { + DLOG(WARNING) << __FUNCTION__ + << " Msgs received for a NULL automation client instance"; + return; + } + + IPC_BEGIN_MESSAGE_MAP(ChromeFrameDelegateImpl, msg) + IPC_MESSAGE_HANDLER(AutomationMsg_NavigationStateChanged, + OnNavigationStateChanged) + IPC_MESSAGE_HANDLER(AutomationMsg_UpdateTargetUrl, OnUpdateTargetUrl) + IPC_MESSAGE_HANDLER(AutomationMsg_HandleAccelerator, + OnAcceleratorPressed) + IPC_MESSAGE_HANDLER(AutomationMsg_TabbedOut, OnTabbedOut) + IPC_MESSAGE_HANDLER(AutomationMsg_OpenURL, OnOpenURL) + IPC_MESSAGE_HANDLER(AutomationMsg_NavigationFailed, OnNavigationFailed) + IPC_MESSAGE_HANDLER(AutomationMsg_DidNavigate, OnDidNavigate) + IPC_MESSAGE_HANDLER(AutomationMsg_TabLoaded, OnLoad) + IPC_MESSAGE_HANDLER(AutomationMsg_ForwardMessageToExternalHost, + OnMessageFromChromeFrame) + IPC_MESSAGE_HANDLER(AutomationMsg_ForwardContextMenuToExternalHost, + OnHandleContextMenu) + IPC_MESSAGE_HANDLER(AutomationMsg_RequestStart, OnRequestStart) + IPC_MESSAGE_HANDLER(AutomationMsg_RequestRead, OnRequestRead) + IPC_MESSAGE_HANDLER(AutomationMsg_RequestEnd, OnRequestEnd) + IPC_MESSAGE_HANDLER(AutomationMsg_SetCookieAsync, OnSetCookieAsync) + IPC_MESSAGE_HANDLER(AutomationMsg_AttachExternalTab, OnAttachExternalTab) + IPC_END_MESSAGE_MAP() +} diff --git a/chrome_frame/chrome_frame_delegate.h b/chrome_frame/chrome_frame_delegate.h new file mode 100644 index 0000000..a3302da --- /dev/null +++ b/chrome_frame/chrome_frame_delegate.h @@ -0,0 +1,99 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_FRAME_CHROME_FRAME_DELEGATE_H_ +#define CHROME_FRAME_CHROME_FRAME_DELEGATE_H_ + +#include "chrome/test/automation/automation_messages.h" +#include "ipc/ipc_message.h" + +// A common interface supported by all the browser specific ChromeFrame +// implementations. +class ChromeFrameDelegate { + public: + + typedef HWND WindowType; + + virtual WindowType GetWindow() const = 0; + virtual void GetBounds(RECT* bounds) = 0; + virtual std::string GetDocumentUrl() = 0; + virtual void OnAutomationServerReady() = 0; + virtual void OnAutomationServerLaunchFailed( + AutomationLaunchResult reason, const std::string& server_version) = 0; + virtual void OnMessageReceived(const IPC::Message& msg) = 0; + + // This remains in interface since we call it if Navigate() + // returns immediate error. + virtual void OnLoadFailed(int error_code, const std::string& url) = 0; + + // Returns true if this instance is alive and well for processing automation + // messages. + virtual bool IsValid() const = 0; + + protected: + ~ChromeFrameDelegate() {} +}; + +// Template specialization +template <> struct RunnableMethodTraits<ChromeFrameDelegate> { + static void RetainCallee(ChromeFrameDelegate* obj) { + } + + static void ReleaseCallee(ChromeFrameDelegate* obj) { + } +}; + +extern UINT kAutomationServerReady; +extern UINT kMessageFromChromeFrame; + +class ChromeFrameDelegateImpl : public ChromeFrameDelegate { + public: + virtual WindowType GetWindow() { return NULL; } + virtual void GetBounds(RECT* bounds) {} + virtual std::string GetDocumentUrl() { return std::string(); } + virtual void OnAutomationServerReady() {} + virtual void OnAutomationServerLaunchFailed( + AutomationLaunchResult reason, const std::string& server_version) {} + virtual void OnLoadFailed(int error_code, const std::string& url) {} + virtual void OnMessageReceived(const IPC::Message& msg); + static bool IsTabMessage(const IPC::Message& message, int* tab_handle); + + virtual bool IsValid() const { + return true; + } + + protected: + // Protected methods to be overriden. + virtual void OnNavigationStateChanged(int tab_handle, int flags, + const IPC::NavigationInfo& nav_info) {} + virtual void OnUpdateTargetUrl(int tab_handle, + const std::wstring& new_target_url) {} + virtual void OnAcceleratorPressed(int tab_handle, const MSG& accel_message) {} + virtual void OnTabbedOut(int tab_handle, bool reverse) {} + virtual void OnOpenURL(int tab_handle, const GURL& url, + int open_disposition) {} + virtual void OnDidNavigate(int tab_handle, + const IPC::NavigationInfo& navigation_info) {} + virtual void OnNavigationFailed(int tab_handle, int error_code, + const GURL& gurl) {} + virtual void OnLoad(int tab_handle, const GURL& url) {} + virtual void OnMessageFromChromeFrame(int tab_handle, + const std::string& message, + const std::string& origin, + const std::string& target) {} + virtual void OnHandleContextMenu(int tab_handle, HANDLE menu_handle, + int x_pos, int y_pos, int align_flags) {} + virtual void OnRequestStart(int tab_handle, int request_id, + const IPC::AutomationURLRequest& request) {} + virtual void OnRequestRead(int tab_handle, int request_id, + int bytes_to_read) {} + virtual void OnRequestEnd(int tab_handle, int request_id, + const URLRequestStatus& status) {} + virtual void OnSetCookieAsync(int tab_handle, const GURL& url, + const std::string& cookie) {} + virtual void OnAttachExternalTab(int tab_handle, intptr_t cookie, + int disposition) {} +}; + +#endif // CHROME_FRAME_CHROME_FRAME_DELEGATE_H_ diff --git a/chrome_frame/chrome_frame_histograms.cc b/chrome_frame/chrome_frame_histograms.cc new file mode 100644 index 0000000..e1ea548 --- /dev/null +++ b/chrome_frame/chrome_frame_histograms.cc @@ -0,0 +1,81 @@ +// Copyright (c) 2009 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. + +#include "chrome_frame/chrome_frame_histograms.h" + +#include "base/histogram.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/pickle.h" + + // Initialize histogram statistics gathering system. +base::LazyInstance<StatisticsRecorder> + g_statistics_recorder_(base::LINKER_INITIALIZED); + +ChromeFrameHistogramSnapshots::ChromeFrameHistogramSnapshots() { + // Ensure that an instance of the StatisticsRecorder object is created. + g_statistics_recorder_.Get(); +} + +ChromeFrameHistogramSnapshots::HistogramPickledList + ChromeFrameHistogramSnapshots::GatherAllHistograms() { + + StatisticsRecorder::Histograms histograms; + StatisticsRecorder::GetHistograms(&histograms); + + HistogramPickledList pickled_histograms; + + for (StatisticsRecorder::Histograms::iterator it = histograms.begin(); + histograms.end() != it; + it++) { + GatherHistogram(**it, &pickled_histograms); + } + + return pickled_histograms; +} + +void ChromeFrameHistogramSnapshots::GatherHistogram( + const Histogram& histogram, + HistogramPickledList* pickled_histograms) { + + // Get up-to-date snapshot of sample stats. + Histogram::SampleSet snapshot; + histogram.SnapshotSample(&snapshot); + const std::string& histogram_name = histogram.histogram_name(); + + // Check if we already have a log of this histogram and if not create an + // empty set. + LoggedSampleMap::iterator it = logged_samples_.find(histogram_name); + Histogram::SampleSet* already_logged; + if (logged_samples_.end() == it) { + // Add new entry. + already_logged = &logged_samples_[histogram.histogram_name()]; + already_logged->Resize(histogram); // Complete initialization. + } else { + already_logged = &(it->second); + // Deduct any stats we've already logged from our snapshot. + snapshot.Subtract(*already_logged); + } + + // Snapshot now contains only a delta to what we've already_logged. + if (snapshot.TotalCount() > 0) { + GatherHistogramDelta(histogram, snapshot, pickled_histograms); + // Add new data into our running total. + already_logged->Add(snapshot); + } +} + +void ChromeFrameHistogramSnapshots::GatherHistogramDelta( + const Histogram& histogram, + const Histogram::SampleSet& snapshot, + HistogramPickledList* pickled_histograms) { + + DCHECK(0 != snapshot.TotalCount()); + snapshot.CheckSize(histogram); + + std::string histogram_info = + Histogram::SerializeHistogramInfo(histogram, snapshot); + pickled_histograms->push_back(histogram_info); +} diff --git a/chrome_frame/chrome_frame_histograms.h b/chrome_frame/chrome_frame_histograms.h new file mode 100644 index 0000000..03aaab5 --- /dev/null +++ b/chrome_frame/chrome_frame_histograms.h @@ -0,0 +1,54 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_FRAME_HISTOGRAM_SNAPSHOTS_H_ +#define CHROME_FRAME_HISTOGRAM_SNAPSHOTS_H_ + +#include <map> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/histogram.h" +#include "base/process.h" +#include "base/scoped_ptr.h" +#include "base/task.h" + +// This class gathers histogram data in the host browser process and +// serializes the data into a vector of strings to be uploaded to the +// Chrome browser process. It records the histogram data which has been +// logged and only uploads the delta with the next log. +// TODO(iyengar) +// This class does not contain any ChromeFrame specific stuff. It should +// be moved to base. +class ChromeFrameHistogramSnapshots { + public: + // Maintain a map of histogram names to the sample stats we've sent. + typedef std::map<std::string, Histogram::SampleSet> LoggedSampleMap; + typedef std::vector<std::string> HistogramPickledList; + + ChromeFrameHistogramSnapshots(); + ~ChromeFrameHistogramSnapshots() {} + + // Extract snapshot data and return it to be sent off to the Chrome browser + // process. + // Return only a delta to what we have already sent. + HistogramPickledList GatherAllHistograms(); + + private: + void GatherHistogram(const Histogram& histogram, + HistogramPickledList* histograms); + + void GatherHistogramDelta(const Histogram& histogram, + const Histogram::SampleSet& snapshot, + HistogramPickledList* histograms); + + // For histograms, record what we've already logged (as a sample for each + // histogram) so that we can send only the delta with the next log. + LoggedSampleMap logged_samples_; + + DISALLOW_COPY_AND_ASSIGN(ChromeFrameHistogramSnapshots); +}; + +#endif // CHROME_RENDERER_HISTOGRAM_SNAPSHOTS_H_ diff --git a/chrome_frame/chrome_frame_npapi.cc b/chrome_frame/chrome_frame_npapi.cc new file mode 100644 index 0000000..0c58cff --- /dev/null +++ b/chrome_frame/chrome_frame_npapi.cc @@ -0,0 +1,1462 @@ +// Copyright (c) 2009 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. + +#include "chrome_frame/chrome_frame_npapi.h" + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/string_util.h" +#include "base/win_util.h" +#include "chrome/test/automation/tab_proxy.h" +#include "chrome_frame/ff_privilege_check.h" +#include "chrome_frame/utils.h" + +MessageLoop* ChromeFrameNPAPI::message_loop_ = NULL; +int ChromeFrameNPAPI::instance_count_ = 0; + +static const char* kNpEventNames[] = { + "focus", + "blur", +}; + +NPClass ChromeFrameNPAPI::plugin_class_ = { + NP_CLASS_STRUCT_VERSION, + ChromeFrameNPAPI::AllocateObject, + ChromeFrameNPAPI::DeallocateObject, + ChromeFrameNPAPI::Invalidate, + ChromeFrameNPAPI::HasMethod, + ChromeFrameNPAPI::Invoke, + NULL, // invokeDefault + ChromeFrameNPAPI::HasProperty, + ChromeFrameNPAPI::GetProperty, + ChromeFrameNPAPI::SetProperty, + NULL, // remove property + NULL, // enumeration + NULL, // construct +}; + +NPIdentifier + ChromeFrameNPAPI::plugin_property_identifiers_[PLUGIN_PROPERTY_COUNT] + = {0}; + +const NPUTF8* ChromeFrameNPAPI::plugin_property_identifier_names_[] = { + "version", + "src", + "onload", + "onloaderror", + "onmessage", + "readystate", + "onprivatemessage", + "usechromenetwork", +}; + +const NPUTF8* ChromeFrameNPAPI::plugin_method_identifier_names_[] = { + "postMessage", + "postPrivateMessage", +}; + +ChromeFrameNPAPI::PluginMethod ChromeFrameNPAPI::plugin_methods_[] = { + &ChromeFrameNPAPI::postMessage, + &ChromeFrameNPAPI::postPrivateMessage, +}; + +NPIdentifier + ChromeFrameNPAPI::plugin_method_identifiers_[arraysize(plugin_methods_)] + = {0}; + + +void ChromeFrameNPAPI::CompileAsserts() { + NOTREACHED(); // This function should never be invoked. + + COMPILE_ASSERT(arraysize(plugin_method_identifier_names_) == + arraysize(plugin_methods_), + you_must_add_both_plugin_method_and_name); + + COMPILE_ASSERT(arraysize(plugin_property_identifier_names_) == + arraysize(plugin_property_identifiers_), + you_must_add_both_plugin_property_and_name); +} + +static const int kMaxBytesForPluginConsumption = 0x7FFFFFFF; + +static const char kPluginSrcAttribute[] = "src"; +static const char kPluginForceFullPageAttribute[] = "force_full_page"; +static const char kPluginOnloadAttribute[] = "onload"; +static const char kPluginOnErrorAttribute[] = "onloaderror"; +static const char kPluginOnMessageAttribute[] = "onmessage"; +static const char kPluginOnPrivateMessageAttribute[] = "onprivatemessage"; +// These properties can only be set in arguments at control instantiation. +// When the privileged_mode property is provided and set to true, the control +// will probe for whether its hosting document has the system principal, in +// which case privileged mode will be enabled. +static const char kPluginPrivilegedModeAttribute[] = "privileged_mode"; +// If privileged mode is enabled, the string value of this argument will +// be appended to the chrome.exe command line. +static const char kPluginChromeExtraArguments[] = "chrome_extra_arguments"; +// If privileged mode is enabled, the string value of this argument will +// be used as the profile name for our chrome.exe instance. +static const char kPluginChromeProfileName[] = "chrome_profile_name"; +// If chrome network stack is to be used +static const char kPluginUseChromeNetwork[] = "usechromenetwork"; + + +NPError NPP_New(NPMIMEType plugin_type, NPP instance, uint16 mode, int16 argc, + char* argn[], char* argv[], NPSavedData* saved) { + if (instance == NULL) + return NPERR_INVALID_INSTANCE_ERROR; + + ChromeFrameNPAPI::ChromeFrameNPObject* chrome_frame_npapi_obj = + reinterpret_cast<ChromeFrameNPAPI::ChromeFrameNPObject*>( + npapi::CreateObject(instance, ChromeFrameNPAPI::PluginClass())); + DCHECK(chrome_frame_npapi_obj != NULL); + + ChromeFrameNPAPI* plugin_instance = + chrome_frame_npapi_obj->chrome_frame_plugin_instance; + DCHECK(plugin_instance != NULL); + + // Note that we MUST set instance->pdata BEFORE calling Initialize. This is + // because Initialize can call back into the NPAPI host which will need the + // pdata field to be set. + chrome_frame_npapi_obj->chrome_frame_plugin_instance = + plugin_instance; + instance->pdata = chrome_frame_npapi_obj; + + bool init = plugin_instance->Initialize(plugin_type, instance, + mode, argc, argn, argv); + DCHECK(init); + + return NPERR_NO_ERROR; +} + +NPError NPP_Destroy(NPP instance, NPSavedData** save) { + // Takes ownership and releases the object at the end of scope. + ScopedNpObject<ChromeFrameNPAPI::ChromeFrameNPObject> chrome_frame_npapi_obj( + reinterpret_cast<ChromeFrameNPAPI::ChromeFrameNPObject*>( + instance->pdata)); + + if (chrome_frame_npapi_obj.get()) { + ChromeFrameNPAPI* plugin_instance = + ChromeFrameNPAPI::ChromeFrameInstanceFromPluginInstance(instance); + + plugin_instance->Uninitialize(); + instance->pdata = NULL; + } + + return NPERR_NO_ERROR; +} + +NPError NPP_SetWindow(NPP instance, NPWindow* window_info) { + if (window_info == NULL) { + NOTREACHED(); + return NPERR_GENERIC_ERROR; + } + + ChromeFrameNPAPI* plugin_instance = + ChromeFrameNPAPI::ChromeFrameInstanceFromPluginInstance(instance); + + if (plugin_instance == NULL) { + return NPERR_INVALID_INSTANCE_ERROR; + } + + plugin_instance->SetWindow(window_info); + return NPERR_NO_ERROR; +} + +NPError NPP_NewStream(NPP instance, NPMIMEType type, NPStream* stream, + NPBool seekable, uint16* stream_type) { + NPAPIUrlRequest* url_request = ChromeFrameNPAPI::ValidateRequest( + instance, stream->notifyData); + if (url_request) { + if (!url_request->OnStreamCreated(type, stream)) + return NPERR_GENERIC_ERROR; + } + + // We need to return the requested stream mode if we are returning a success + // code. If we don't do this it causes Opera to blow up. + *stream_type = NP_NORMAL; + return NPERR_NO_ERROR; +} + +NPError NPP_DestroyStream(NPP instance, NPStream* stream, NPReason reason) { + NPAPIUrlRequest* url_request = ChromeFrameNPAPI::ValidateRequest( + instance, stream->notifyData); + if (url_request) { + url_request->OnStreamDestroyed(reason); + } + + return NPERR_NO_ERROR; +} + +NPError NPP_GetValue(NPP instance, NPPVariable variable, void* value) { + if (variable == NPPVpluginScriptableNPObject) { + void** plugin = reinterpret_cast<void**>(value); + ChromeFrameNPAPI::ChromeFrameNPObject* chrome_frame_npapi_obj = + reinterpret_cast<ChromeFrameNPAPI::ChromeFrameNPObject*>( + instance->pdata); + // Return value is expected to be retained + npapi::RetainObject(reinterpret_cast<NPObject*>(chrome_frame_npapi_obj)); + *plugin = chrome_frame_npapi_obj; + return NPERR_NO_ERROR; + } + return NPERR_GENERIC_ERROR; +} + +NPError NPP_SetValue(NPP instance, NPNVariable variable, void* value) { + return NPERR_GENERIC_ERROR; +} + +int32 NPP_WriteReady(NPP instance, NPStream* stream) { + NPAPIUrlRequest* url_request = ChromeFrameNPAPI::ValidateRequest( + instance, stream->notifyData); + if (url_request) { + return url_request->OnWriteReady(); + } + + return kMaxBytesForPluginConsumption; +} + +int32 NPP_Write(NPP instance, NPStream* stream, int32 offset, int32 len, + void* buffer) { + NPAPIUrlRequest* url_request = ChromeFrameNPAPI::ValidateRequest( + instance, stream->notifyData); + if (url_request) { + return url_request->OnWrite(buffer, len); + } + + return len; +} + +void NPP_URLNotify(NPP instance, const char* url, NPReason reason, + void* notifyData) { + ChromeFrameNPAPI* plugin_instance = + ChromeFrameNPAPI::ChromeFrameInstanceFromPluginInstance(instance); + if (plugin_instance) { + plugin_instance->UrlNotify(url, reason, notifyData); + } +} + +void NPP_Print(NPP instance, NPPrint* print_info) { + ChromeFrameNPAPI* plugin_instance = + ChromeFrameNPAPI::ChromeFrameInstanceFromPluginInstance(instance); + + if (plugin_instance == NULL) { + NOTREACHED(); + return; + } + + plugin_instance->Print(print_info); +} + +// ChromeFrameNPAPI member defines. + +// TODO(tommi): remove ignore_setfocus_ since that's not how focus is +// handled anymore. + +ChromeFrameNPAPI::ChromeFrameNPAPI() + : instance_(NULL), + mode_(NP_EMBED), + force_full_page_plugin_(false), + ready_state_(READYSTATE_LOADING), + enabled_popups_(false) { +} + +ChromeFrameNPAPI::~ChromeFrameNPAPI() { + if (IsWindow()) { + if (!UnsubclassWindow()) { + // TODO(tommi): Figure out why this can sometimes happen in the + // WidgetModeFF_Resize unittest. + DLOG(ERROR) << "Couldn't unsubclass safely!"; + UnsubclassWindow(TRUE); + } + } + m_hWnd = NULL; + + instance_count_--; + if (instance_count_ <= 0) { + delete message_loop_; + message_loop_ = NULL; + } + + Uninitialize(); +} + +std::string GetLocation(NPP instance, NPObject* window) { + if (!window) { + // Can fail if the browser is closing (seen in Opera). + return ""; + } + + std::string result; + ScopedNpVariant href; + ScopedNpVariant location; + + bool ok = npapi::GetProperty(instance, window, + npapi::GetStringIdentifier("location"), &location); + DCHECK(ok); + DCHECK(location.type == NPVariantType_Object); + + if (ok) { + ok = npapi::GetProperty(instance, + location.value.objectValue, + npapi::GetStringIdentifier("href"), + &href); + DCHECK(ok); + DCHECK(href.type == NPVariantType_String); + if (ok) { + result.assign(href.value.stringValue.UTF8Characters, + href.value.stringValue.UTF8Length); + } + } + + return result; +} + +std::string ChromeFrameNPAPI::GetLocation() { + // Note that GetWindowObject() will cache the window object here. + return ::GetLocation(instance_, GetWindowObject()); +} + +bool ChromeFrameNPAPI::Initialize(NPMIMEType mime_type, NPP instance, + uint16 mode, int16 argc, char* argn[], + char* argv[]) { + if (!Base::Initialize()) + return false; + + instance_ = instance; + mime_type_ = mime_type; + mode_ = mode; + document_url_ = GetLocation(); + + if (instance_count_ == 0) { + DCHECK(message_loop_ == NULL); + message_loop_ = new MessageLoop(); + } + + instance_count_++; + + // Create our prefs service wrapper here. + DCHECK(!pref_service_.get()); + pref_service_ = CreatePrefService(); + if (!pref_service_.get()) { + NOTREACHED() << "new NpProxyService"; + return false; + } + + // Temporary variables for privileged only parameters + const char* onprivatemessage_arg = NULL; + const char* chrome_extra_arguments_arg = NULL; + const char* chrome_profile_name_arg = NULL; + bool chrome_network_arg_set = false; + bool chrome_network_arg = false; + bool wants_privileged = false; + + for (int i = 0; i < argc; ++i) { + if (LowerCaseEqualsASCII(argn[i], kPluginSrcAttribute)) { + src_ = ResolveURL(GetDocumentUrl(), argv[i]); + } else if (LowerCaseEqualsASCII(argn[i], kPluginForceFullPageAttribute)) { + force_full_page_plugin_ = atoi(argv[i]) ? true : false; + } else if (LowerCaseEqualsASCII(argn[i], kPluginOnErrorAttribute)) { + onerror_handler_ = JavascriptToNPObject(argv[i]); + } else if (LowerCaseEqualsASCII(argn[i], kPluginOnMessageAttribute)) { + onmessage_handler_ = JavascriptToNPObject(argv[i]); + } else if (LowerCaseEqualsASCII(argn[i], + kPluginPrivilegedModeAttribute)) { + // Test for the FireFox privileged mode if the user requests it + // in initialization parameters. + wants_privileged = atoi(argv[i]) ? true : false; + } else if (LowerCaseEqualsASCII(argn[i], + kPluginOnPrivateMessageAttribute)) { + onprivatemessage_arg = argv[i]; + } else if (LowerCaseEqualsASCII(argn[i], kPluginChromeExtraArguments)) { + chrome_extra_arguments_arg = argv[i]; + } else if (LowerCaseEqualsASCII(argn[i], kPluginChromeProfileName)) { + chrome_profile_name_arg = argv[i]; + } else if (LowerCaseEqualsASCII(argn[i], kPluginUseChromeNetwork)) { + chrome_network_arg_set = true; + chrome_network_arg = atoi(argv[i]) ? true : false; + } + } + + // Is the privileged mode requested? + if (wants_privileged) { + is_privileged_ = IsFireFoxPrivilegedInvocation(instance); + if (!is_privileged_) { + DLOG(WARNING) << "Privileged mode requested in non-privileged context"; + } + } + + std::wstring extra_arguments; + std::wstring profile_name(GetHostProcessName(false)); + if (is_privileged_) { + // Process any privileged mode-only arguments we were handed. + if (onprivatemessage_arg) + onprivatemessage_handler_ = JavascriptToNPObject(onprivatemessage_arg); + + if (chrome_extra_arguments_arg) + extra_arguments = UTF8ToWide(chrome_extra_arguments_arg); + + if (chrome_profile_name_arg) + profile_name = UTF8ToWide(chrome_profile_name_arg); + + if (chrome_network_arg_set) + automation_client_->set_use_chrome_network(chrome_network_arg); + + } + + // TODO(joshia): Initialize navigation here and send proxy config as + // part of LaunchSettings + /* + if (!src_.empty()) + automation_client_->InitiateNavigation(src_); + + std::string proxy_settings; + bool has_prefs = pref_service_->Initialize(instance_, + automation_client_.get()); + if (has_prefs && pref_service_->GetProxyValueJSONString(&proxy_settings)) { + automation_client_->SetProxySettings(proxy_settings); + } + */ + + // We can't call SubscribeToFocusEvents here since + // when Initialize gets called, Opera is in a state where + // it can't handle calls back and the thread will hang. + // Instead, we call SubscribeToFocusEvents when we initialize + // our plugin window. + + // TODO(stoyan): Ask host for specific interface whether to honor + // host's in-private mode. + return InitializeAutomation(profile_name, extra_arguments, + GetBrowserIncognitoMode()); +} + +void ChromeFrameNPAPI::Uninitialize() { + // Don't call SetReadyState as it will end up calling FireEvent. + // We are in the context of NPP_DESTROY. + ready_state_ = READYSTATE_UNINITIALIZED; + + UnsubscribeFromFocusEvents(); + + if (pref_service_) { + pref_service_->UnInitialize(); + pref_service_ = NULL; + } + + window_object_.Free(); + onerror_handler_.Free(); + onmessage_handler_.Free(); + onprivatemessage_handler_.Free(); + + Base::Uninitialize(); +} + +void ChromeFrameNPAPI::OnFinalMessage(HWND window) { + // The automation server should be gone by now. + Uninitialize(); +} + +void ChromeFrameNPAPI::SubscribeToFocusEvents() { + DCHECK(focus_listener_.get() == NULL); + + focus_listener_ = new DomEventListener(this); + if (!focus_listener_->Subscribe(instance_, kNpEventNames, + arraysize(kNpEventNames))) { + focus_listener_ = NULL; + focus_listener_ = new NPObjectEventListener(this); + if (!focus_listener_->Subscribe(instance_, kNpEventNames, + arraysize(kNpEventNames))) { + DLOG(ERROR) << "Failed to subscribe to focus events"; + focus_listener_ = NULL; + } + } +} + +void ChromeFrameNPAPI::UnsubscribeFromFocusEvents() { + if (!focus_listener_.get()) + return; + + bool ret = focus_listener_->Unsubscribe(instance_, kNpEventNames, + arraysize(kNpEventNames)); + DLOG_IF(WARNING, !ret) << "focus_listener_->Unsubscribe failed"; + focus_listener_ = NULL; +} + +bool ChromeFrameNPAPI::SetWindow(NPWindow* window_info) { + if (!window_info || !automation_client_.get()) { + NOTREACHED(); + return false; + } + + HWND window = reinterpret_cast<HWND>(window_info->window); + if (!::IsWindow(window)) { + // No window created yet. Ignore this call. + return false; + } + + if (IsWindow()) { + // We've already subclassed, make sure that SetWindow doesn't get called + // with an HWND other than the one we subclassed during our lifetime. + DCHECK(window == m_hWnd); + return true; + } + + automation_client_->SetParentWindow(window); + + SubscribeToFocusEvents(); + + if (force_full_page_plugin_) { + // By default full page mode is only enabled when the plugin is loaded off + // a separate file, i.e. it is the primary content in the window. Even if + // we specify the width/height attributes for the plugin as 100% each, FF + // instantiates the plugin passing in a width/height of 100px each. To + // workaround this we resize the plugin window passed in by FF to the size + // of its parent. + HWND plugin_parent_window = ::GetParent(window); + RECT plugin_parent_rect = {0}; + ::GetClientRect(plugin_parent_window, &plugin_parent_rect); + ::SetWindowPos(window, NULL, plugin_parent_rect.left, + plugin_parent_rect.top, + plugin_parent_rect.right - plugin_parent_rect.left, + plugin_parent_rect.bottom - plugin_parent_rect.top, 0); + } + + // Subclass the browser's plugin window here. + if (SubclassWindow(window)) { + DWORD new_style_flags = WS_CLIPCHILDREN; + ModifyStyle(0, new_style_flags, 0); + + if (ready_state_ < READYSTATE_INTERACTIVE) { + SetReadyState(READYSTATE_INTERACTIVE); + } + } + + return true; +} + +void ChromeFrameNPAPI::Print(NPPrint* print_info) { + if (!print_info) { + NOTREACHED(); + return; + } + + // We dont support full tab mode yet. + if (print_info->mode != NP_EMBED) { + NOTREACHED(); + return; + } + + NPWindow window = print_info->print.embedPrint.window; + + RECT print_bounds = {0}; + print_bounds.left = window.x; + print_bounds.top = window.y; + print_bounds.right = window.x + window.width; + print_bounds.bottom = window.x + window.height; + + automation_client_->Print( + reinterpret_cast<HDC>(print_info->print.embedPrint.platformPrint), + print_bounds); +} + +void ChromeFrameNPAPI::UrlNotify(const char* url, NPReason reason, + void* notify_data) { + if (enabled_popups_) { + // We have opened the URL so tell the browser to restore popup settings + enabled_popups_ = false; + npapi::PopPopupsEnabledState(instance_); + } + + // It is now safe to release the additional reference on the request + NPAPIUrlRequest* request = RequestFromNotifyData(notify_data); + if (request) { + request->Stop(); + request->Release(); + } +} + +void ChromeFrameNPAPI::OnAcceleratorPressed(int tab_handle, + const MSG& accel_message) { + DLOG(INFO) << __FUNCTION__ << " msg:" + << StringPrintf("0x%04X", accel_message.message) << " key:" + << accel_message.wParam; + + // The host browser does call TranslateMessage on messages like WM_KEYDOWN + // WM_KEYUP, etc, which will result in messages like WM_CHAR, WM_SYSCHAR, etc + // being posted to the message queue. We don't post these messages here to + // avoid these messages from getting handled twice. + if (accel_message.message != WM_CHAR && + accel_message.message != WM_DEADCHAR && + accel_message.message != WM_SYSCHAR && + accel_message.message != WM_SYSDEADCHAR) { + // A very primitive way to handle keystrokes. + // TODO(tommi): When we've implemented a way for chrome to + // know when keystrokes are handled (deterministically) on that side, + // then this function should get called and not otherwise. + ::PostMessage(::GetParent(m_hWnd), accel_message.message, + accel_message.wParam, accel_message.lParam); + } + + if (automation_client_.get()) { + TabProxy* tab = automation_client_->tab(); + if (tab) { + tab->ProcessUnhandledAccelerator(accel_message); + } + } +} + +void ChromeFrameNPAPI::OnTabbedOut(int tab_handle, bool reverse) { + DLOG(INFO) << __FUNCTION__; + + ignore_setfocus_ = true; + HWND parent = ::GetParent(m_hWnd); + ::SetFocus(parent); + + INPUT input = {0}; + input.type = INPUT_KEYBOARD; + input.ki.wVk = VK_TAB; + SendInput(1, &input, sizeof(input)); + input.ki.dwFlags = KEYEVENTF_KEYUP; + SendInput(1, &input, sizeof(input)); + + ignore_setfocus_ = false; +} + +void ChromeFrameNPAPI::OnOpenURL(int tab_handle, + const GURL& url, int open_disposition) { + std::string target; + switch (open_disposition) { + case NEW_FOREGROUND_TAB: + target = "_blank"; + break; + case NEW_BACKGROUND_TAB: + target = "_blank"; + break; + case NEW_WINDOW: + target = "_new"; + break; + default: + break; + } + + // Tell the browser to temporarily allow popups + enabled_popups_ = true; + npapi::PushPopupsEnabledState(instance_, TRUE); + npapi::GetURLNotify(instance_, url.spec().c_str(), target.c_str(), NULL); +} + +void ChromeFrameNPAPI::OnRequestStart(int tab_handle, int request_id, + const IPC::AutomationURLRequest& request) { + scoped_refptr<NPAPIUrlRequest> new_request(new NPAPIUrlRequest(instance_)); + DCHECK(new_request); + if (new_request->Initialize(automation_client_.get(), tab_handle, + request_id, request.url, request.method, + request.referrer, request.extra_request_headers, + request.upload_data.get(), true)) { + if (new_request->Start()) { + // Keep additional reference on request for NPSTREAM + // This will be released in NPP_UrlNotify + new_request->AddRef(); + } + } +} + +void ChromeFrameNPAPI::OnRequestRead(int tab_handle, int request_id, + int bytes_to_read) { + automation_client_->ReadRequest(request_id, bytes_to_read); +} + +void ChromeFrameNPAPI::OnRequestEnd(int tab_handle, int request_id, + const URLRequestStatus& status) { + automation_client_->RemoveRequest(request_id, status.status(), true); +} + +void ChromeFrameNPAPI::OnSetCookieAsync(int tab_handle, const GURL& url, + const std::string& cookie) { + // Use the newer NPAPI way if available + if (npapi::VersionMinor() >= NPVERS_HAS_URL_AND_AUTH_INFO) { + npapi::SetValueForURL(instance_, NPNURLVCookie, url.spec().c_str(), + cookie.c_str(), cookie.length()); + } else if (url == GURL(document_url_)) { + std::string script = "javascript:document.cookie="; + script.append(cookie); + script.append(1, ';'); + ExecuteScript(script, NULL); + } else { + // Third party cookie, use nsICookieService to set the cookie. + NOTREACHED(); + } +} + +bool ChromeFrameNPAPI::HasMethod(NPObject* obj, NPIdentifier name) { + for (int i = 0; i < arraysize(plugin_methods_); ++i) { + if (name == plugin_method_identifiers_[i]) + return true; + } + + return false; +} + +bool ChromeFrameNPAPI::Invoke(NPObject* header, NPIdentifier name, + const NPVariant* args, uint32_t arg_count, + NPVariant* result) { + ChromeFrameNPAPI* plugin_instance = ChromeFrameInstanceFromNPObject(header); + if (!plugin_instance && (plugin_instance->automation_client_.get())) + return false; + + bool success = false; + for (int i = 0; i < arraysize(plugin_methods_); ++i) { + if (name == plugin_method_identifiers_[i]) { + PluginMethod method = plugin_methods_[i]; + success = (plugin_instance->*method)(header, args, arg_count, result); + break; + } + } + + return success; +} + +void ChromeFrameNPAPI::InitializeIdentifiers() { + npapi::GetStringIdentifiers(plugin_method_identifier_names_, + arraysize(plugin_methods_), + plugin_method_identifiers_); + + npapi::GetStringIdentifiers(plugin_property_identifier_names_, + PLUGIN_PROPERTY_COUNT, + plugin_property_identifiers_); +} + +NPObject* ChromeFrameNPAPI::AllocateObject(NPP instance, NPClass* class_name) { + static bool identifiers_initialized = false; + + ChromeFrameNPObject* plugin_object = new ChromeFrameNPObject(); + DCHECK(plugin_object != NULL); + + plugin_object->chrome_frame_plugin_instance = new ChromeFrameNPAPI(); + DCHECK(plugin_object->chrome_frame_plugin_instance != NULL); + + plugin_object->npp = NULL; + + COMPILE_ASSERT(arraysize(plugin_method_identifiers_) == + arraysize(plugin_method_identifier_names_), + method_count_mismatch); + + COMPILE_ASSERT(arraysize(plugin_method_identifiers_) == + arraysize(plugin_methods_), + method_count_mismatch); + + if (!identifiers_initialized) { + InitializeIdentifiers(); + identifiers_initialized = true; + } + + return reinterpret_cast<NPObject*>(plugin_object); +} + +void ChromeFrameNPAPI::DeallocateObject(NPObject* header) { + ChromeFrameNPObject* plugin_object = + reinterpret_cast<ChromeFrameNPObject*>(header); + DCHECK(plugin_object != NULL); + + if (plugin_object) { + delete plugin_object->chrome_frame_plugin_instance; + delete plugin_object; + } +} + +void ChromeFrameNPAPI::Invalidate(NPObject* header) { + DCHECK(header); + ChromeFrameNPObject* plugin_object = + reinterpret_cast<ChromeFrameNPObject*>(header); + if (plugin_object) { + DCHECK(plugin_object->chrome_frame_plugin_instance); + plugin_object->chrome_frame_plugin_instance->Uninitialize(); + } +} + +ChromeFrameNPAPI* ChromeFrameNPAPI::ChromeFrameInstanceFromPluginInstance( + NPP instance) { + if ((instance == NULL) || (instance->pdata == NULL)) { + NOTREACHED(); + return NULL; + } + + return ChromeFrameInstanceFromNPObject(instance->pdata); +} + +ChromeFrameNPAPI* ChromeFrameNPAPI::ChromeFrameInstanceFromNPObject( + void* object) { + ChromeFrameNPObject* plugin_object = + reinterpret_cast<ChromeFrameNPObject*>(object); + if (!plugin_object) { + NOTREACHED(); + return NULL; + } + + DCHECK(plugin_object->chrome_frame_plugin_instance); + return plugin_object->chrome_frame_plugin_instance; +} + +bool ChromeFrameNPAPI::HasProperty(NPObject* obj, NPIdentifier name) { + for (int i = 0; i < PLUGIN_PROPERTY_COUNT; ++i) { + if (name == plugin_property_identifiers_[i]) + return true; + } + return false; +} + +bool ChromeFrameNPAPI::GetProperty(NPIdentifier name, + NPVariant* variant) { + if (name == plugin_property_identifiers_[PLUGIN_PROPERTY_ONERROR]) { + if (onerror_handler_) { + variant->type = NPVariantType_Object; + variant->value.objectValue = onerror_handler_.Copy(); + return true; + } + } else if (name == plugin_property_identifiers_[PLUGIN_PROPERTY_ONMESSAGE]) { + if (onmessage_handler_) { + variant->type = NPVariantType_Object; + variant->value.objectValue = onmessage_handler_.Copy(); + return true; + } + } else if (name == + plugin_property_identifiers_[PLUGIN_PROPERTY_ONPRIVATEMESSAGE]) { + if (!is_privileged_) { + DLOG(WARNING) << "Attempt to read onprivatemessage property while not " + "privileged"; + } else { + if (onprivatemessage_handler_) { + variant->type = NPVariantType_Object; + variant->value.objectValue = + onprivatemessage_handler_.Copy(); + return true; + } + } + } else if (name == plugin_property_identifiers_[PLUGIN_PROPERTY_SRC]) { + AllocateStringVariant(src_, variant); + return true; + } else if (name == plugin_property_identifiers_[PLUGIN_PROPERTY_VERSION]) { + const std::wstring version = + automation_client_->GetVersion(); + AllocateStringVariant(WideToUTF8(version), variant); + return true; + } else if (name == plugin_property_identifiers_[PLUGIN_PROPERTY_READYSTATE]) { + INT32_TO_NPVARIANT(ready_state_, *variant); + return true; + } else if (name == + plugin_property_identifiers_[PLUGIN_PROPERTY_USECHROMENETWORK]) { + BOOLEAN_TO_NPVARIANT(automation_client_->use_chrome_network(), *variant); + return true; + } + + return false; +} + +bool ChromeFrameNPAPI::GetProperty(NPObject* object, NPIdentifier name, + NPVariant* variant) { + if (!object || !variant) { + NOTREACHED(); + return false; + } + + ChromeFrameNPAPI* plugin_instance = ChromeFrameInstanceFromNPObject(object); + if (!plugin_instance) { + NOTREACHED(); + return false; + } + + return plugin_instance->GetProperty(name, variant); +} + +bool ChromeFrameNPAPI::SetProperty(NPIdentifier name, + const NPVariant* variant) { + if (NPVARIANT_IS_OBJECT(*variant)) { + if (name == plugin_property_identifiers_[PLUGIN_PROPERTY_ONERROR]) { + onerror_handler_.Free(); + onerror_handler_ = variant->value.objectValue; + return true; + } else if ( + name == plugin_property_identifiers_[PLUGIN_PROPERTY_ONMESSAGE]) { + onmessage_handler_.Free(); + onmessage_handler_ = variant->value.objectValue; + return true; + } else if (name == + plugin_property_identifiers_[PLUGIN_PROPERTY_ONPRIVATEMESSAGE]) { + if (!is_privileged_) { + DLOG(WARNING) << "Attempt to set onprivatemessage while not privileged"; + } else { + onprivatemessage_handler_.Free(); + onprivatemessage_handler_ = variant->value.objectValue; + return true; + } + } + } else if (NPVARIANT_IS_STRING(*variant) || NPVARIANT_IS_NULL(*variant)) { + if (name == plugin_property_identifiers_[PLUGIN_PROPERTY_SRC]) { + return NavigateToURL(variant, 1, NULL); + } + } else if (NPVARIANT_IS_BOOLEAN(*variant)) { + if (name == + plugin_property_identifiers_[PLUGIN_PROPERTY_USECHROMENETWORK]) { + automation_client_->set_use_chrome_network( + NPVARIANT_TO_BOOLEAN(*variant)); + } + } + + return false; +} + +bool ChromeFrameNPAPI::SetProperty(NPObject* object, NPIdentifier name, + const NPVariant* variant) { + if (!object || !variant) { + DLOG(ERROR) << "Cannot set property: " << npapi::StringFromIdentifier(name); + return false; + } + + ChromeFrameNPAPI* plugin_instance = ChromeFrameInstanceFromNPObject(object); + if (!plugin_instance) { + NOTREACHED(); + return false; + } + + return plugin_instance->SetProperty(name, variant); +} + +void ChromeFrameNPAPI::OnFocus() { + DLOG(INFO) << __FUNCTION__; + PostMessage(WM_SETFOCUS, 0, 0); +} + +void ChromeFrameNPAPI::OnEvent(const char* event_name) { + DCHECK(event_name); + DLOG(INFO) << event_name; + + if (lstrcmpiA(event_name, "focus") == 0) { + OnFocus(); + } else if (lstrcmpiA(event_name, "blur") == 0) { + OnBlur(); + } else { + NOTREACHED() << event_name; + } +} + +LRESULT CALLBACK ChromeFrameNPAPI::DropKillFocusHook(int code, WPARAM wparam, + LPARAM lparam) { + LRESULT ret = 0; + CWPSTRUCT* wp = reinterpret_cast<CWPSTRUCT*>(lparam); + if ((code < 0) || (wp->message != WM_KILLFOCUS)) + ret = ::CallNextHookEx(NULL, code, wparam, lparam); + + return ret; +} + +LRESULT ChromeFrameNPAPI::OnSetFocus(UINT message, WPARAM wparam, + LPARAM lparam, BOOL& handled) { // NO_LINT + // Opera has a WH_CALLWNDPROC hook that handles WM_KILLFOCUS and + // prevents us from setting the focus to the tab. + // To work around that, we set a temporary hook here that does nothing + // (not even call other hooks) when it sees WM_KILLFOCUS. + HHOOK hook = NULL; + hook = ::SetWindowsHookEx(WH_CALLWNDPROC, DropKillFocusHook, NULL, + ::GetCurrentThreadId()); + // Since we chain message maps, make sure we are not calling base class + // twice for WM_SETFOCUS. + BOOL handled_by_base = TRUE; + LRESULT ret = Base::OnSetFocus(message, wparam, lparam, handled_by_base); + if (hook) + ::UnhookWindowsHookEx(hook); + + return ret; +} + +void ChromeFrameNPAPI::OnBlur() { + DLOG(INFO) << __FUNCTION__; +} + +void ChromeFrameNPAPI::OnLoad(int, const GURL& gurl) { + DLOG(INFO) << "Firing onload"; + FireEvent("load", gurl.spec()); +} + +void ChromeFrameNPAPI::OnLoadFailed(int error_code, const std::string& url) { + FireEvent("loaderror", url); + + ScopedNpVariant result; + InvokeDefault(onerror_handler_, url, &result); +} + +void ChromeFrameNPAPI::OnMessageFromChromeFrame(int tab_handle, + const std::string& message, + const std::string& origin, + const std::string& target) { + bool private_message = false; + if (target.compare("*") != 0) { + if (is_privileged_) { + private_message = true; + } else { + if (!HaveSameOrigin(target, document_url_)) { + DLOG(WARNING) << "Dropping posted message since target doesn't match " + "the current document's origin. target=" << target; + return; + } + } + } + + // Create a MessageEvent object that contains the message and origin + // as well as supporting other MessageEvent (see the HTML5 spec) properties. + // Then call the onmessage handler. + ScopedNpObject<NPObject> event; + bool ok = CreateMessageEvent(false, true, message, origin, event.Receive()); + if (ok) { + // Don't call FireEvent here (or we'll have an event wrapped by an event). + DispatchEvent(event); + + ScopedNpVariant result; + NPVariant params[2]; + OBJECT_TO_NPVARIANT(event, params[0]); + bool invoke = false; + if (private_message) { + DCHECK(is_privileged_); + STRINGN_TO_NPVARIANT(target.c_str(), target.length(), params[1]); + invoke = InvokeDefault(onprivatemessage_handler_, + arraysize(params), + params, + &result); + } else { + invoke = InvokeDefault(onmessage_handler_, params[0], &result); + } + DLOG_IF(WARNING, !invoke) << "InvokeDefault failed"; + } else { + NOTREACHED() << "CreateMessageEvent"; + } +} + +void ChromeFrameNPAPI::OnAutomationServerReady() { + Base::OnAutomationServerReady(); + + std::string proxy_settings; + bool has_prefs = pref_service_->Initialize(instance_, + automation_client_.get()); + if (has_prefs && pref_service_->GetProxyValueJSONString(&proxy_settings)) { + automation_client_->SetProxySettings(proxy_settings); + } + + if (!src_.empty()) { + if (!automation_client_->InitiateNavigation(src_)) { + DLOG(ERROR) << "Failed to navigate to: " << src_; + src_.clear(); + } + } + + SetReadyState(READYSTATE_COMPLETE); +} + +void ChromeFrameNPAPI::OnAutomationServerLaunchFailed( + AutomationLaunchResult reason, const std::string& server_version) { + SetReadyState(READYSTATE_UNINITIALIZED); + + if (reason == AUTOMATION_VERSION_MISMATCH) { + DisplayVersionMismatchWarning(m_hWnd, server_version); + } +} + +bool ChromeFrameNPAPI::InvokeDefault(NPObject* object, + unsigned param_count, + const NPVariant* params, + NPVariant* result) { + if (!object) + return false; + + bool ret = npapi::InvokeDefault(instance_, object, params, param_count, + result); + // InvokeDefault can return false in FF even though we do see the call + // go through. It's not clear to me what the circumstances are, so + // we log it as a warning while tracking it down. + DLOG_IF(WARNING, !ret) << "npapi::InvokeDefault failed"; + return ret; +} + +bool ChromeFrameNPAPI::InvokeDefault(NPObject* object, const std::string& param, + NPVariant* result) { + NPVariant arg; + STRINGN_TO_NPVARIANT(param.c_str(), param.length(), arg); + return InvokeDefault(object, arg, result); +} + +bool ChromeFrameNPAPI::InvokeDefault(NPObject* object, const NPVariant& param, + NPVariant* result) { + return InvokeDefault(object, 1, ¶m, result); +} + +bool ChromeFrameNPAPI::CreateEvent(const std::string& type, bool bubbles, + bool cancelable, NPObject** basic_event) { + DCHECK(basic_event); + NPObject* window = GetWindowObject(); + if (!window) { + // Can fail if the browser is closing (seen in Opera). + return false; + } + + const char* identifier_names[] = { + "document", + "createEvent", + "initEvent", + }; + + NPIdentifier identifiers[arraysize(identifier_names)]; + npapi::GetStringIdentifiers(identifier_names, arraysize(identifier_names), + identifiers); + + // Fetch the document object from the window. + ScopedNpVariant document; + bool ok = npapi::GetProperty(instance_, window, identifiers[0], &document); + if (!ok) { + // This could happen if the page is being unloaded. + DLOG(WARNING) << "Failed to fetch the document object"; + return false; + } + + bool success = false; + if (ok && NPVARIANT_IS_OBJECT(document)) { + // Call document.createEvent("Event") to create a basic event object. + NPVariant event_type; + STRINGN_TO_NPVARIANT("Event", sizeof("Event") - 1, event_type); + ScopedNpVariant result; + success = npapi::Invoke(instance_, NPVARIANT_TO_OBJECT(document), + identifiers[1], &event_type, 1, &result); + if (!NPVARIANT_IS_OBJECT(result)) { + DLOG(WARNING) << "Failed to invoke createEvent"; + success = false; + } else { + NPVariant init_args[3]; + STRINGN_TO_NPVARIANT(type.c_str(), type.length(), init_args[0]); + BOOLEAN_TO_NPVARIANT(bubbles, init_args[1]); + BOOLEAN_TO_NPVARIANT(cancelable, init_args[2]); + + // Now initialize the event object by calling + // event.initEvent(type, bubbles, cancelable); + ScopedNpVariant init_results; + ok = npapi::Invoke(instance_, NPVARIANT_TO_OBJECT(result), identifiers[2], + init_args, arraysize(init_args), &init_results); + if (ok) { + success = true; + // Finally, pass the ownership to the caller. + *basic_event = NPVARIANT_TO_OBJECT(result); + VOID_TO_NPVARIANT(result); // Prevent the object from being released. + } else { + DLOG(ERROR) << "initEvent failed"; + success = false; + } + } + } + + return success; +} + +bool ChromeFrameNPAPI::CreateMessageEvent(bool bubbles, bool cancelable, + const std::string& data, + const std::string& origin, + NPObject** message_event) { + DCHECK(message_event); + ScopedNpObject<NPObject> event; + bool ok = CreateEvent("message", false, true, event.Receive()); + if (ok) { + typedef enum { + DATA, + ORIGIN, + LAST_EVENT_ID, + SOURCE, + MESSAGE_PORT, + IDENTIFIER_COUNT, // Must be last. + } StringIdentifiers; + + static NPIdentifier identifiers[IDENTIFIER_COUNT] = {0}; + if (!identifiers[0]) { + const NPUTF8* identifier_names[] = { + "data", + "origin", + "lastEventId", + "source", + "messagePort", + }; + COMPILE_ASSERT(arraysize(identifier_names) == arraysize(identifiers), + mismatched_array_size); + npapi::GetStringIdentifiers(identifier_names, IDENTIFIER_COUNT, + identifiers); + } + + NPVariant arg; + STRINGN_TO_NPVARIANT(data.c_str(), data.length(), arg); + npapi::SetProperty(instance_, event, identifiers[DATA], &arg); + STRINGN_TO_NPVARIANT(origin.c_str(), origin.length(), arg); + npapi::SetProperty(instance_, event, identifiers[ORIGIN], &arg); + STRINGN_TO_NPVARIANT("", 0, arg); + npapi::SetProperty(instance_, event, identifiers[LAST_EVENT_ID], &arg); + NULL_TO_NPVARIANT(arg); + npapi::SetProperty(instance_, event, identifiers[SOURCE], &arg); + npapi::SetProperty(instance_, event, identifiers[MESSAGE_PORT], &arg); + *message_event = event.Detach(); + } + + return ok; +} + + +void ChromeFrameNPAPI::DispatchEvent(NPObject* event) { + DCHECK(event != NULL); + + ScopedNpObject<NPObject> embed; + npapi::GetValue(instance_, NPNVPluginElementNPObject, &embed); + if (embed != NULL) { + NPVariant param; + OBJECT_TO_NPVARIANT(event, param); + ScopedNpVariant result; + bool invoke = npapi::Invoke(instance_, embed, + npapi::GetStringIdentifier("dispatchEvent"), ¶m, 1, &result); + DLOG_IF(WARNING, !invoke) << "dispatchEvent failed"; + } else { + NOTREACHED() << "NPNVPluginElementNPObject"; + } +} + +bool ChromeFrameNPAPI::ExecuteScript(const std::string& script, + NPVariant* result) { + NPObject* window = GetWindowObject(); + if (!window) { + NOTREACHED(); + return false; + } + + NPString script_for_execution; + script_for_execution.UTF8Characters = script.c_str(); + script_for_execution.UTF8Length = script.length(); + + return npapi::Evaluate(instance_, window, &script_for_execution, result); +} + +NPObject* ChromeFrameNPAPI::JavascriptToNPObject(const std::string& script) { + // Convert the passed in script to an invocable NPObject + // To achieve this we save away the function in a dummy window property + // which is then read to get the script object representing the function. + + std::string script_code = + "javascript:window.__cf_get_function_object ="; + + // If we are able to look up the name in the javascript namespace, then it + // means that the caller passed in a function name. Convert the function + // name to a NPObject we can invoke on. + if (IsValidJavascriptFunction(script)) { + script_code += script; + } else { + script_code += "new Function(\""; + script_code += script; + script_code += "\");"; + } + + NPVariant result; + if (!ExecuteScript(script_code, &result)) { + NOTREACHED(); + return NULL; + } + + DCHECK(result.type == NPVariantType_Object); + DCHECK(result.value.objectValue != NULL); + return result.value.objectValue; +} + +bool ChromeFrameNPAPI::IsValidJavascriptFunction(const std::string& script) { + std::string script_code = "javascript:window['"; + script_code += script; + script_code += "'];"; + + ScopedNpVariant result; + if (!ExecuteScript(script_code, &result)) { + NOTREACHED(); + return NULL; + } + + return result.type == NPVariantType_Object; +} + +bool ChromeFrameNPAPI::NavigateToURL(const NPVariant* args, uint32_t arg_count, + NPVariant* result) { + // Note that 'result' might be NULL. + if (arg_count != 1 || !(NPVARIANT_IS_STRING(args[0]) || + NPVARIANT_IS_NULL(args[0]))) { + NOTREACHED(); + return false; + } + + if (ready_state_ == READYSTATE_UNINITIALIZED) { + // Error(L"Chrome Frame failed to initialize."); + // TODO(tommi): call NPN_SetException + DLOG(WARNING) << "NavigateToURL called after failed initialization"; + return false; + } + + std::string url("about:blank"); + + if (!NPVARIANT_IS_NULL(args[0])) { + const NPString& str = args[0].value.stringValue; + if (str.UTF8Length) { + url.assign(std::string(str.UTF8Characters, str.UTF8Length)); + } + } + DLOG(WARNING) << __FUNCTION__ << " " << url; + std::string full_url = ResolveURL(GetDocumentUrl(), url); + if (full_url.empty()) + return false; + + src_ = full_url; + // Navigate only if we completed initialization i.e. proxy is set etc. + if (ready_state_ == READYSTATE_COMPLETE) { + if (!automation_client_->InitiateNavigation(full_url)) { + // TODO(tommi): call NPN_SetException. + src_.clear(); + return false; + } + } + return true; +} + +bool ChromeFrameNPAPI::postMessage(NPObject* npobject, const NPVariant* args, + uint32_t arg_count, NPVariant* result) { + if (arg_count < 1 || arg_count > 2 || !NPVARIANT_IS_STRING(args[0])) { + NOTREACHED(); + return false; + } + + const NPString& str = args[0].value.stringValue; + std::string message(str.UTF8Characters, str.UTF8Length); + std::string target; + if (arg_count == 2 && NPVARIANT_IS_STRING(args[1])) { + const NPString& str = args[1].value.stringValue; + target.assign(str.UTF8Characters, str.UTF8Length); + if (target.compare("*") != 0) { + GURL resolved(target); + if (!resolved.is_valid()) { + npapi::SetException(npobject, + "Unable to parse the specified target URL."); + return false; + } + target = resolved.spec(); + } + } else { + target = "*"; + } + + GURL url(GURL(document_url_).GetOrigin()); + std::string origin(url.is_empty() ? "null" : url.spec()); + + automation_client_->ForwardMessageFromExternalHost(message, origin, target); + + return true; +} + +bool ChromeFrameNPAPI::postPrivateMessage(NPObject* npobject, + const NPVariant* args, + uint32_t arg_count, + NPVariant* result) { + if (!is_privileged_) { + DLOG(WARNING) << "postPrivateMessage invoked in non-privileged mode"; + return false; + } + + if (arg_count != 3 || !NPVARIANT_IS_STRING(args[0]) || + !NPVARIANT_IS_STRING(args[1]) || !NPVARIANT_IS_STRING(args[2])) { + NOTREACHED(); + return false; + } + + const NPString& message_str = args[0].value.stringValue; + const NPString& origin_str = args[1].value.stringValue; + const NPString& target_str = args[2].value.stringValue; + std::string message(message_str.UTF8Characters, message_str.UTF8Length); + std::string origin(origin_str.UTF8Characters, origin_str.UTF8Length); + std::string target(target_str.UTF8Characters, target_str.UTF8Length); + + automation_client_->ForwardMessageFromExternalHost(message, origin, target); + + return true; +} + +void ChromeFrameNPAPI::FireEvent(const std::string& event_type, + const std::string& data) { + NPVariant arg; + STRINGN_TO_NPVARIANT(data.c_str(), data.length(), arg); + FireEvent(event_type, arg); +} + +void ChromeFrameNPAPI::FireEvent(const std::string& event_type, + const NPVariant& data) { + // Check that we're not bundling an event inside an event. + // Right now we're only expecting simple types for the data argument. + DCHECK(NPVARIANT_IS_OBJECT(data) == false); + + ScopedNpObject<NPObject> ev; + CreateEvent(event_type, false, false, ev.Receive()); + if (ev) { + // Add the 'data' member to the event. + bool set = npapi::SetProperty(instance_, ev, + npapi::GetStringIdentifier("data"), const_cast<NPVariant*>(&data)); + DCHECK(set); + DispatchEvent(ev); + } +} + +NpProxyService* ChromeFrameNPAPI::CreatePrefService() { + return new NpProxyService; +} + +NPObject* ChromeFrameNPAPI::GetWindowObject() const { + if (!window_object_.get()) { + NPError ret = npapi::GetValue(instance_, NPNVWindowNPObject, + window_object_.Receive()); + DLOG_IF(ERROR, ret != NPERR_NO_ERROR) << "NPNVWindowNPObject failed"; + } + return window_object_; +} + +bool ChromeFrameNPAPI::GetBrowserIncognitoMode() { + bool incognito_mode = false; + + // Check disabled for Opera due to bug: http://b/issue?id=1815494 + if (GetBrowserType() != BROWSER_OPERA) { + // Check whether host browser is in private mode; + NPBool private_mode = FALSE; + NPError err = npapi::GetValue(instance_, + NPNVprivateModeBool, + &private_mode); + if (err == NPERR_NO_ERROR && private_mode) { + incognito_mode = true; + } + } else { + DLOG(WARNING) << "Not checking for private mode in Opera"; + } + + return incognito_mode; +} + +NPAPIUrlRequest* ChromeFrameNPAPI::ValidateRequest( + NPP instance, void* notify_data) { + ChromeFrameNPAPI* plugin_instance = + ChromeFrameNPAPI::ChromeFrameInstanceFromPluginInstance(instance); + if (plugin_instance) { + return plugin_instance->RequestFromNotifyData(notify_data); + } + + return NULL; +} + +NPAPIUrlRequest* ChromeFrameNPAPI::RequestFromNotifyData( + void* notify_data) const { + NPAPIUrlRequest* request = reinterpret_cast<NPAPIUrlRequest*>(notify_data); + DCHECK(request ? automation_client_->IsValidRequest(request) : 1); + return request; +} + +bool ChromeFrameNPAPI::HandleContextMenuCommand(UINT cmd) { + if (cmd == IDC_ABOUT_CHROME_FRAME) { + // TODO: implement "About Chrome Frame" + } + return false; +} diff --git a/chrome_frame/chrome_frame_npapi.h b/chrome_frame/chrome_frame_npapi.h new file mode 100644 index 0000000..005d72f --- /dev/null +++ b/chrome_frame/chrome_frame_npapi.h @@ -0,0 +1,333 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_FRAME_CHROME_FRAME_NPAPI_H_ +#define CHROME_FRAME_CHROME_FRAME_NPAPI_H_ + +#include <atlbase.h> +#include <atlwin.h> +#include <string> + +#include "chrome_frame/chrome_frame_automation.h" +#include "chrome_frame/chrome_frame_plugin.h" +#include "chrome_frame/np_browser_functions.h" +#include "chrome_frame/np_event_listener.h" +#include "chrome_frame/np_proxy_service.h" +#include "chrome_frame/npapi_url_request.h" + +class MessageLoop; + +// ChromeFrameNPAPI: Implementation of the NPAPI plugin, which is responsible +// for hosting a chrome frame, i.e. an iframe like widget which hosts the the +// chrome window. This object delegates to Chrome.exe (via the Chrome +// IPC-based automation mechanism) for the actual rendering. +class ChromeFrameNPAPI + : public CWindowImpl<ChromeFrameNPAPI>, + public ChromeFramePlugin<ChromeFrameNPAPI>, + public NpEventDelegate { + public: + typedef ChromeFramePlugin<ChromeFrameNPAPI> Base; + + // NPObject structure which is exposed by us. + struct ChromeFrameNPObject : public NPObject { + NPP npp; + ChromeFrameNPAPI* chrome_frame_plugin_instance; + }; + + typedef enum { + PLUGIN_PROPERTY_VERSION, + PLUGIN_PROPERTY_SRC, + PLUGIN_PROPERTY_ONLOAD, + PLUGIN_PROPERTY_ONERROR, + PLUGIN_PROPERTY_ONMESSAGE, + PLUGIN_PROPERTY_READYSTATE, + PLUGIN_PROPERTY_ONPRIVATEMESSAGE, + PLUGIN_PROPERTY_USECHROMENETWORK, + PLUGIN_PROPERTY_COUNT // must be last + } PluginPropertyId; + + static const int kWmSwitchFocusToChromeFrame = WM_APP + 0x100; + + static NPClass plugin_class_; + static NPClass* PluginClass() { + return &plugin_class_; + } + + ChromeFrameNPAPI(); + ~ChromeFrameNPAPI(); + + bool Initialize(NPMIMEType mime_type, NPP instance, uint16 mode, + int16 argc, char* argn[], char* argv[]); + void Uninitialize(); + + bool SetWindow(NPWindow* window_info); + void UrlNotify(const char* url, NPReason reason, void* notify_data); + bool NewStream(NPMIMEType type, NPStream* stream, NPBool seekable, + uint16* stream_type); + + void Print(NPPrint* print_info); + + // NPObject functions, which ensure that the plugin object is scriptable. + static bool HasMethod(NPObject* obj, NPIdentifier name); + static bool Invoke(NPObject* header, NPIdentifier name, + const NPVariant* args, uint32_t arg_count, + NPVariant* result); + static NPObject* AllocateObject(NPP instance, NPClass* class_name); + static void DeallocateObject(NPObject* header); + + // Called by the scripting environment when the native code is shutdown. + // Any attempt to message a NPObject instance after the invalidate callback + // has been called will result in undefined behavior, even if the native code + // is still retaining those NPObject instances. + static void Invalidate(NPObject* header); + + // The following functions need to be implemented to ensure that FF3 + // invokes methods on the plugin. If these methods are not implemented + // then invokes on the plugin NPObject from the script fail with a + // bad NPObject error. + static bool HasProperty(NPObject* obj, NPIdentifier name); + static bool GetProperty(NPObject* obj, NPIdentifier name, NPVariant *variant); + static bool SetProperty(NPObject* obj, NPIdentifier name, + const NPVariant *variant); + + // Returns the ChromeFrameNPAPI object pointer from the NPP instance structure + // passed in by the browser. + static ChromeFrameNPAPI* ChromeFrameInstanceFromPluginInstance(NPP instance); + + // Returns the ChromeFrameNPAPI object pointer from the NPObject structure + // which represents our plugin class. + static ChromeFrameNPAPI* ChromeFrameInstanceFromNPObject(void* object); + + // Return a UrlRequest instance associated with the given instance and + // stream combination. + static NPAPIUrlRequest* ValidateRequest(NPP instance, void* notify_data); + +BEGIN_MSG_MAP(ChromeFrameNPAPI) + MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus) + CHAIN_MSG_MAP(Base) +END_MSG_MAP() + + LRESULT OnSetFocus(UINT message, WPARAM wparam, LPARAM lparam, + BOOL& handled); // NO_LINT + + // Implementation of NpEventDelegate + virtual void OnEvent(const char* event_name); + + void OnFocus(); + void OnBlur(); + + // Implementation of SetProperty, public to allow unittesting. + bool SetProperty(NPIdentifier name, const NPVariant *variant); + // Implementation of GetProperty, public to allow unittesting. + bool GetProperty(NPIdentifier name, NPVariant *variant); + + // Initialize string->identifier mapping, public to allow unittesting. + static void InitializeIdentifiers(); + + bool HandleContextMenuCommand(UINT cmd); + protected: + // Handler for accelerator messages passed on from the hosted chrome + // instance. + virtual void OnAcceleratorPressed(int tab_handle, const MSG& accel_message); + virtual void OnTabbedOut(int tab_handle, bool reverse); + virtual void OnOpenURL(int tab_handle, const GURL& url, int open_disposition); + virtual void OnLoad(int tab_handle, const GURL& url); + virtual void OnMessageFromChromeFrame(int tab_handle, + const std::string& message, + const std::string& origin, + const std::string& target); + virtual void OnRequestStart(int tab_handle, int request_id, + const IPC::AutomationURLRequest& request); + virtual void OnRequestRead(int tab_handle, int request_id, + int bytes_to_read); + virtual void OnRequestEnd(int tab_handle, int request_id, + const URLRequestStatus& status); + virtual void OnSetCookieAsync(int tab_handle, const GURL& url, + const std::string& cookie); + + // ChromeFrameDelegate overrides + virtual void OnLoadFailed(int error_code, const std::string& url); + virtual void OnAutomationServerReady(); + virtual void OnAutomationServerLaunchFailed( + AutomationLaunchResult reason, const std::string& server_version); + + private: + void SubscribeToFocusEvents(); + void UnsubscribeFromFocusEvents(); + + // Equivalent of: + // event = window.document.createEvent("Event"); + // event.initEvent(type, bubbles, cancelable); + // and then returns the event object. + bool CreateEvent(const std::string& type, bool bubbles, bool cancelable, + NPObject** basic_event); + + // Creates and initializes an event object of type "message". + // Used for postMessage. + bool CreateMessageEvent(bool bubbles, bool cancelable, + const std::string& data, const std::string& origin, + NPObject** message_event); + + // Calls chrome_frame.dispatchEvent to fire events to event listeners. + void DispatchEvent(NPObject* event); + + // Returns a pointer to the <object> element in the page that + // hosts the plugin. Note that this is the parent element of the <embed> + // element. The <embed> element doesn't support some of the events that + // we require, so we use the object element for receiving events. + bool GetObjectElement(nsIDOMElement** element); + + // Prototype for all methods that can be invoked from script. + typedef bool (ChromeFrameNPAPI::*PluginMethod)(NPObject* npobject, + const NPVariant* args, + uint32_t arg_count, + NPVariant* result); + + // Implementations of scriptable methods. + + bool NavigateToURL(const NPVariant* args, uint32_t arg_count, + NPVariant* result); + + bool postMessage(NPObject* npobject, const NPVariant* args, + uint32_t arg_count, NPVariant* result); + + // This method is only available when the control is in privileged mode. + bool postPrivateMessage(NPObject* npobject, const NPVariant* args, + uint32_t arg_count, NPVariant* result); + + // Pointers to method implementations. + static PluginMethod plugin_methods_[]; + + // NPObject method ids exposed by the plugin. + static NPIdentifier plugin_method_identifiers_[]; + + // NPObject method names exposed by the plugin. + static const NPUTF8* plugin_method_identifier_names_[]; + + // NPObject property ids exposed by the plugin. + static NPIdentifier plugin_property_identifiers_[]; + + // NPObject property names exposed by the plugin. + static const NPUTF8* + plugin_property_identifier_names_[]; + + virtual void OnFinalMessage(HWND window); + + // Helper function to invoke a function on a NPObject. + bool InvokeDefault(NPObject* object, const std::string& param, + NPVariant* result); + + bool InvokeDefault(NPObject* object, const NPVariant& param, + NPVariant* result); + + bool InvokeDefault(NPObject* object, unsigned param_count, + const NPVariant* params, NPVariant* result); + + // Helper function to convert javascript code to a NPObject we can + // invoke on. + virtual NPObject* JavascriptToNPObject(const std::string& function_name); + + // Helper function to execute a script. + // Returns S_OK on success. + bool ExecuteScript(const std::string& script, NPVariant* result); + + // Returns true if the script passed in is a valid function in the DOM. + bool IsValidJavascriptFunction(const std::string& script); + + // Converts the data parameter to an NPVariant and forwards the call to the + // other FireEvent method. + void FireEvent(const std::string& event_type, const std::string& data); + + // Creates an event object, assigns the data parameter to a |data| property + // on the event object and then calls DispatchEvent to fire the event to + // listeners. event_type is the name of the event being fired. + void FireEvent(const std::string& event_type, const NPVariant& data); + + // Returns a new prefs service. Virtual to allow overriding in unittests. + virtual NpProxyService* CreatePrefService(); + + // Returns our associated windows' location. + virtual std::string GetLocation(); + + // Returns true iff we're successfully able to query for the browser's + // incognito mode, and the browser returns true. + virtual bool GetBrowserIncognitoMode(); + + // Returns the window script object for the page. + // This function will cache the window object to avoid calling + // npapi::GetValue which can cause problems in Opera. + NPObject* GetWindowObject() const; + + virtual void SetReadyState(READYSTATE new_state) { + ready_state_ = new_state; + NPVariant var; + INT32_TO_NPVARIANT(ready_state_, var); + FireEvent("readystatechanged", var); + } + + // Host function to compile-time asserts over members of this class. + static void CompileAsserts(); + + // Get request from the stream notify data + NPAPIUrlRequest* RequestFromNotifyData(void* notify_data) const; + + static LRESULT CALLBACK DropKillFocusHook(int code, WPARAM wparam, + LPARAM lparam); // NO_LINT + + // The plugins opaque instance handle + NPP instance_; + + // The plugin instantiation mode (NP_FULL or NP_EMBED) + int16 mode_; + // The plugins mime type. + std::string mime_type_; + + // Set to true if we need a full page plugin. + bool force_full_page_plugin_; + + scoped_refptr<NpProxyService> pref_service_; + + // Used to receive focus and blur events from the object element + // that hosts the plugin. + scoped_refptr<NpEventListener> focus_listener_; + + // In some cases the IPC channel proxy object is instantiated on the UI + // thread in FF. It then tries to use the IPC logger, which relies on + // the message loop being around. Declaring a dummy message loop + // is a hack to get around this. Eventually the automation code needs to + // be fixed to ensure that the channel proxy always gets created on a thread + // with a message loop. + static MessageLoop* message_loop_; + static int instance_count_; + + // The following members contain the NPObject pointers representing the + // onload/onerror/onmessage handlers on the page. + ScopedNpObject<NPObject> onerror_handler_; + ScopedNpObject<NPObject> onmessage_handler_; + ScopedNpObject<NPObject> onprivatemessage_handler_; + + // As a workaround for a problem in Opera we cache the window object. + // The problem stems from two things: window messages aren't always removed + // from the message queue and messages can be pumped inside GetValue. + // This can cause an infinite recursion of processing the same message + // repeatedly. + mutable ScopedNpObject<NPObject> window_object_; + + // Note since 'onload' is a registered event name, the browser will + // automagically create a code block for the handling code and hook it + // up to the CF object via addEventListener. + // See this list of known event types: + // http://www.w3.org/TR/DOM-Level-3-Events/events.html#Event-types + + READYSTATE ready_state_; + + + // Popups are enabled + bool enabled_popups_; + + // The value of src property keeping the current URL. + std::string src_; +}; + +#endif // CHROME_FRAME_CHROME_FRAME_NPAPI_H_ diff --git a/chrome_frame/chrome_frame_npapi.rgs b/chrome_frame/chrome_frame_npapi.rgs new file mode 100644 index 0000000..70e6ca1 --- /dev/null +++ b/chrome_frame/chrome_frame_npapi.rgs @@ -0,0 +1,13 @@ +HKLM {
+ NoRemove Software {
+ NoRemove MozillaPlugins {
+ ForceRemove '@google.com/ChromeFrame,version=1.0' {
+ val Path = s '%MODULE%'
+ val Description = s 'Google ChromeFrame'
+ val ProductName = s 'Google ChromeFrame'
+ val Vendor = s 'Google'
+ val Version = s '%VERSION%'
+ }
+ }
+ }
+}
diff --git a/chrome_frame/chrome_frame_npapi_entrypoints.cc b/chrome_frame/chrome_frame_npapi_entrypoints.cc new file mode 100644 index 0000000..f1eec1c --- /dev/null +++ b/chrome_frame/chrome_frame_npapi_entrypoints.cc @@ -0,0 +1,53 @@ +// Copyright (c) 2009 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. + +#include "chrome_frame/chrome_frame_npapi.h" + +#define NPAPI WINAPI + +// Plugin entry points. +extern "C" { + NPError NPAPI NP_Initialize(NPNetscapeFuncs* browser_funcs); + NPError NPAPI NP_GetEntryPoints(NPPluginFuncs* plugin_funcs); + void NPAPI NP_Shutdown(); +} + +NPError NPAPI NP_Initialize(NPNetscapeFuncs* browser_funcs) { + DLOG(INFO) << __FUNCTION__; + _pAtlModule->Lock(); + npapi::InitializeBrowserFunctions(browser_funcs); + return NPERR_NO_ERROR; +} + +NPError NPAPI NP_GetEntryPoints(NPPluginFuncs* plugin_funcs) { + plugin_funcs->version = (NP_VERSION_MAJOR << 8) | NP_VERSION_MINOR; + plugin_funcs->size = sizeof(plugin_funcs); + plugin_funcs->newp = NPP_New; + plugin_funcs->destroy = NPP_Destroy; + plugin_funcs->setwindow = NPP_SetWindow; + plugin_funcs->newstream = NPP_NewStream; + plugin_funcs->destroystream = NPP_DestroyStream; + plugin_funcs->asfile = NULL; + plugin_funcs->writeready = NPP_WriteReady; + plugin_funcs->write = NPP_Write; + plugin_funcs->print = NPP_Print; + plugin_funcs->event = NULL; + plugin_funcs->urlnotify = NPP_URLNotify; + plugin_funcs->getvalue = NPP_GetValue; + plugin_funcs->setvalue = NPP_SetValue; + return NPERR_NO_ERROR; +} + +void NPAPI NP_Shutdown() { + DLOG(INFO) << __FUNCTION__; + + npapi::UninitializeBrowserFunctions(); + + _pAtlModule->Unlock(); // matches Lock() inside NP_Initialize + + DLOG_IF(ERROR, _pAtlModule->GetLockCount() != 0) + << "Being shut down but still have " << _pAtlModule->GetLockCount() + << " living objects"; +} + diff --git a/chrome_frame/chrome_frame_npapi_unittest.cc b/chrome_frame/chrome_frame_npapi_unittest.cc new file mode 100644 index 0000000..d2c9b4e --- /dev/null +++ b/chrome_frame/chrome_frame_npapi_unittest.cc @@ -0,0 +1,551 @@ +// Copyright (c) 2009 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. + +#include "gtest/gtest.h" +#include "gmock/gmock.h" +#include "chrome_frame/chrome_frame_automation.h" +#include "chrome_frame/chrome_frame_npapi.h" +#include "chrome_frame/ff_privilege_check.h" + + +TEST(ChromeFrameNPAPI, DoesNotCrashOnConstruction) { + ChromeFrameNPAPI* api = new ChromeFrameNPAPI(); + delete api; +} + + +// All mocks in the anonymous namespace. +namespace { + +using ::testing::_; +using ::testing::Eq; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::StrEq; + +// Make mocking privilege test easy. +class MockPrivilegeTest { + public: + MockPrivilegeTest() { + CHECK(current_ == NULL); + current_ = this; + } + ~MockPrivilegeTest() { + CHECK(current_ == this); + current_ = NULL; + } + + MOCK_METHOD1(IsFireFoxPrivilegedInvocation, bool(NPP)); + + static MockPrivilegeTest* current() { return current_; } + + private: + static MockPrivilegeTest* current_; +}; + +MockPrivilegeTest* MockPrivilegeTest::current_ = NULL; + +const char* kMimeType = "application/chromeframe"; +// The default profile name is by default derived from the currently +// running executable's name. +const wchar_t* kDefaultProfileName = L"chrome_frame_unittests"; + + +class MockNPAPI: public ChromeFrameNPAPI { + public: + MockNPAPI() : mock_automation_client_(NULL) {} + + MOCK_METHOD0(CreatePrefService, NpProxyService*()); + + MOCK_METHOD0(GetLocation, std::string()); + MOCK_METHOD0(GetBrowserIncognitoMode, bool()); + + MOCK_METHOD1(JavascriptToNPObject, virtual NPObject*(const std::string&)); + + // Make public for test purposes + void OnAutomationServerReady() { + ChromeFrameNPAPI::OnAutomationServerReady(); + } + + // Neuter this (or it dchecks during testing). + void SetReadyState(READYSTATE new_state) {} + + ChromeFrameAutomationClient* CreateAutomationClient() { + return mock_automation_client_; + } + + ChromeFrameAutomationClient* mock_automation_client_; +}; + +class MockAutomationClient: public ChromeFrameAutomationClient { + public: + MOCK_METHOD6(Initialize, bool(ChromeFrameDelegate*, int, bool, + const std::wstring&, const std::wstring&, + bool)); + MOCK_METHOD1(SetEnableExtensionAutomation, void(bool)); // NOLINT +}; + +class MockProxyService: public NpProxyService { + public: + MOCK_METHOD2(Initialize, bool(NPP instance, ChromeFrameAutomationClient*)); +}; + + +// Test fixture to allow testing the privileged NPAPI APIs +class TestNPAPIPrivilegedApi: public ::testing::Test { + public: + virtual void SetUp() { + memset(&instance, 0, sizeof(instance)); + + // Gets owned & destroyed by mock_api (in the + // ChromeFramePlugin<T>::Uninitialize() function). + mock_automation = new MockAutomationClient; + + mock_api.mock_automation_client_ = mock_automation; + mock_proxy = new MockProxyService; + mock_proxy->AddRef(); + mock_proxy_holder.Attach(mock_proxy); + } + + virtual void TearDown() { + } + + void SetupPrivilegeTest(bool is_incognito, + bool expect_privilege_check, + bool is_privileged, + const std::wstring& profile_name, + const std::wstring& extra_args) { + EXPECT_CALL(mock_api, GetLocation()) + .WillOnce(Return(std::string("http://www.google.com"))); + EXPECT_CALL(mock_api, CreatePrefService()) + .WillOnce(Return(mock_proxy)); + EXPECT_CALL(mock_api, GetBrowserIncognitoMode()) + .WillOnce(Return(is_incognito)); + + EXPECT_CALL(*mock_proxy, Initialize(_, _)).WillRepeatedly(Return(false)); + + EXPECT_CALL(*mock_automation, + Initialize(_, _, true, StrEq(profile_name), StrEq(extra_args), false)) + .WillOnce(Return(true)); + + if (expect_privilege_check) { + EXPECT_CALL(mock_priv, IsFireFoxPrivilegedInvocation(_)) + .WillOnce(Return(is_privileged)); + } else { + EXPECT_CALL(mock_priv, IsFireFoxPrivilegedInvocation(_)) + .Times(0); // Fail if privilege check invoked. + } + } + + public: + MockNPAPI mock_api; + MockAutomationClient* mock_automation; + MockProxyService* mock_proxy; + ScopedNsPtr<nsISupports> mock_proxy_holder; + MockPrivilegeTest mock_priv; + NPP_t instance; +}; + +} // namespace + +// Stub for unittesting. +bool IsFireFoxPrivilegedInvocation(NPP npp) { + MockPrivilegeTest* mock = MockPrivilegeTest::current(); + if (!mock) + return false; + + return mock->IsFireFoxPrivilegedInvocation(npp); +} + +TEST_F(TestNPAPIPrivilegedApi, NoPrivilegeCheckWhenNoArguments) { + SetupPrivilegeTest(false, // Not incognito + false, // Fail if privilege check is invoked. + false, + kDefaultProfileName, + L""); // No extra args to initialize. + + // No arguments, no privilege requested. + EXPECT_TRUE(mock_api.Initialize(const_cast<NPMIMEType>(kMimeType), + &instance, + NP_EMBED, + 0, 0, 0)); +} + +TEST_F(TestNPAPIPrivilegedApi, NoPrivilegeCheckWhenZeroArgument) { + SetupPrivilegeTest(false, // Not incognito + false, // Fail if privilege check is invoked. + false, + kDefaultProfileName, + L""); // No extra args to initialize. + + // Privileged mode explicitly zero. + char* argn = "is_privileged"; + char* argv = "0"; + EXPECT_TRUE(mock_api.Initialize(const_cast<NPMIMEType>(kMimeType), + &instance, + NP_EMBED, + 1, &argn, &argv)); +} + +TEST_F(TestNPAPIPrivilegedApi, NotPrivilegedDoesNotAllowArgsOrProfile) { + SetupPrivilegeTest(false, // Not incognito. + true, // Fail unless privilege check is invoked. + false, // Not privileged. + kDefaultProfileName, + L""); // No extra arguments allowed. + + char* argn[] = { + "privileged_mode", + "chrome_extra_arguments", + "chrome_profile_name", + }; + char *argv[] = { + "1", + "foo", + "bar", + }; + EXPECT_TRUE(mock_api.Initialize(const_cast<NPMIMEType>(kMimeType), + &instance, + NP_EMBED, + arraysize(argn), argn, argv)); +} + +TEST_F(TestNPAPIPrivilegedApi, PrivilegedAllowsArgsAndProfile) { + SetupPrivilegeTest(false, // Not incognito. + true, // Fail unless privilege check is invoked. + true, // Privileged mode. + L"custom_profile_name", // Custom profile expected. + L"-bar=far"); // Extra arguments expected + + // With privileged mode we expect automation to be enabled. + EXPECT_CALL(*mock_automation, SetEnableExtensionAutomation(true)) + .Times(1); + + char* argn[] = { + "privileged_mode", + "chrome_extra_arguments", + "chrome_profile_name", + }; + char *argv[] = { + "1", + "-bar=far", + "custom_profile_name", + }; + EXPECT_TRUE(mock_api.Initialize(const_cast<NPMIMEType>(kMimeType), + &instance, + NP_EMBED, + arraysize(argn), argn, argv)); + + // Since we're mocking out ChromeFrameAutomationClient::Initialize, we need + // to tickle this explicitly. + mock_api.OnAutomationServerReady(); +} + + +namespace { + +static const NPIdentifier kOnPrivateMessageId = + reinterpret_cast<NPIdentifier>(0x100); +static const NPIdentifier kPostPrivateMessageId = + reinterpret_cast<NPIdentifier>(0x100); + + +class MockNetscapeFuncs { + public: + MockNetscapeFuncs() { + CHECK(NULL == current_); + current_ = this; + } + + ~MockNetscapeFuncs() { + CHECK(this == current_); + current_ = NULL; + } + + MOCK_METHOD3(GetValue, NPError(NPP, NPNVariable, void *)); + MOCK_METHOD3(GetStringIdentifiers, void(const NPUTF8 **, + int32_t, + NPIdentifier *)); // NOLINT + MOCK_METHOD1(RetainObject, NPObject*(NPObject*)); // NOLINT + MOCK_METHOD1(ReleaseObject, void(NPObject*)); // NOLINT + + + void GetPrivilegedStringIdentifiers(const NPUTF8 **names, + int32_t name_count, + NPIdentifier *identifiers) { + for (int32_t i = 0; i < name_count; ++i) { + if (0 == strcmp(names[i], "onprivatemessage")) { + identifiers[i] = kOnPrivateMessageId; + } else if (0 == strcmp(names[i], "postPrivateMessage")) { + identifiers[i] = kPostPrivateMessageId; + } else { + identifiers[i] = 0; + } + } + } + + static const NPNetscapeFuncs* netscape_funcs() { + return &netscape_funcs_; + } + + private: + static NPError MockGetValue(NPP instance, + NPNVariable variable, + void *ret_value) { + DCHECK(current_); + return current_->GetValue(instance, variable, ret_value); + } + + static void MockGetStringIdentifiers(const NPUTF8 **names, + int32_t name_count, + NPIdentifier *identifiers) { + DCHECK(current_); + return current_->GetStringIdentifiers(names, name_count, identifiers); + } + + static NPObject* MockRetainObject(NPObject* obj) { + DCHECK(current_); + return current_->RetainObject(obj); + } + + static void MockReleaseObject(NPObject* obj) { + DCHECK(current_); + current_->ReleaseObject(obj); + } + + static MockNetscapeFuncs* current_; + static NPNetscapeFuncs netscape_funcs_; +}; + +MockNetscapeFuncs* MockNetscapeFuncs::current_ = NULL; +NPNetscapeFuncs MockNetscapeFuncs::netscape_funcs_ = { + 0, // size + 0, // version + NULL, // geturl + NULL, // posturl + NULL, // requestread + NULL, // newstream + NULL, // write + NULL, // destroystream + NULL, // status + NULL, // uagent + NULL, // memalloc + NULL, // memfree + NULL, // memflush + NULL, // reloadplugins + NULL, // getJavaEnv + NULL, // getJavaPeer + NULL, // geturlnotify + NULL, // posturlnotify + MockGetValue, // getvalue + NULL, // setvalue + NULL, // invalidaterect + NULL, // invalidateregion + NULL, // forceredraw + NULL, // getstringidentifier + MockGetStringIdentifiers, // getstringidentifiers + NULL, // getintidentifier + NULL, // identifierisstring + NULL, // utf8fromidentifier + NULL, // intfromidentifier + NULL, // createobject + MockRetainObject, // retainobject + MockReleaseObject, // releaseobject + NULL, // invoke + NULL, // invokeDefault + NULL, // evaluate + NULL, // getproperty + NULL, // setproperty + NULL, // removeproperty + NULL, // hasproperty + NULL, // hasmethod + NULL, // releasevariantvalue + NULL, // setexception + NULL, // pushpopupsenabledstate + NULL, // poppopupsenabledstate + NULL, // enumerate + NULL, // pluginthreadasynccall + NULL, // construct +}; + +NPObject* const kMockNPObject = reinterpret_cast<NPObject*>(0xCafeBabe); + +class TestNPAPIPrivilegedProperty: public TestNPAPIPrivilegedApi { + public: + virtual void SetUp() { + TestNPAPIPrivilegedApi::SetUp(); + npapi::InitializeBrowserFunctions( + const_cast<NPNetscapeFuncs*>(mock_funcs.netscape_funcs())); + + // Expect calls to release and retain objects. + EXPECT_CALL(mock_funcs, RetainObject(kMockNPObject)) + .WillRepeatedly(Return(kMockNPObject)); + EXPECT_CALL(mock_funcs, ReleaseObject(kMockNPObject)) + .WillRepeatedly(Return()); + + // And we should expect SetEnableExtensionAutomation to be called + // for privileged tests. + EXPECT_CALL(*mock_automation, SetEnableExtensionAutomation(true)) + .WillRepeatedly(Return()); + + // Initializes identifiers. + EXPECT_CALL(mock_funcs, GetStringIdentifiers(_, _, _)) + .WillRepeatedly( + Invoke(&mock_funcs, + &MockNetscapeFuncs::GetPrivilegedStringIdentifiers)); + MockNPAPI::InitializeIdentifiers(); + } + + virtual void TearDown() { + npapi::UninitializeBrowserFunctions(); + TestNPAPIPrivilegedApi::TearDown(); + } + + public: + MockNetscapeFuncs mock_funcs; +}; + + +} // namespace + +TEST_F(TestNPAPIPrivilegedProperty, + NonPrivilegedOnPrivateMessageInitializationFails) { + // Attempt setting onprivatemessage when not privileged. + SetupPrivilegeTest(false, // not incognito. + true, // expect privilege check. + false, // not privileged. + kDefaultProfileName, + L""); + + char* on_private_message_str = "onprivatemessage()"; + EXPECT_CALL(mock_api, JavascriptToNPObject(StrEq(on_private_message_str))) + .Times(0); // this should not be called. + + char* argn[] = { + "privileged_mode", + "onprivatemessage", + }; + char* argv[] = { + "1", + on_private_message_str, + }; + EXPECT_TRUE(mock_api.Initialize(const_cast<NPMIMEType>(kMimeType), + &instance, + NP_EMBED, + arraysize(argn), argn, argv)); + // Shouldn't be able to retrieve it. + NPVariant var; + VOID_TO_NPVARIANT(var); + EXPECT_FALSE(mock_api.GetProperty(kOnPrivateMessageId, &var)); + EXPECT_TRUE(NPVARIANT_IS_VOID(var)); + + mock_api.Uninitialize(); +} + +TEST_F(TestNPAPIPrivilegedProperty, + PrivilegedOnPrivateMessageInitializationSucceeds) { + // Set onprivatemessage argument when privileged. + SetupPrivilegeTest(false, // not incognito. + true, // expect privilege check. + true, // privileged. + kDefaultProfileName, + L""); + + char* on_private_message_str = "onprivatemessage()"; + NPObject* on_private_object = kMockNPObject; + EXPECT_CALL(mock_api, JavascriptToNPObject(StrEq(on_private_message_str))) + .WillOnce(Return(on_private_object)); + + char* argn[] = { + "privileged_mode", + "onprivatemessage", + }; + char* argv[] = { + "1", + on_private_message_str, + }; + EXPECT_TRUE(mock_api.Initialize(const_cast<NPMIMEType>(kMimeType), + &instance, + NP_EMBED, + arraysize(argn), argn, argv)); + // The property should have been set, verify that + // we can retrieve it and test it for correct value. + NPVariant var; + VOID_TO_NPVARIANT(var); + EXPECT_TRUE(mock_api.GetProperty(kOnPrivateMessageId, &var)); + EXPECT_TRUE(NPVARIANT_IS_OBJECT(var)); + EXPECT_EQ(kMockNPObject, NPVARIANT_TO_OBJECT(var)); + + mock_api.Uninitialize(); +} + +TEST_F(TestNPAPIPrivilegedProperty, + NonPrivilegedOnPrivateMessageAssignmentFails) { + // Assigning to onprivatemessage when not privileged should fail. + SetupPrivilegeTest(false, // not incognito. + true, // expect privilege check. + false, // not privileged. + kDefaultProfileName, + L""); + + char* argn = "privileged_mode"; + char* argv = "1"; + EXPECT_TRUE(mock_api.Initialize(const_cast<NPMIMEType>(kMimeType), + &instance, + NP_EMBED, + 1, &argn, &argv)); + + NPVariant var = {}; + OBJECT_TO_NPVARIANT(kMockNPObject, var); + // Setting should fail. + EXPECT_FALSE(mock_api.SetProperty(kOnPrivateMessageId, &var)); + + // And so should getting. + NULL_TO_NPVARIANT(var); + EXPECT_FALSE(mock_api.GetProperty(kOnPrivateMessageId, &var)); + + mock_api.Uninitialize(); +} + +TEST_F(TestNPAPIPrivilegedProperty, + PrivilegedOnPrivateMessageAssignmentSucceeds) { + // Assigning to onprivatemessage when privileged should succeed. + SetupPrivilegeTest(false, // not incognito. + true, // expect privilege check. + true, // privileged. + kDefaultProfileName, + L""); + + char* argn = "privileged_mode"; + char* argv = "1"; + EXPECT_TRUE(mock_api.Initialize(const_cast<NPMIMEType>(kMimeType), + &instance, + NP_EMBED, + 1, &argn, &argv)); + + NPVariant var = {}; + VOID_TO_NPVARIANT(var); + // Getting the property when NULL fails under current implementation. + // I shouldn't have thought this is correct behavior, e.g. I should + // have thought retrieving the NULL should succeed, but this is consistent + // with how other properties behave. + // TODO(robertshield): investigate and/or fix. + EXPECT_FALSE(mock_api.GetProperty(kOnPrivateMessageId, &var)); + // EXPECT_TRUE(NPVARIANT_IS_OBJECT(var)); + // EXPECT_EQ(NULL, NPVARIANT_TO_OBJECT(var)); + + // Setting the property should succeed. + OBJECT_TO_NPVARIANT(kMockNPObject, var); + EXPECT_TRUE(mock_api.SetProperty(kOnPrivateMessageId, &var)); + + // And fething it should return the value we just set. + VOID_TO_NPVARIANT(var); + EXPECT_TRUE(mock_api.GetProperty(kOnPrivateMessageId, &var)); + EXPECT_TRUE(NPVARIANT_IS_OBJECT(var)); + EXPECT_EQ(kMockNPObject, NPVARIANT_TO_OBJECT(var)); + + mock_api.Uninitialize(); +} + +// TODO(siggi): test invoking postPrivateMessage. diff --git a/chrome_frame/chrome_frame_plugin.h b/chrome_frame/chrome_frame_plugin.h new file mode 100644 index 0000000..b0814bb --- /dev/null +++ b/chrome_frame/chrome_frame_plugin.h @@ -0,0 +1,214 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_FRAME_CHROME_FRAME_PLUGIN_H_ +#define CHROME_FRAME_CHROME_FRAME_PLUGIN_H_ + +#include "base/win_util.h" +#include "chrome_frame/chrome_frame_automation.h" +#include "chrome_frame/utils.h" + +#define IDC_ABOUT_CHROME_FRAME 40018 + +// A class to implement common functionality for all types of +// plugins: NPAPI. ActiveX and ActiveDoc +template <typename T> +class ChromeFramePlugin : public ChromeFrameDelegateImpl { + public: + ChromeFramePlugin() + : ignore_setfocus_(false), + is_privileged_(false) { + } + ~ChromeFramePlugin() { + Uninitialize(); + } + +BEGIN_MSG_MAP(ChromeFrameActivex) + MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus) + MESSAGE_HANDLER(WM_SIZE, OnSize) + MESSAGE_HANDLER(WM_PARENTNOTIFY, OnParentNotify) +END_MSG_MAP() + + bool Initialize() { + DCHECK(!automation_client_.get()); + automation_client_.reset(CreateAutomationClient()); + if (!automation_client_.get()) { + NOTREACHED() << "new ChromeFrameAutomationClient"; + return false; + } + + return true; + } + + void Uninitialize() { + if (automation_client_.get()) { + automation_client_->Uninitialize(); + automation_client_.reset(); + } + } + + bool InitializeAutomation(const std::wstring& profile_name, + const std::wstring& extra_chrome_arguments, + bool incognito) { + // We don't want to do incognito when privileged, since we're + // running in browser chrome or some other privileged context. + bool incognito_mode = !is_privileged_ && incognito; + return automation_client_->Initialize(this, kCommandExecutionTimeout, true, + profile_name, extra_chrome_arguments, + incognito_mode); + } + + // ChromeFrameDelegate implementation + virtual WindowType GetWindow() const { + return (static_cast<const T*>(this))->m_hWnd; + } + + virtual void GetBounds(RECT* bounds) { + if (bounds) { + if (::IsWindow(GetWindow())) { + (static_cast<T*>(this))->GetClientRect(bounds); + } + } + } + virtual std::string GetDocumentUrl() { + return document_url_; + } + virtual void OnAutomationServerReady() { + // Issue the extension automation request if we're privileged to + // allow this control to handle extension requests from Chrome. + if (is_privileged_) + automation_client_->SetEnableExtensionAutomation(true); + } + + virtual bool IsValid() const { + return automation_client_.get() != NULL; + } + + protected: + virtual void OnNavigationFailed(int tab_handle, int error_code, + const GURL& gurl) { + OnLoadFailed(error_code, gurl.spec()); + } + + virtual void OnHandleContextMenu(int tab_handle, HANDLE menu_handle, + int x_pos, int y_pos, int align_flags) { + if (!menu_handle || !automation_client_.get()) { + NOTREACHED(); + return; + } + + // TrackPopupMenuEx call will fail on IE on Vista running + // in low integrity mode. We DO seem to be able to enumerate the menu + // though, so just clone it and show the copy: + HMENU copy = UtilCloneContextMenu(static_cast<HMENU>(menu_handle)); + if (!copy) + return; + + T* pThis = static_cast<T*>(this); + if (pThis->PreProcessContextMenu(copy)) { + UINT flags = align_flags | TPM_LEFTBUTTON | TPM_RETURNCMD | TPM_RECURSE; + UINT selected = TrackPopupMenuEx(copy, flags, x_pos, y_pos, GetWindow(), + NULL); + if (selected != 0 && !pThis->HandleContextMenuCommand(selected)) { + automation_client_->SendContextMenuCommandToChromeFrame(selected); + } + } + + DestroyMenu(copy); + } + + LRESULT OnSetFocus(UINT message, WPARAM wparam, LPARAM lparam, + BOOL& handled) { // NO_LINT + if (!ignore_setfocus_ && automation_client_ != NULL) { + TabProxy* tab = automation_client_->tab(); + HWND chrome_window = automation_client_->tab_window(); + if (tab && ::IsWindow(chrome_window)) { + DLOG(INFO) << "Setting initial focus"; + tab->SetInitialFocus(win_util::IsShiftPressed()); + } + } + + return 0; + } + + LRESULT OnSize(UINT message, WPARAM wparam, LPARAM lparam, + BOOL& handled) { // NO_LINT + handled = FALSE; + // When we get resized, we need to resize the external tab window too. + if (automation_client_.get()) + automation_client_->Resize(LOWORD(lparam), HIWORD(lparam), + SWP_NOACTIVATE | SWP_NOZORDER); + return 0; + } + + LRESULT OnParentNotify(UINT message, WPARAM wparam, LPARAM lparam, + BOOL& handled) { // NO_LINT + switch (LOWORD(wparam)) { + case WM_LBUTTONDOWN: + case WM_MBUTTONDOWN: + case WM_RBUTTONDOWN: + case WM_XBUTTONDOWN: { + // If we got activated via mouse click on the external tab, + // we need to update the state of this thread and tell the + // browser that we now have the focus. + HWND focus = ::GetFocus(); + HWND plugin_window = GetWindow(); + if (focus != plugin_window && !IsChild(plugin_window, focus)) { + ignore_setfocus_ = true; + SetFocus(plugin_window); + ignore_setfocus_ = false; + } + break; + } + } + + return 0; + } + + // Return true if context menu should be displayed. The menu could be + // modified as well (enable/disable commands, add/remove items). + // Override in most-derived class if needed. + bool PreProcessContextMenu(HMENU menu) { + // Add an "About" item. + // TODO: The string should be localized and menu should + // be modified in ExternalTabContainer:: once we go public. + AppendMenu(menu, MF_STRING, IDC_ABOUT_CHROME_FRAME, + L"About Chrome Frame..."); + return true; + } + + // Return true if menu command is processed, otherwise the command will be + // passed to Chrome for execution. Override in most-derived class if needed. + bool HandleContextMenuCommand(UINT cmd) { + return false; + } + + // Allow overriding the type of automation client used, for unit tests. + virtual ChromeFrameAutomationClient* CreateAutomationClient() { + return new ChromeFrameAutomationClient; + } + + protected: + // Our gateway to chrome land + scoped_ptr<ChromeFrameAutomationClient> automation_client_; + + // Url of the containing document. + std::string document_url_; + + // We set this flag when we're taking the focus ourselves + // and notifying the host browser that we're doing so. + // When the flag is not set, we transfer the focus to chrome. + bool ignore_setfocus_; + + // The plugin is privileged if it is: + // * Invoked by a window running under the system principal in FireFox. + // * Being hosted by a custom host exposing the SID_ChromeFramePrivileged + // service. + // + // When privileged, additional interfaces are made available to the user. + bool is_privileged_; +}; + +#endif // CHROME_FRAME_CHROME_FRAME_PLUGIN_H_ + diff --git a/chrome_frame/chrome_frame_unittest_main.cc b/chrome_frame/chrome_frame_unittest_main.cc new file mode 100644 index 0000000..70157a4 --- /dev/null +++ b/chrome_frame/chrome_frame_unittest_main.cc @@ -0,0 +1,48 @@ +// Copyright 2009, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <atlbase.h> +#include <atlcom.h> +#include "base/at_exit.h" +#include "base/command_line.h" +#include "gtest/gtest.h" + +class ObligatoryModule: public CAtlExeModuleT<ObligatoryModule> { +}; + +ObligatoryModule g_obligatory_atl_module; + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + + base::AtExitManager at_exit_manager; + CommandLine::Init(argc, argv); + + RUN_ALL_TESTS(); +} diff --git a/chrome_frame/chrome_launcher.cc b/chrome_frame/chrome_launcher.cc new file mode 100644 index 0000000..7670aec --- /dev/null +++ b/chrome_frame/chrome_launcher.cc @@ -0,0 +1,125 @@ +// Copyright (c) 2009 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.
+
+#include "chrome_frame/chrome_launcher.h"
+
+#include "base/base_switches.h"
+#include "base/command_line.h"
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome_frame/chrome_frame_automation.h"
+#include "chrome_frame/crash_report.h"
+
+namespace chrome_launcher {
+
+const wchar_t kLauncherExeBaseName[] = L"chrome_launcher.exe";
+
+// These are the switches we will allow (along with their values) in the
+// safe-for-Low-Integrity version of the Chrome command line.
+const wchar_t* kAllowedSwitches[] = {
+ switches::kAutomationClientChannelID,
+ switches::kDisableMetrics,
+ switches::kNoFirstRun,
+ switches::kUserDataDir,
+ switches::kLoadExtension,
+};
+
+CommandLine* CreateLaunchCommandLine() {
+ // TODO(joi) As optimization, could launch Chrome directly when running at
+ // medium integrity. (Requires bringing in code to read SIDs, etc.)
+
+ // The launcher EXE will be in the same directory as the npchrome_tab DLL,
+ // so create a full path to it based on this assumption. Since our unit
+ // tests also use this function, and live in the directory above, we test
+ // existence of the file and try the path that includes the /servers/
+ // directory if needed.
+ FilePath module_path;
+ if (PathService::Get(base::FILE_MODULE, &module_path)) {
+ FilePath current_dir = module_path.DirName();
+ FilePath same_dir_path = current_dir.Append(kLauncherExeBaseName);
+ if (file_util::PathExists(same_dir_path)) {
+ return new CommandLine(same_dir_path.ToWStringHack());
+ } else {
+ FilePath servers_path =
+ current_dir.Append(L"servers").Append(kLauncherExeBaseName);
+ DCHECK(file_util::PathExists(servers_path)) <<
+ "What module is this? It's not in 'servers' or main output dir.";
+ return new CommandLine(servers_path.ToWStringHack());
+ }
+ } else {
+ NOTREACHED();
+ return NULL;
+ }
+}
+
+void SanitizeCommandLine(const CommandLine& original, CommandLine* sanitized) {
+ int num_sanitized_switches = 0;
+ for (int i = 0; i < arraysize(kAllowedSwitches); ++i) {
+ const wchar_t* current_switch = kAllowedSwitches[i];
+ if (original.HasSwitch(current_switch)) {
+ ++num_sanitized_switches;
+ std::wstring switch_value = original.GetSwitchValue(current_switch);
+ if (0 == switch_value.length()) {
+ sanitized->AppendSwitch(current_switch);
+ } else {
+ sanitized->AppendSwitchWithValue(current_switch, switch_value);
+ }
+ }
+ }
+ if (num_sanitized_switches != original.GetSwitchCount()) {
+ NOTREACHED();
+ LOG(ERROR) << "Original command line from Low Integrity had switches "
+ << "that are not on our whitelist.";
+ }
+}
+
+bool SanitizeAndLaunchChrome(const wchar_t* command_line) {
+ std::wstring command_line_with_program(L"dummy.exe ");
+ command_line_with_program += command_line;
+ CommandLine original(L"");
+ original.ParseFromString(command_line_with_program);
+ CommandLine sanitized(GetChromeExecutablePath());
+ SanitizeCommandLine(original, &sanitized);
+
+ return base::LaunchApp(sanitized.command_line_string(), false, false, NULL);
+}
+
+std::wstring GetChromeExecutablePath() {
+ std::wstring cur_path;
+ PathService::Get(base::DIR_MODULE, &cur_path);
+ file_util::AppendToPath(&cur_path, chrome::kBrowserProcessExecutableName);
+
+ // The installation model for Chrome places the DLLs in a versioned
+ // sub-folder one down from the Chrome executable. If we fail to find
+ // chrome.exe in the current path, try looking one up and launching that
+ // instead.
+ if (!file_util::PathExists(cur_path)) {
+ PathService::Get(base::DIR_MODULE, &cur_path);
+ file_util::UpOneDirectory(&cur_path);
+ file_util::AppendToPath(&cur_path, chrome::kBrowserProcessExecutableName);
+ }
+
+ return cur_path;
+}
+
+} // namespace chrome_launcher
+
+// Entrypoint that implements the logic of chrome_launcher.exe.
+int CALLBACK CfLaunchChrome() {
+ if (chrome_launcher::SanitizeAndLaunchChrome(::GetCommandLine())) {
+ return ERROR_SUCCESS;
+ } else {
+ return ERROR_OPEN_FAILED;
+ }
+}
+
+// Compile-time check to see that the type CfLaunchChromeProc is correct.
+#ifndef NODEBUG
+namespace {
+chrome_launcher::CfLaunchChromeProc cf_launch_chrome = CfLaunchChrome;
+} // namespace
+#endif // NODEBUG
diff --git a/chrome_frame/chrome_launcher.h b/chrome_frame/chrome_launcher.h new file mode 100644 index 0000000..72b198a --- /dev/null +++ b/chrome_frame/chrome_launcher.h @@ -0,0 +1,45 @@ +// Copyright (c) 2009 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.
+
+#ifndef CHROME_FRAME_CHROME_LAUNCHER_H_
+#define CHROME_FRAME_CHROME_LAUNCHER_H_
+
+#include <string>
+
+class CommandLine;
+
+namespace chrome_launcher {
+
+// The base name of the chrome_launcher.exe file.
+extern const wchar_t kLauncherExeBaseName[];
+
+// Creates a command line suitable for launching Chrome. You can add any
+// flags needed before launching.
+//
+// The command-line may use the Chrome executable directly, or use an in-between
+// process if needed for security/elevation purposes. You must delete the
+// returned command line.
+CommandLine* CreateLaunchCommandLine();
+
+// Fills in a new command line from the flags on this process's command line
+// that we are allowing Low Integrity to invoke.
+//
+// Logs a warning for any flags that were passed that are not allowed to be
+// invoked by Low Integrity.
+void SanitizeCommandLine(const CommandLine& original, CommandLine* sanitized);
+
+// Given a command-line without an initial program part, launch our associated
+// chrome.exe with a sanitized version of that command line. Returns true iff
+// successful.
+bool SanitizeAndLaunchChrome(const wchar_t* command_line);
+
+// Returns the full path to the Chrome executable.
+std::wstring GetChromeExecutablePath();
+
+// The type of the CfLaunchChrome entrypoint exported from this DLL.
+typedef int (__stdcall *CfLaunchChromeProc)();
+
+} // namespace chrome_launcher
+
+#endif // CHROME_FRAME_CHROME_LAUNCHER_H_
diff --git a/chrome_frame/chrome_launcher_main.cc b/chrome_frame/chrome_launcher_main.cc new file mode 100644 index 0000000..5347f5a --- /dev/null +++ b/chrome_frame/chrome_launcher_main.cc @@ -0,0 +1,79 @@ +// Copyright (c) 2009 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.
+
+#include <windows.h>
+
+#include "chrome_frame/chrome_launcher.h"
+
+// We want to keep this EXE tiny, so we avoid all dependencies and link to no
+// libraries, and we do not use the C runtime.
+//
+// To catch errors in debug builds, we define an extremely simple assert macro.
+#ifndef NDEBUG
+#define CLM_ASSERT(x) do { if (!(x)) { ::DebugBreak(); } } while (false)
+#else
+#define CLM_ASSERT(x)
+#endif // NDEBUG
+
+// In release builds, we skip the standard library completely to minimize
+// size. This is more work in debug builds, and unnecessary, hence the
+// different signatures.
+#ifndef NDEBUG
+int APIENTRY wWinMain(HINSTANCE, HINSTANCE, wchar_t*, int) {
+#else
+extern "C" void __cdecl WinMainCRTStartup() {
+#endif // NDEBUG
+ // This relies on the chrome_launcher.exe residing in the same directory
+ // as our DLL. We build a full path to avoid loading it from any other
+ // directory in the DLL search path.
+ //
+ // The code is a bit verbose because we can't use the standard library.
+ const wchar_t kBaseName[] = L"npchrome_tab.dll";
+ wchar_t file_path[MAX_PATH + (sizeof(kBaseName) / sizeof(kBaseName[0])) + 1];
+ file_path[0] = L'\0';
+ ::GetModuleFileName(::GetModuleHandle(NULL), file_path, MAX_PATH);
+
+ // Find index of last slash, and null-terminate the string after it.
+ //
+ // Proof for security purposes, since we can't use the safe string
+ // manipulation functions from the runtime:
+ // - File_path is always null-terminated, by us initially and by
+ // ::GetModuleFileName if it puts anything into the buffer.
+ // - If there is no slash in the path then it's a relative path, not an
+ // absolute one, and the code ends up creating a relative path to
+ // npchrome_tab.dll.
+ // - It's safe to use lstrcatW since we know the maximum length of both
+ // parts we are concatenating, and we know the buffer will fit them in
+ // the worst case.
+ int slash_index = lstrlenW(file_path);
+ // Invariant: 0 <= slash_index < MAX_PATH
+ CLM_ASSERT(slash_index > 0);
+ while (slash_index > 0 && file_path[slash_index] != L'\\')
+ --slash_index;
+ // Invariant: 0 <= slash_index < MAX_PATH and it is either the index of
+ // the last \ in the path, or 0.
+ if (slash_index != 0)
+ ++slash_index; // don't remove the last '\'
+ file_path[slash_index] = L'\0';
+
+ lstrcatW(file_path, kBaseName);
+
+ UINT exit_code = ERROR_FILE_NOT_FOUND;
+ HMODULE chrome_tab = ::LoadLibrary(file_path);
+ CLM_ASSERT(chrome_tab);
+ if (chrome_tab) {
+ chrome_launcher::CfLaunchChromeProc proc =
+ reinterpret_cast<chrome_launcher::CfLaunchChromeProc>(
+ ::GetProcAddress(chrome_tab, "CfLaunchChrome"));
+ CLM_ASSERT(proc);
+ if (proc) {
+ exit_code = proc();
+ } else {
+ exit_code = ERROR_INVALID_FUNCTION;
+ }
+
+ ::FreeLibrary(chrome_tab);
+ }
+ ::ExitProcess(exit_code);
+}
diff --git a/chrome_frame/chrome_launcher_unittest.cc b/chrome_frame/chrome_launcher_unittest.cc new file mode 100644 index 0000000..1181f09 --- /dev/null +++ b/chrome_frame/chrome_launcher_unittest.cc @@ -0,0 +1,49 @@ +// Copyright (c) 2009 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.
+
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome_frame/chrome_launcher.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+// Utility class to disable logging. Required to disable DCHECKs that some
+// of our tests would otherwise trigger.
+class LogDisabler {
+ public:
+ LogDisabler() {
+ initial_log_level_ = logging::GetMinLogLevel();
+ logging::SetMinLogLevel(logging::LOG_FATAL + 1);
+ }
+
+ ~LogDisabler() {
+ logging::SetMinLogLevel(initial_log_level_);
+ }
+
+ private:
+ int initial_log_level_;
+};
+
+} // namespace
+
+TEST(ChromeLauncher, SanitizeCommandLine) {
+ CommandLine bad(L"dummy.exe");
+ bad.AppendSwitch(switches::kDisableMetrics); // in whitelist
+ bad.AppendSwitchWithValue(switches::kLoadExtension, L"foo"); // in whitelist
+ bad.AppendSwitch(L"no-such-switch"); // does not exist
+ bad.AppendSwitch(switches::kHomePage); // exists but not in whitelist
+
+ LogDisabler no_dchecks;
+
+ CommandLine sanitized(L"dumbo.exe");
+ chrome_launcher::SanitizeCommandLine(bad, &sanitized);
+ EXPECT_TRUE(sanitized.HasSwitch(switches::kDisableMetrics));
+ EXPECT_TRUE(sanitized.HasSwitch(switches::kLoadExtension));
+ EXPECT_FALSE(sanitized.HasSwitch(L"no-such-switch"));
+ EXPECT_FALSE(sanitized.HasSwitch(switches::kHomePage));
+
+ EXPECT_EQ(sanitized.GetSwitchValue(switches::kLoadExtension), L"foo");
+}
diff --git a/chrome_frame/chrome_protocol.cc b/chrome_frame/chrome_protocol.cc new file mode 100644 index 0000000..87ca34e --- /dev/null +++ b/chrome_frame/chrome_protocol.cc @@ -0,0 +1,85 @@ +// Copyright (c) 2009 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. + +// Implementation of ChromeProtocol +#include "chrome_frame/chrome_protocol.h" + +#include "base/logging.h" + +static const wchar_t* kChromeMimeType = L"application/chromepage"; + +// ChromeProtocol + +// Starts the class associated with the asynchronous pluggable protocol. +STDMETHODIMP ChromeProtocol::Start(LPCWSTR url, + IInternetProtocolSink* prot_sink, + IInternetBindInfo* bind_info, + DWORD flags, + DWORD reserved) { + DLOG(INFO) << __FUNCTION__ << ": URL = " << url; + prot_sink->ReportProgress(BINDSTATUS_VERIFIEDMIMETYPEAVAILABLE, + kChromeMimeType); + prot_sink->ReportData( + BSCF_FIRSTDATANOTIFICATION | BSCF_LASTDATANOTIFICATION | + BSCF_DATAFULLYAVAILABLE, + 0, + 0); + return S_OK; +} + +// Allows the pluggable protocol handler to continue processing data on the +// apartment (or user interface) thread. This method is called in response +// to a call to IInternetProtocolSink::Switch. +STDMETHODIMP ChromeProtocol::Continue(PROTOCOLDATA* protocol_data) { + DLOG(INFO) << __FUNCTION__; + return S_OK; +} + +// Aborts an operation in progress. +STDMETHODIMP ChromeProtocol::Abort(HRESULT reason, DWORD options) { + DLOG(INFO) << __FUNCTION__; + return S_OK; +} + +STDMETHODIMP ChromeProtocol::Terminate(DWORD options) { + DLOG(INFO) << __FUNCTION__; + return S_OK; +} + +STDMETHODIMP ChromeProtocol::Suspend() { + return E_NOTIMPL; +} +STDMETHODIMP ChromeProtocol::Resume() { + return E_NOTIMPL; +} + +// Reads data retrieved by the pluggable protocol handler. +STDMETHODIMP ChromeProtocol::Read(void* buffer, + ULONG buffer_size_in_bytes, + ULONG* bytes_read) { + DLOG(INFO) << __FUNCTION__; + return S_FALSE; +} + +// Moves the current seek offset. +STDMETHODIMP ChromeProtocol::Seek(LARGE_INTEGER move_by, + DWORD origin, + ULARGE_INTEGER* new_position) { + DLOG(INFO) << __FUNCTION__; + return E_NOTIMPL; +} + +// Locks the request so that IInternetProtocolRoot::Terminate () +// can be called and the remaining data can be read. +STDMETHODIMP ChromeProtocol::LockRequest(DWORD options) { + DLOG(INFO) << __FUNCTION__; + return S_OK; +} + +// Frees any resources associated with a lock. Called only if +// IInternetProtocol::LockRequest () was called. +STDMETHODIMP ChromeProtocol::UnlockRequest() { + DLOG(INFO) << __FUNCTION__; + return S_OK; +} diff --git a/chrome_frame/chrome_protocol.h b/chrome_frame/chrome_protocol.h new file mode 100644 index 0000000..751162b --- /dev/null +++ b/chrome_frame/chrome_protocol.h @@ -0,0 +1,65 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_FRAME_CHROME_PROTOCOL_H_ +#define CHROME_FRAME_CHROME_PROTOCOL_H_ + +#include <atlbase.h> +#include <atlcom.h> +#include "chrome_frame/resource.h" +#include "grit/chrome_frame_resources.h" + +// Include without path to make GYP build see it. +#include "chrome_tab.h" // NOLINT + +// ChromeProtocol +class ATL_NO_VTABLE ChromeProtocol + : public CComObjectRootEx<CComSingleThreadModel>, + public CComCoClass<ChromeProtocol, &CLSID_ChromeProtocol>, + public IInternetProtocol { + public: + ChromeProtocol() { + } + DECLARE_REGISTRY_RESOURCEID(IDR_CHROMEPROTOCOL) + + BEGIN_COM_MAP(ChromeProtocol) + COM_INTERFACE_ENTRY(IInternetProtocol) + COM_INTERFACE_ENTRY(IInternetProtocolRoot) + END_COM_MAP() + + DECLARE_PROTECT_FINAL_CONSTRUCT() + + HRESULT FinalConstruct() { + return S_OK; + } + void FinalRelease() { + } + + public: + // IInternetProtocolRoot + STDMETHOD(Start)(LPCWSTR url, + IInternetProtocolSink* prot_sink, + IInternetBindInfo* bind_info, + DWORD flags, + DWORD reserved); + STDMETHOD(Continue)(PROTOCOLDATA* protocol_data); + STDMETHOD(Abort)(HRESULT reason, DWORD options); + STDMETHOD(Terminate)(DWORD options); + STDMETHOD(Suspend)(); + STDMETHOD(Resume)(); + + // IInternetProtocol based on IInternetProtocolRoot + STDMETHOD(Read)(void* buffer, + ULONG buffer_size_in_bytes, + ULONG* bytes_read); + STDMETHOD(Seek)(LARGE_INTEGER move_by, + DWORD origin, + ULARGE_INTEGER* new_position); + STDMETHOD(LockRequest)(DWORD options); + STDMETHOD(UnlockRequest)(void); +}; + +OBJECT_ENTRY_AUTO(__uuidof(ChromeProtocol), ChromeProtocol) + +#endif // CHROME_FRAME_CHROME_PROTOCOL_H_ diff --git a/chrome_frame/chrome_protocol.rgs b/chrome_frame/chrome_protocol.rgs new file mode 100644 index 0000000..747d7b5 --- /dev/null +++ b/chrome_frame/chrome_protocol.rgs @@ -0,0 +1,39 @@ +HKLM {
+ NoRemove Software {
+ NoRemove Classes {
+ ChromeTab.ChromeProtocol.1 = s 'ChromeProtocol Class' {
+ CLSID = s '{9875BFAF-B04D-445E-8A69-BE36838CDE3E}'
+ }
+ ChromeTab.ChromeProtocol = s 'ChromeProtocol Class' {
+ CLSID = s '{9875BFAF-B04D-445E-8A69-BE36838CDE3E}'
+ CurVer = s 'ChromeTab.ChromeProtocol.1'
+ }
+ NoRemove CLSID {
+ ForceRemove {9875BFAF-B04D-445E-8A69-BE36838CDE3E} = s 'ChromeProtocol Class' {
+ ProgID = s 'ChromeTab.ChromeProtocol.1'
+ VersionIndependentProgID = s 'ChromeTab.ChromeProtocol'
+ ForceRemove 'Programmable'
+ InprocServer32 = s '%MODULE%' {
+ val ThreadingModel = s 'Apartment'
+ }
+ val AppID = s '%APPID%'
+ 'TypeLib' = s '{6F2664E1-FF6E-488A-BCD1-F4CA6001DFCC}'
+ }
+ }
+ }
+ }
+}
+
+HKLM {
+ NoRemove Software {
+ NoRemove Classes {
+ NoRemove Protocols {
+ NoRemove Handler {
+ NoRemove 'cf' {
+ val CLSID = s '{9875BFAF-B04D-445E-8A69-BE36838CDE3E}'
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/chrome_frame/chrome_tab.cc b/chrome_frame/chrome_tab.cc new file mode 100644 index 0000000..1b10ff6 --- /dev/null +++ b/chrome_frame/chrome_tab.cc @@ -0,0 +1,308 @@ +// Copyright (c) 2009 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. + +// chrome_tab.cc : Implementation of DLL Exports. +#include "base/at_exit.h" +#include "base/command_line.h" +#include "base/file_util.h" +#include "base/file_version_info.h" +#include "base/logging.h" +#include "base/path_service.h" +#include "base/registry.h" +#include "base/string_piece.h" +#include "base/string_util.h" +#include "base/sys_string_conversions.h" +#include "chrome/common/chrome_constants.h" +#include "grit/chrome_frame_resources.h" +#include "chrome_frame/bho.h" +#include "chrome_frame/chrome_frame_automation.h" +#include "chrome_frame/chrome_launcher.h" +#include "chrome_frame/crash_report.h" +#include "chrome_frame/resource.h" +#include "chrome_frame/utils.h" + +// Include without path to make GYP build see it. +#include "chrome_tab.h" // NOLINT + +static const wchar_t kBhoRegistryPath[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer" + L"\\Browser Helper Objects"; + +const wchar_t kBhoNoLoadExplorerValue[] = L"NoExplorer"; + +class ChromeTabModule + : public AtlPerUserModule<CAtlDllModuleT<ChromeTabModule> > { + public: + typedef AtlPerUserModule<CAtlDllModuleT<ChromeTabModule> > ParentClass; + + DECLARE_LIBID(LIBID_ChromeTabLib) + DECLARE_REGISTRY_APPID_RESOURCEID(IDR_CHROMETAB, + "{FD9B1B31-F4D8-436A-8F4F-D3C2E36733D3}") + + // Override to add our SYSTIME binary value to registry scripts. + // See chrome_frame_activex.rgs for usage. + virtual HRESULT AddCommonRGSReplacements(IRegistrarBase* registrar) throw() { + HRESULT hr = ParentClass::AddCommonRGSReplacements(registrar); + + if (SUCCEEDED(hr)) { + SYSTEMTIME local_time; + ::GetSystemTime(&local_time); + std::string hex(HexEncode(&local_time, sizeof(local_time))); + base::StringPiece sp_hex(hex); + hr = registrar->AddReplacement(L"SYSTIME", + base::SysNativeMBToWide(sp_hex).c_str()); + DCHECK(SUCCEEDED(hr)); + } + + if (SUCCEEDED(hr)) { + std::wstring app_path( + chrome_launcher::GetChromeExecutablePath()); + app_path = file_util::GetDirectoryFromPath(app_path); + hr = registrar->AddReplacement(L"CHROME_APPPATH", app_path.c_str()); + DCHECK(SUCCEEDED(hr)); + } + + if (SUCCEEDED(hr)) { + hr = registrar->AddReplacement(L"CHROME_APPNAME", + chrome::kBrowserProcessExecutableName); + DCHECK(SUCCEEDED(hr)); + + // Fill in VERSION from the VERSIONINFO stored in the DLL's resources. + scoped_ptr<FileVersionInfo> module_version_info( + FileVersionInfo::CreateFileVersionInfoForCurrentModule()); + DCHECK(module_version_info != NULL); + std::wstring file_version(module_version_info->file_version()); + hr = registrar->AddReplacement(L"VERSION", file_version.c_str()); + DCHECK(SUCCEEDED(hr)); + } + + if (SUCCEEDED(hr)) { + // Add the directory of chrome_launcher.exe. This will be the same + // as the directory for the current DLL. + std::wstring module_dir; + FilePath module_path; + if (PathService::Get(base::FILE_MODULE, &module_path)) { + module_dir = module_path.DirName().ToWStringHack(); + } else { + NOTREACHED(); + } + hr = registrar->AddReplacement(L"CHROME_LAUNCHER_APPPATH", + module_dir.c_str()); + DCHECK(SUCCEEDED(hr)); + } + + if (SUCCEEDED(hr)) { + // Add the filename of chrome_launcher.exe + hr = registrar->AddReplacement(L"CHROME_LAUNCHER_APPNAME", + chrome_launcher::kLauncherExeBaseName); + DCHECK(SUCCEEDED(hr)); + } + + return hr; + } +}; + +ChromeTabModule _AtlModule; + +base::AtExitManager* g_exit_manager = NULL; + +// DLL Entry Point +extern "C" BOOL WINAPI DllMain(HINSTANCE instance, + DWORD reason, + LPVOID reserved) { + UNREFERENCED_PARAMETER(instance); + if (reason == DLL_PROCESS_ATTACH) { +#ifndef NDEBUG + // Silence traces from the ATL registrar to reduce the log noise. + ATL::CTrace::s_trace.ChangeCategory(atlTraceRegistrar, 0, + ATLTRACESTATUS_DISABLED); +#endif + InitializeCrashReporting(false, false); + g_exit_manager = new base::AtExitManager(); + CommandLine::Init(0, NULL); + logging::InitLogging(NULL, logging::LOG_ONLY_TO_SYSTEM_DEBUG_LOG, + logging::LOCK_LOG_FILE, logging::DELETE_OLD_LOG_FILE); + } else if (reason == DLL_PROCESS_DETACH) { + g_patch_helper.UnpatchIfNeeded(); + delete g_exit_manager; + g_exit_manager = NULL; + ShutdownCrashReporting(); + } + return _AtlModule.DllMain(reason, reserved); +} + +#ifdef _MANAGED +#pragma managed(pop) +#endif + +const wchar_t kPostPlatformUAKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\\" + L"User Agent\\Post Platform"; +const wchar_t kClockUserAgent[] = L"chromeframe"; + +// To delete the clock user agent, set value to NULL. +HRESULT SetClockUserAgent(const wchar_t* value) { + HRESULT hr; + RegKey ua_key; + if (ua_key.Create(HKEY_CURRENT_USER, kPostPlatformUAKey, KEY_WRITE)) { + if (value) { + ua_key.WriteValue(kClockUserAgent, value); + } else { + ua_key.DeleteValue(kClockUserAgent); + } + hr = S_OK; + } else { + DLOG(ERROR) << __FUNCTION__ << ": " << kPostPlatformUAKey; + hr = E_UNEXPECTED; + } + + return hr; +} + +HRESULT RefreshElevationPolicy() { + const wchar_t kIEFrameDll[] = L"ieframe.dll"; + const char kIERefreshPolicy[] = "IERefreshElevationPolicy"; + HRESULT hr = E_NOTIMPL; + HMODULE ieframe_module = LoadLibrary(kIEFrameDll); + if (ieframe_module) { + typedef HRESULT (__stdcall *IERefreshPolicy)(); + IERefreshPolicy ie_refresh_policy = reinterpret_cast<IERefreshPolicy>( + GetProcAddress(ieframe_module, kIERefreshPolicy)); + + if (ie_refresh_policy) { + hr = ie_refresh_policy(); + } else { + hr = HRESULT_FROM_WIN32(GetLastError()); + } + + FreeLibrary(ieframe_module); + } else { + hr = HRESULT_FROM_WIN32(GetLastError()); + } + + return hr; +} + +HRESULT RegisterChromeTabBHO() { + RegKey ie_bho_key; + if (!ie_bho_key.Create(HKEY_LOCAL_MACHINE, kBhoRegistryPath, + KEY_CREATE_SUB_KEY)) { + DLOG(WARNING) << "Failed to open registry key " + << kBhoRegistryPath + << " for write"; + return E_FAIL; + } + + wchar_t bho_class_id_as_string[MAX_PATH] = {0}; + StringFromGUID2(CLSID_ChromeFrameBHO, bho_class_id_as_string, + arraysize(bho_class_id_as_string)); + + if (!ie_bho_key.CreateKey(bho_class_id_as_string, KEY_READ | KEY_WRITE)) { + DLOG(WARNING) << "Failed to create bho registry key under " + << kBhoRegistryPath + << " for write"; + return E_FAIL; + } + + ie_bho_key.WriteValue(kBhoNoLoadExplorerValue, 1); + DLOG(INFO) << "Registered ChromeTab BHO"; + + SetClockUserAgent(L"1"); + RefreshElevationPolicy(); + return S_OK; +} + +HRESULT UnregisterChromeTabBHO() { + SetClockUserAgent(NULL); + + RegKey ie_bho_key; + if (!ie_bho_key.Open(HKEY_LOCAL_MACHINE, kBhoRegistryPath, + KEY_READ | KEY_WRITE)) { + DLOG(WARNING) << "Failed to open registry key " + << kBhoRegistryPath + << " for write."; + return E_FAIL; + } + + wchar_t bho_class_id_as_string[MAX_PATH] = {0}; + StringFromGUID2(CLSID_ChromeFrameBHO, bho_class_id_as_string, + arraysize(bho_class_id_as_string)); + + if (!ie_bho_key.DeleteKey(bho_class_id_as_string)) { + DLOG(WARNING) << "Failed to delete bho registry key " + << bho_class_id_as_string + << " under " + << kBhoRegistryPath; + return E_FAIL; + } + + DLOG(INFO) << "Unregistered ChromeTab BHO"; + return S_OK; +} + +// Used to determine whether the DLL can be unloaded by OLE +STDAPI DllCanUnloadNow() { + return _AtlModule.DllCanUnloadNow(); +} + +// Returns a class factory to create an object of the requested type +STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv) { + if (g_patch_helper.state() == PatchHelper::UNKNOWN) { + g_patch_helper.InitializeAndPatchProtocolsIfNeeded(); + UrlMkSetSessionOption(URLMON_OPTION_USERAGENT_REFRESH, NULL, 0, 0); + } + + return _AtlModule.DllGetClassObject(rclsid, riid, ppv); +} + +// DllRegisterServer - Adds entries to the system registry +STDAPI DllRegisterServer() { + // registers object, typelib and all interfaces in typelib + HRESULT hr = _AtlModule.DllRegisterServer(TRUE); + +#ifdef GOOGLE_CHROME_BUILD + // Muck with the Omaha configuration so that we don't get updated by non-CF + // Google Chrome builds. + UtilUpdateOmahaConfig(true); +#endif + + if (SUCCEEDED(hr)) { + // Best effort attempt to register the BHO. At this point we silently + // ignore any errors during registration. There are some traces emitted + // to the debug log. + RegisterChromeTabBHO(); + } + + return hr; +} + +// DllUnregisterServer - Removes entries from the system registry +STDAPI DllUnregisterServer() { + HRESULT hr = _AtlModule.DllUnregisterServer(TRUE); + +#ifdef GOOGLE_CHROME_BUILD + // Undo any prior mucking with the Omaha config. + UtilUpdateOmahaConfig(false); +#endif + + if (SUCCEEDED(hr)) { + // Best effort attempt to unregister the BHO. At this point we silently + // ignore any errors during unregistration. There are some traces emitted + // to the debug log. + UnregisterChromeTabBHO(); + } + return hr; +} + +STDAPI RegisterNPAPIPlugin() { + HRESULT hr = _AtlModule.UpdateRegistryFromResourceS(IDR_CHROMEFRAME_NPAPI, + TRUE); + return hr; +} + +STDAPI UnregisterNPAPIPlugin() { + HRESULT hr = _AtlModule.UpdateRegistryFromResourceS(IDR_CHROMEFRAME_NPAPI, + FALSE); + return hr; +} diff --git a/chrome_frame/chrome_tab.def b/chrome_frame/chrome_tab.def new file mode 100644 index 0000000..ae2ddd2 --- /dev/null +++ b/chrome_frame/chrome_tab.def @@ -0,0 +1,15 @@ +; ChromeTab.def : Declares the module parameters. + +LIBRARY "npchrome_tab.dll" + +EXPORTS + DllCanUnloadNow PRIVATE + DllGetClassObject PRIVATE + DllRegisterServer PRIVATE + DllUnregisterServer PRIVATE + NP_Initialize PRIVATE + NP_GetEntryPoints PRIVATE + NP_Shutdown PRIVATE + CfLaunchChrome PRIVATE + RegisterNPAPIPlugin PRIVATE + UnregisterNPAPIPlugin PRIVATE diff --git a/chrome_frame/chrome_tab.idl b/chrome_frame/chrome_tab.idl new file mode 100644 index 0000000..6f8a2ac --- /dev/null +++ b/chrome_frame/chrome_tab.idl @@ -0,0 +1,153 @@ +// Copyright (c) 2009 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 will be processed by the MIDL tool to +// produce the type library (chrome_tab.tlb) and marshalling code. + +#include "olectl.h" +import "oaidl.idl"; +import "ocidl.idl"; + +[ + object, + uuid(B9F5EA20-C450-4f46-B70F-BFD3CA9A20C5), + dual, + nonextensible, + helpstring("IChromeFrame Interface"), + pointer_default(unique) +] +interface IChromeFrame : IDispatch { + [propget, id(1)] + HRESULT src([out, retval] BSTR* src); + [propput, id(1)] + HRESULT src([in] BSTR src); + + [id(3)] + HRESULT postMessage([in] BSTR message, [in, optional] VARIANT target); + + [id(4), propget] + HRESULT onload([out, retval] VARIANT* onload_handler); + [id(4), propput] + HRESULT onload([in] VARIANT onload_handler); + + [propget, id(5)] + HRESULT onloaderror([out, retval] VARIANT* onerror_handler); + [propput, id(5)] + HRESULT onloaderror([in] VARIANT onerror_handler); + + [propget, id(6)] + HRESULT onmessage([out, retval] VARIANT* onmessage_handler); + [propput, id(6)] + HRESULT onmessage([in] VARIANT onmessage_handler); + + [propget, id(DISPID_READYSTATE)] + HRESULT readyState([out, retval] long* ready_state); + + [id(7)] + HRESULT addEventListener([in] BSTR event_type, [in] IDispatch* listener, + [in, optional] VARIANT use_capture); + + [id(8)] + HRESULT removeEventListener([in] BSTR event_type, [in] IDispatch* listener, + [in, optional] VARIANT use_capture); + + [propget, id(9)] + HRESULT version([out, retval] BSTR* version); + + [id(10), hidden] + // This method is available only when the control is in privileged mode. + HRESULT postPrivateMessage([in] BSTR message, + [in] BSTR origin, + [in] BSTR target); + + [propget, id(11)] + HRESULT useChromeNetwork([out, retval] VARIANT_BOOL* pVal); + [propput, id(11)] + HRESULT useChromeNetwork([in] VARIANT_BOOL newVal); +}; + +[ + object, + uuid(679E292F-DBAB-46b8-8693-03084CEF61BE), + oleautomation, + nonextensible, + hidden, +] +interface IChromeFramePrivileged: IUnknown { + // If the host returns false for wants_privileged, the control + // won't enable privileged mode. + HRESULT GetWantsPrivileged([out] boolean *wants_privileged); + // Extra arguments to supply to the Chrome instance. Returns S_FALSE when + // no extra arguments are needed. Always sets the output string to non-NULL. + HRESULT GetChromeExtraArguments([out] BSTR *args); + // The profile name we want to use. + HRESULT GetChromeProfileName([out] BSTR *profile_name); +}; + +// Expose this service to the ChromeFrame control to trigger privileged +// mode. If the control is in privileged mode, it will forward messages +// to the onmessage handler irrespective of origin. +cpp_quote("#define SID_ChromeFramePrivileged __uuidof(IChromeFramePrivileged)") + +typedef enum { + CF_EVENT_DISPID_ONLOAD = 1, + CF_EVENT_DISPID_ONLOADERROR, + CF_EVENT_DISPID_ONMESSAGE, + CF_EVENT_DISPID_ONPRIVATEMESSAGE, + CF_EVENT_DISPID_ONREADYSTATECHANGED = DISPID_READYSTATECHANGE, +} ChromeFrameEventDispId; + +[ + uuid(6F2664E1-FF6E-488A-BCD1-F4CA6001DFCC), + version(1.0), + helpstring("ChromeTab 1.0 Type Library") +] +library ChromeTabLib { + importlib("stdole2.tlb"); + + [uuid(A96B8A02-DD11-4936-8C0F-B2520289FABB)] + dispinterface DIChromeFrameEvents { + properties: + // None. + + methods: + [id(CF_EVENT_DISPID_ONLOAD)] + void onload(); + [id(CF_EVENT_DISPID_ONLOADERROR)] + void onloaderror(); + [id(CF_EVENT_DISPID_ONMESSAGE)] + void onmessage([in] IDispatch* event); + [id(CF_EVENT_DISPID_ONREADYSTATECHANGED)] + void onreadystatechanged(); + [id(CF_EVENT_DISPID_ONPRIVATEMESSAGE)] + // This event is only fired when the control is in privileged mode. + void onprivatemessage([in] IDispatch* event, [in] BSTR target); + }; + + [uuid(BB1176EE-20DD-41DC-9D1E-AC1335C7BBB0)] + coclass HtmlFilter { + [default] interface IUnknown; + }; + + [uuid(9875BFAF-B04D-445E-8A69-BE36838CDE3E)] + coclass ChromeProtocol { + [default] interface IUnknown; + }; + + [uuid(3E1D0E7F-F5E3-44CC-AA6A-C0A637619AB8), control] + coclass ChromeActiveDocument { + [default] interface IChromeFrame; + }; + + [uuid(E0A900DF-9611-4446-86BD-4B1D47E7DB2A), control] + coclass ChromeFrame { + [default] interface IChromeFrame; + [default, source] dispinterface DIChromeFrameEvents; + }; + + [uuid(ECB3C477-1A0A-44bd-BB57-78F9EFE34FA7)] + coclass ChromeFrameBHO { + [default] interface IUnknown; + }; +}; diff --git a/chrome_frame/chrome_tab.rgs b/chrome_frame/chrome_tab.rgs new file mode 100644 index 0000000..ab4008b --- /dev/null +++ b/chrome_frame/chrome_tab.rgs @@ -0,0 +1,12 @@ +HKLM {
+ NoRemove Software {
+ NoRemove Classes {
+ NoRemove AppID {
+ '%APPID%' = s 'ChromeTab'
+ 'ChromeTab.DLL' {
+ val AppID = s '%APPID%'
+ }
+ }
+ }
+ }
+}
diff --git a/chrome_frame/chrome_tab_version.rc.version b/chrome_frame/chrome_tab_version.rc.version new file mode 100644 index 0000000..84c02f4 --- /dev/null +++ b/chrome_frame/chrome_tab_version.rc.version @@ -0,0 +1,45 @@ +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION @MAJOR@,@MINOR@,@BUILD@,@PATCH@ + PRODUCTVERSION @MAJOR@,@MINOR@,@BUILD@,@PATCH@ + FILEFLAGSMASK 0x17L +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + // Note that Firefox 3.0 requires the charset to be 04e4 (multi-lingual). + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "@COMPANY_FULLNAME@" + VALUE "CompanyShortName", "@COMPANY_SHORTNAME@" + VALUE "ProductName", "Google Chrome Frame" + VALUE "ProductVersion", "@MAJOR@.@MINOR@.@BUILD@.@PATCH@" + VALUE "FileDescription", "Chrome Frame renders the Web of the future in the browsers of the past. It's like strapping a rocket engine to a minivan." + VALUE "FileVersion", "@MAJOR@.@MINOR@.@BUILD@.@PATCH@" + VALUE "InternalName", "Google Chrome Frame" + VALUE "LegalCopyright", "@COPYRIGHT@" + VALUE "MIMEType", "application/chromeframe" + VALUE "FileExtents", "chromeframe" + VALUE "FileOpenName", "chromeframe" + VALUE "OriginalFilename", "npchrome_tab.dll" + VALUE "LastChange", "@LASTCHANGE@" + VALUE "Official Build", "@OFFICIAL_BUILD@" + END + END + BLOCK "VarFileInfo" + BEGIN + // Note that Firefox 3.0 requires the charset to be 1252 (multi-lingual). + VALUE "Translation", 0x409, 1252 + END +END diff --git a/chrome_frame/com_message_event.cc b/chrome_frame/com_message_event.cc new file mode 100644 index 0000000..1e25b87 --- /dev/null +++ b/chrome_frame/com_message_event.cc @@ -0,0 +1,157 @@ +// Copyright (c) 2009 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. + +#include "chrome_frame/com_message_event.h" + +#include "base/logging.h" +#include "base/string_util.h" + +ComMessageEvent::ComMessageEvent() { +} + +ComMessageEvent::~ComMessageEvent() { +} + +bool ComMessageEvent::Initialize(IOleContainer* container, + const std::string& message, + const std::string& origin, + const std::string& event_type) { + DCHECK(container); + message_ = message; + origin_ = origin; + type_ = event_type; + + // May remain NULL if container not IE + ScopedComPtr<IHTMLEventObj> basic_event; + ScopedComPtr<IHTMLDocument2> doc; + + // Fetching doc may fail in non-IE containers. + container->QueryInterface(doc.Receive()); + if (doc) { + ScopedComPtr<IHTMLDocument4> doc4; + doc4.QueryFrom(doc); + DCHECK(doc4); // supported by IE5.5 and higher + if (doc4) { + // IHTMLEventObj5 is only supported in IE8 and later, so we provide our + // own (minimalistic) implementation of it. + doc4->createEventObject(NULL, basic_event.Receive()); + DCHECK(basic_event); + } + } + + basic_event_ = basic_event; + return true; +} + +STDMETHODIMP ComMessageEvent::GetTypeInfoCount(UINT* info) { + // Don't DCHECK as python scripts might still call this function + // inadvertently. + DLOG(WARNING) << "Not implemented: " << __FUNCTION__; + return E_NOTIMPL; +} + +STDMETHODIMP ComMessageEvent::GetTypeInfo(UINT which_info, LCID lcid, + ITypeInfo** type_info) { + NOTREACHED(); + return E_NOTIMPL; +} + +STDMETHODIMP ComMessageEvent::GetIDsOfNames(REFIID iid, LPOLESTR* names, + UINT count_names, LCID lcid, + DISPID* dispids) { + HRESULT hr = S_OK; + + // Note that since we're using LowerCaseEqualsASCII for string comparison, + // the second argument _must_ be all lower case. I.e. we cannot compare + // against L"messagePort" since it has a capital 'P'. + for (UINT i = 0; SUCCEEDED(hr) && i < count_names; ++i) { + const wchar_t* name_begin = names[i]; + const wchar_t* name_end = name_begin + wcslen(name_begin); + if (LowerCaseEqualsASCII(name_begin, name_end, "data")) { + dispids[i] = DISPID_MESSAGE_EVENT_DATA; + } else if (LowerCaseEqualsASCII(name_begin, name_end, "origin")) { + dispids[i] = DISPID_MESSAGE_EVENT_ORIGIN; + } else if (LowerCaseEqualsASCII(name_begin, name_end, "lasteventid")) { + dispids[i] = DISPID_MESSAGE_EVENT_LAST_EVENT_ID; + } else if (LowerCaseEqualsASCII(name_begin, name_end, "source")) { + dispids[i] = DISPID_MESSAGE_EVENT_SOURCE; + } else if (LowerCaseEqualsASCII(name_begin, name_end, "messageport")) { + dispids[i] = DISPID_MESSAGE_EVENT_MESSAGE_PORT; + } else if (LowerCaseEqualsASCII(name_begin, name_end, "type")) { + dispids[i] = DISPID_MESSAGE_EVENT_TYPE; + } else { + if (basic_event_) { + hr = basic_event_->GetIDsOfNames(IID_IDispatch, &names[i], 1, lcid, + &dispids[i]); + } else { + hr = DISP_E_MEMBERNOTFOUND; + } + + if (FAILED(hr)) { + DLOG(WARNING) << "member not found: " << names[i] + << StringPrintf(L"0x%08X", hr); + } + } + } + return hr; +} + +STDMETHODIMP ComMessageEvent::Invoke(DISPID dispid, REFIID iid, LCID lcid, + WORD flags, DISPPARAMS* params, + VARIANT* result, EXCEPINFO* excepinfo, + UINT* arg_err) { + HRESULT hr = DISP_E_MEMBERNOTFOUND; + switch (dispid) { + case DISPID_MESSAGE_EVENT_DATA: + hr = GetStringProperty(flags, UTF8ToWide(message_).c_str(), result); + break; + + case DISPID_MESSAGE_EVENT_ORIGIN: + hr = GetStringProperty(flags, UTF8ToWide(origin_).c_str(), result); + break; + + case DISPID_MESSAGE_EVENT_TYPE: + hr = GetStringProperty(flags, UTF8ToWide(type_).c_str(), result); + break; + + case DISPID_MESSAGE_EVENT_LAST_EVENT_ID: + hr = GetStringProperty(flags, L"", result); + break; + + case DISPID_MESSAGE_EVENT_SOURCE: + case DISPID_MESSAGE_EVENT_MESSAGE_PORT: + if (flags & DISPATCH_PROPERTYGET) { + result->vt = VT_NULL; + hr = S_OK; + } else { + hr = DISP_E_TYPEMISMATCH; + } + break; + + default: + if (basic_event_) { + hr = basic_event_->Invoke(dispid, iid, lcid, flags, params, result, + excepinfo, arg_err); + } + break; + } + + return hr; +} + +HRESULT ComMessageEvent::GetStringProperty(WORD flags, const wchar_t* value, + VARIANT* result) { + if (!result) + return E_INVALIDARG; + + HRESULT hr; + if (flags & DISPATCH_PROPERTYGET) { + result->vt = VT_BSTR; + result->bstrVal = ::SysAllocString(value); + hr = S_OK; + } else { + hr = DISP_E_TYPEMISMATCH; + } + return hr; +} diff --git a/chrome_frame/com_message_event.h b/chrome_frame/com_message_event.h new file mode 100644 index 0000000..86fec2d --- /dev/null +++ b/chrome_frame/com_message_event.h @@ -0,0 +1,75 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_FRAME_COM_MESSAGE_EVENT_H_ +#define CHROME_FRAME_COM_MESSAGE_EVENT_H_ + +#include <atlbase.h> +#include <atlcom.h> +#include <mshtml.h> // IHTMLEventObj +#include "base/basictypes.h" +#include "base/scoped_comptr_win.h" + +// Implements a MessageEvent compliant event object by providing MessageEvent +// specific properties itself and inherited properties from a browser provided +// event implementation. +// NOTE: The messagePort and source properties will always be NULL. +// See the HTML 5 spec for further details on a MessageEvent object: +// http://www.whatwg.org/specs/web-apps/current-work/multipage/comms.html#messageevent +class ComMessageEvent + : public CComObjectRootEx<CComSingleThreadModel>, + public IDispatch { + public: + ComMessageEvent(); + ~ComMessageEvent(); + +BEGIN_COM_MAP(ComMessageEvent) + COM_INTERFACE_ENTRY(IDispatch) +END_COM_MAP() + + // The dispids we support. These are based on HTML5 and not IHTMLEventObj5 + // (there are a couple of differences). + // http://dev.w3.org/html5/spec/Overview.html#messageevent + // vs http://msdn.microsoft.com/en-us/library/cc288548(VS.85).aspx + typedef enum { + DISPID_MESSAGE_EVENT_DATA = 201, + DISPID_MESSAGE_EVENT_ORIGIN, + DISPID_MESSAGE_EVENT_LAST_EVENT_ID, + DISPID_MESSAGE_EVENT_SOURCE, + DISPID_MESSAGE_EVENT_MESSAGE_PORT, + DISPID_MESSAGE_EVENT_TYPE + } MessageEventDispIds; + + // Utility function for checking Invoke flags and assigning a BSTR to the + // result variable. + HRESULT GetStringProperty(WORD flags, const wchar_t* value, VARIANT* result); + + STDMETHOD(GetTypeInfoCount)(UINT* info); + STDMETHOD(GetTypeInfo)(UINT which_info, LCID lcid, ITypeInfo** type_info); + STDMETHOD(GetIDsOfNames)(REFIID iid, LPOLESTR* names, UINT count_names, + LCID lcid, DISPID* dispids); + STDMETHOD(Invoke)(DISPID dispid, REFIID iid, LCID lcid, WORD flags, + DISPPARAMS* params, VARIANT* result, EXCEPINFO* excepinfo, + UINT* arg_err); + + // Initializes this object. The container pointer is used to find the + // container's IHTMLEventObj implementation if available. + bool Initialize(IOleContainer* container, const std::string& message, + const std::string& origin, const std::string& event_type); + + protected: + // HTML Event object to which we delegate property and method + // calls that we do not directly support. This may not be initialized + // if our container does not require the properties/methods exposed by + // the basic event object. + ScopedComPtr<IHTMLEventObj> basic_event_; + std::string message_; + std::string origin_; + std::string type_; + + private: + DISALLOW_COPY_AND_ASSIGN(ComMessageEvent); +}; + +#endif // CHROME_FRAME_COM_MESSAGE_EVENT_H_ diff --git a/chrome_frame/com_type_info_holder.cc b/chrome_frame/com_type_info_holder.cc new file mode 100644 index 0000000..a018f81 --- /dev/null +++ b/chrome_frame/com_type_info_holder.cc @@ -0,0 +1,123 @@ +// Copyright (c) 2009 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. + +#include "chrome_frame/com_type_info_holder.h" + +#include "base/lazy_instance.h" +#include "base/logging.h" + +extern "C" IMAGE_DOS_HEADER __ImageBase; + +namespace com_util { + +base::LazyInstance<TypeInfoCache> type_info_cache(base::LINKER_INITIALIZED); + +// TypeInfoCache + +TypeInfoCache::~TypeInfoCache() { + CacheMap::iterator it = cache_.begin(); + while (it != cache_.end()) { + delete it->second; + it++; + } +} + +TypeInfoNameCache* TypeInfoCache::Lookup(const IID* iid) { + DCHECK(Singleton() == this); + + TypeInfoNameCache* tih = NULL; + + AutoLock lock(lock_); + CacheMap::iterator it = cache_.find(iid); + if (it == cache_.end()) { + tih = new TypeInfoNameCache(); + HRESULT hr = tih ? tih->Initialize(*iid) : E_OUTOFMEMORY; + if (SUCCEEDED(hr)) { + cache_[iid] = tih; + } else { + NOTREACHED(); + delete tih; + } + } else { + tih = it->second; + } + + return tih; +} + +TypeInfoCache* TypeInfoCache::Singleton() { + return type_info_cache.Pointer(); +} + +HRESULT TypeInfoNameCache::Initialize(const IID& iid) { + DCHECK(type_info_ == NULL); + + wchar_t file_path[MAX_PATH]; + DWORD path_len = ::GetModuleFileNameW(reinterpret_cast<HMODULE>(&__ImageBase), + file_path, arraysize(file_path)); + if (path_len == 0 || path_len == MAX_PATH) { + NOTREACHED(); + return E_UNEXPECTED; + } + + ScopedComPtr<ITypeLib> type_lib; + HRESULT hr = LoadTypeLib(file_path, type_lib.Receive()); + if (SUCCEEDED(hr)) { + hr = type_lib->GetTypeInfoOfGuid(iid, type_info_.Receive()); + } + + return hr; +} + +// TypeInfoNameCache + +HRESULT TypeInfoNameCache::GetIDsOfNames(OLECHAR** names, uint32 count, + DISPID* dispids) { + DCHECK(type_info_ != NULL); + + HRESULT hr = S_OK; + for (uint32 i = 0; i < count && SUCCEEDED(hr); ++i) { + NameToDispIdCache::HashType hash = NameToDispIdCache::Hash(names[i]); + if (!cache_.Lookup(hash, &dispids[i])) { + hr = type_info_->GetIDsOfNames(&names[i], 1, &dispids[i]); + if (SUCCEEDED(hr)) { + cache_.Add(hash, dispids[i]); + } + } + } + + return hr; +} + +HRESULT TypeInfoNameCache::Invoke(IDispatch* p, DISPID dispid, WORD flags, + DISPPARAMS* params, VARIANT* result, + EXCEPINFO* excepinfo, UINT* arg_err) { + DCHECK(type_info_); + HRESULT hr = type_info_->Invoke(p, dispid, flags, params, result, excepinfo, + arg_err); + DCHECK(hr != RPC_E_WRONG_THREAD); + return hr; +} + +// NameToDispIdCache + +bool NameToDispIdCache::Lookup(HashType hash, DISPID* dispid) const { + AutoLock lock(lock_); + const DispidMap::const_iterator it = map_.find(hash); + bool found = (it != map_.end()); + if (found) + *dispid = it->second; + return found; +} + +void NameToDispIdCache::Add(HashType hash, DISPID dispid) { + AutoLock lock(lock_); + map_[hash] = dispid; +} + +NameToDispIdCache::HashType NameToDispIdCache::Hash(const wchar_t* name) { + return LHashValOfName(LANG_NEUTRAL, name); +} + +} // namespace com_util diff --git a/chrome_frame/com_type_info_holder.h b/chrome_frame/com_type_info_holder.h new file mode 100644 index 0000000..9b0e6cf --- /dev/null +++ b/chrome_frame/com_type_info_holder.h @@ -0,0 +1,191 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_FRAME_COM_TYPE_INFO_HOLDER_H_ +#define CHROME_FRAME_COM_TYPE_INFO_HOLDER_H_ + +#include <map> +#include <ocidl.h> // IProvideClassInfo2 + +#include "base/lock.h" +#include "base/scoped_comptr_win.h" + +#define NO_VTABLE __declspec(novtable) + +namespace com_util { + +// A map from a name hash (32 bit value) to a DISPID. +// Used as a caching layer before looking the name up in a type lib. +class NameToDispIdCache { + public: + typedef uint32 HashType; + + bool Lookup(HashType hash, DISPID* dispid) const; + void Add(HashType hash, DISPID dispid); + + // Hashes the name by calling LHashValOfName. + // The returned hash value is independent of the case of the characters + // in |name|. + static HashType Hash(const wchar_t* name); + + protected: + typedef std::map<HashType, DISPID> DispidMap; + DispidMap map_; + mutable Lock lock_; +}; + +// Wraps an instance of ITypeInfo and builds+maintains a cache of names +// to dispids. Also offers an Invoke method that simply forwards the call +// to ITypeInfo::Invoke. +class TypeInfoNameCache { + public: + // Loads the module's type library and fetches the ITypeInfo object for + // the specified interface ID. + HRESULT Initialize(const IID& iid); + + // Fetches the id's of the given names. If there's a cache miss, the results + // are fetched from the underlying ITypeInfo and then cached. + HRESULT GetIDsOfNames(OLECHAR** names, uint32 count, DISPID* dispids); + + // Calls ITypeInfo::Invoke. + HRESULT Invoke(IDispatch* p, DISPID dispid, WORD flags, DISPPARAMS* params, + VARIANT* result, EXCEPINFO* excepinfo, UINT* arg_err); + + inline ITypeInfo* CopyTypeInfo() { + ITypeInfo* ti = type_info_.get(); + if (ti) + ti->AddRef(); + return ti; + } + + protected: + ScopedComPtr<ITypeInfo> type_info_; + NameToDispIdCache cache_; +}; + +// The root class for type lib access. +// This class has only one instance that should be accessed via the +// Singleton method. +class TypeInfoCache { + public: + TypeInfoCache() { + } + + ~TypeInfoCache(); + + // Looks up a previously cached TypeInfoNameCache instance or creates and + // caches a new one. + TypeInfoNameCache* Lookup(const IID* iid); + + // Call to get access to the singleton instance of TypeInfoCache. + static TypeInfoCache* Singleton(); + + protected: + typedef std::map<const IID*, TypeInfoNameCache*> CacheMap; + Lock lock_; + CacheMap cache_; +}; + +// Holds a pointer to the type info of a given COM interface. +// The type info is loaded once on demand and after that cached. +// NOTE: This class only supports loading the first typelib from the +// current module. +template <const IID& iid> +class TypeInfoHolder { + public: + TypeInfoHolder() : type_info_(NULL) { + } + + bool EnsureTI() { + if (!type_info_) + type_info_ = TypeInfoCache::Singleton()->Lookup(&iid); + return type_info_ != NULL; + } + + HRESULT GetTypeInfo(UINT itinfo, LCID lcid, ITypeInfo** info) { + if (EnsureTI()) { + *info = type_info_->CopyTypeInfo(); + return S_OK; + } + + return E_UNEXPECTED; + } + + HRESULT GetIDsOfNames(REFIID riid, OLECHAR** names, UINT count, LCID lcid, + DISPID* dispids) { + if (!EnsureTI()) + return E_UNEXPECTED; + return type_info_->GetIDsOfNames(names, count, dispids); + } + + HRESULT Invoke(IDispatch* p, DISPID dispid, REFIID riid, LCID lcid, + WORD flags, DISPPARAMS* params, VARIANT* result, + EXCEPINFO* excepinfo, UINT* arg_err) { + if (!EnsureTI()) + return E_UNEXPECTED; + + return type_info_->Invoke(p, dispid, flags, params, result, excepinfo, + arg_err); + } + + protected: + TypeInfoNameCache* type_info_; +}; + +// Implements IDispatch part of T (where T is an IDispatch derived interface). +// The class assumes that the type info of T is available in a typelib of the +// current module. +template <class T, const IID& iid = __uuidof(T)> +class NO_VTABLE IDispatchImpl : public T { + public: + STDMETHOD(GetTypeInfoCount)(UINT* count) { + if (count == NULL) + return E_POINTER; + *count = 1; + return S_OK; + } + + STDMETHOD(GetTypeInfo)(UINT itinfo, LCID lcid, ITypeInfo** pptinfo) { + return type_info_.GetTypeInfo(itinfo, lcid, pptinfo); + } + + STDMETHOD(GetIDsOfNames)(REFIID riid, LPOLESTR* names, UINT count, + LCID lcid, DISPID* dispids) { + return type_info_.GetIDsOfNames(riid, names, count, lcid, dispids); + } + STDMETHOD(Invoke)(DISPID dispid, REFIID riid, LCID lcid, WORD flags, + DISPPARAMS* params, VARIANT* result, EXCEPINFO* excepinfo, + UINT* arg_err) { + return type_info_.Invoke(static_cast<IDispatch*>(this), dispid, riid, lcid, + flags, params, result, excepinfo, arg_err); + } + + protected: + TypeInfoHolder<iid> type_info_; +}; + +// Simple implementation of IProvideClassInfo[2]. +template <const CLSID& class_id, const IID& source_iid> +class NO_VTABLE IProvideClassInfo2Impl : public IProvideClassInfo2 { + public: + STDMETHOD(GetClassInfo)(ITypeInfo** pptinfo) { + return type_info_.GetTypeInfo(0, LANG_NEUTRAL, pptinfo); + } + + STDMETHOD(GetGUID)(DWORD guid_kind, GUID* guid) { + if(guid == NULL || guid_kind != GUIDKIND_DEFAULT_SOURCE_DISP_IID) + return E_INVALIDARG; + + *guid = source_iid; + + return S_OK; + } + + protected: + TypeInfoHolder<class_id> type_info_; +}; + +} // namespace com_util + +#endif // CHROME_FRAME_COM_TYPE_INFO_HOLDER_H_ diff --git a/chrome_frame/combine_libs.py b/chrome_frame/combine_libs.py new file mode 100644 index 0000000..97d3de7 --- /dev/null +++ b/chrome_frame/combine_libs.py @@ -0,0 +1,114 @@ +# Copyright (c) 2009 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. + +# TODO(slightlyoff): move to using shared version of this script. + +'''This script makes it easy to combine libs and object files to a new lib, +optionally removing some of the object files in the input libs by regular +expression matching. +For usage information, run the script with a --help argument. +''' +import optparse +import os +import re +import subprocess +import sys + + +def Shell(*args): + '''Runs the program and args in args, returns the output from the program.''' + process = subprocess.Popen(args, + stdin = None, + stdout = subprocess.PIPE, + stderr = subprocess.STDOUT) + output = process.stdout.readlines() + process.wait() + retcode = process.returncode + if retcode != 0: + raise RuntimeError('%s exited with status %d' % (args[0], retcode)) + return output + + +def CollectRemovals(remove_re, inputs): + '''Returns a list of all object files in inputs that match remove_re.''' + removals = [] + for input in inputs: + output = Shell('lib.exe', '/list', input) + + for line in output: + line = line.rstrip() + if remove_re.search(line): + removals.append(line) + + return removals + + +def CombineLibraries(output, remove_re, inputs): + '''Combines all the libraries and objects in inputs, while removing any + object files that match remove_re. + ''' + removals = [] + if remove_re: + removals = CollectRemovals(remove_re, inputs) + + print removals + + args = ['lib.exe', '/out:%s' % output] + args += ['/remove:%s' % obj for obj in removals] + args += inputs + Shell(*args) + + +USAGE = '''usage: %prog [options] <lib or obj>+ + +Combines input libraries or objects into an output library, while removing +any object file (in the input libraries) that matches a given regular +expression. +''' + +def GetOptionParser(): + parser = optparse.OptionParser(USAGE) + parser.add_option('-o', '--output', dest = 'output', + help = 'write to this output library') + parser.add_option('-r', '--remove', dest = 'remove', + help = 'object files matching this regexp will be removed ' + 'from the output library') + return parser + + +def Main(): + '''Main function for this script''' + parser = GetOptionParser() + (opt, args) = parser.parse_args() + output = opt.output + remove = opt.remove + if not output: + parser.error('You must specify an output file') + + if not args: + parser.error('You must specify at least one object or library') + + output = output.strip() + remove = remove.strip() + + if remove: + try: + remove_re = re.compile(opt.remove) + except: + parser.error('%s is not a valid regular expression' % opt.remove) + else: + remove_re = None + + if sys.platform != 'win32': + parser.error('this script only works on Windows for now') + + # If this is set, we can't capture lib.exe's output. + if 'VS_UNICODE_OUTPUT' in os.environ: + del os.environ['VS_UNICODE_OUTPUT'] + + CombineLibraries(output, remove_re, args) + + +if __name__ == '__main__': + Main() diff --git a/chrome_frame/common/extra_defines.vsprops b/chrome_frame/common/extra_defines.vsprops new file mode 100644 index 0000000..ca29c05 --- /dev/null +++ b/chrome_frame/common/extra_defines.vsprops @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioPropertySheet
+ ProjectType="Visual C++"
+ Version="8.00"
+ Name="extra"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ PreprocessorDefinitions="EXCLUDE_SKIA_DEPENDENCIES"
+ AdditionalIncludeDirectories=""
+ />
+</VisualStudioPropertySheet>
diff --git a/chrome_frame/crash_report.cc b/chrome_frame/crash_report.cc new file mode 100644 index 0000000..5a67316 --- /dev/null +++ b/chrome_frame/crash_report.cc @@ -0,0 +1,144 @@ +// Copyright (c) 2009 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.
+
+// crash_report.cc : Implementation crash reporting.
+#include "chrome_frame/crash_report.h"
+
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/win_util.h"
+#include "breakpad/src/client/windows/handler/exception_handler.h"
+#include "chrome/installer/util/google_update_settings.h"
+#include "chrome/installer/util/install_util.h"
+#include "chrome_frame/vectored_handler.h"
+#include "chrome_frame/vectored_handler-impl.h"
+
+namespace {
+// TODO(joshia): factor out common code with chrome used for crash reporting
+const wchar_t kGoogleUpdatePipeName[] = L"\\\\.\\pipe\\GoogleCrashServices\\";
+const wchar_t kChromePipeName[] = L"\\\\.\\pipe\\ChromeCrashServices";
+// Well known SID for the system principal.
+const wchar_t kSystemPrincipalSid[] = L"S-1-5-18";
+google_breakpad::ExceptionHandler* g_breakpad = NULL;
+
+// Returns the custom info structure based on the dll in parameter and the
+// process type.
+google_breakpad::CustomClientInfo* GetCustomInfo() {
+ // TODO(joshia): Grab these based on build.
+ static google_breakpad::CustomInfoEntry ver_entry(L"ver", L"0.1.0.0");
+ static google_breakpad::CustomInfoEntry prod_entry(L"prod", L"ChromeFrame");
+ static google_breakpad::CustomInfoEntry plat_entry(L"plat", L"Win32");
+ static google_breakpad::CustomInfoEntry type_entry(L"ptype", L"chrome_frame");
+ static google_breakpad::CustomInfoEntry entries[] = {
+ ver_entry, prod_entry, plat_entry, type_entry };
+ static google_breakpad::CustomClientInfo custom_info = {
+ entries, arraysize(entries) };
+ return &custom_info;
+}
+
+__declspec(naked)
+static EXCEPTION_REGISTRATION_RECORD* InternalRtlpGetExceptionList() {
+ __asm {
+ mov eax, fs:0
+ ret
+ }
+}
+} // end of namespace
+
+// Class which methods simply forwards to Win32 API and uses breakpad to write
+// a minidump. Used as template (external interface) of VectoredHandlerT<E>.
+class Win32VEHTraits : public VEHTraitsBase {
+ public:
+ static inline void* Register(PVECTORED_EXCEPTION_HANDLER func,
+ const void* module_start, const void* module_end) {
+ VEHTraitsBase::SetModule(module_start, module_end);
+ return ::AddVectoredExceptionHandler(1, func);
+ }
+
+ static inline ULONG Unregister(void* handle) {
+ return ::RemoveVectoredExceptionHandler(handle);
+ }
+
+ static inline bool WriteDump(EXCEPTION_POINTERS* p) {
+ return g_breakpad->WriteMinidumpForException(p);
+ }
+
+ static inline EXCEPTION_REGISTRATION_RECORD* RtlpGetExceptionList() {
+ return InternalRtlpGetExceptionList();
+ }
+
+ static inline WORD RtlCaptureStackBackTrace(DWORD FramesToSkip,
+ DWORD FramesToCapture, void** BackTrace, DWORD* BackTraceHash) {
+ return ::RtlCaptureStackBackTrace(FramesToSkip, FramesToCapture,
+ BackTrace, BackTraceHash);
+ }
+};
+
+extern "C" IMAGE_DOS_HEADER __ImageBase;
+bool InitializeCrashReporting(bool use_crash_service, bool full_dump) {
+ if (g_breakpad)
+ return true;
+
+ std::wstring pipe_name;
+ if (use_crash_service) {
+ // Crash reporting is done by crash_service.exe.
+ pipe_name = kChromePipeName;
+ } else {
+ // We want to use the Google Update crash reporting. We need to check if the
+ // user allows it first.
+ if (!GoogleUpdateSettings::GetCollectStatsConsent())
+ return true;
+
+ // Build the pipe name. It can be either:
+ // System-wide install: "NamedPipe\GoogleCrashServices\S-1-5-18"
+ // Per-user install: "NamedPipe\GoogleCrashServices\<user SID>"
+ wchar_t dll_path[MAX_PATH * 2] = {0};
+ GetModuleFileName(reinterpret_cast<HMODULE>(&__ImageBase), dll_path,
+ arraysize(dll_path));
+
+ std::wstring user_sid;
+ if (InstallUtil::IsPerUserInstall(dll_path)) {
+ if (!win_util::GetUserSidString(&user_sid)) {
+ return false;
+ }
+ } else {
+ user_sid = kSystemPrincipalSid;
+ }
+
+ pipe_name = kGoogleUpdatePipeName;
+ pipe_name += user_sid;
+ }
+
+ // Get the alternate dump directory. We use the temp path.
+ FilePath temp_directory;
+ if (!file_util::GetTempDir(&temp_directory) || temp_directory.empty()) {
+ return false;
+ }
+
+ MINIDUMP_TYPE dump_type = full_dump ? MiniDumpWithFullMemory : MiniDumpNormal;
+ g_breakpad = new google_breakpad::ExceptionHandler(
+ temp_directory.value(), NULL, NULL, NULL,
+ google_breakpad::ExceptionHandler::HANDLER_INVALID_PARAMETER |
+ google_breakpad::ExceptionHandler::HANDLER_PURECALL, dump_type,
+ pipe_name.c_str(), GetCustomInfo());
+
+ if (g_breakpad) {
+ // Find current module boundaries.
+ const void* start = &__ImageBase;
+ const char* s = reinterpret_cast<const char*>(start);
+ const IMAGE_NT_HEADERS32* nt = reinterpret_cast<const IMAGE_NT_HEADERS32*>
+ (s + __ImageBase.e_lfanew);
+ const void* end = s + nt->OptionalHeader.SizeOfImage;
+ VectoredHandler::Register(start, end);
+ }
+
+ return g_breakpad != NULL;
+}
+
+bool ShutdownCrashReporting() {
+ VectoredHandler::Unregister();
+ delete g_breakpad;
+ g_breakpad = NULL;
+ return true;
+}
diff --git a/chrome_frame/crash_report.h b/chrome_frame/crash_report.h new file mode 100644 index 0000000..3758e33 --- /dev/null +++ b/chrome_frame/crash_report.h @@ -0,0 +1,13 @@ +// Copyright (c) 2009 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.
+
+// crash_report.h : Declarations for crash reporting.
+
+#ifndef CHROME_FRAME_CRASH_REPORT_H_
+#define CHROME_FRAME_CRASH_REPORT_H_
+
+bool InitializeCrashReporting(bool use_crash_service, bool full_dump);
+bool ShutdownCrashReporting();
+
+#endif // CHROME_FRAME_CRASH_REPORT_H_
diff --git a/chrome_frame/extra_system_apis.h b/chrome_frame/extra_system_apis.h new file mode 100644 index 0000000..0fbe0ea --- /dev/null +++ b/chrome_frame/extra_system_apis.h @@ -0,0 +1,37 @@ +// Copyright (c) 2009 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 header file contains declarations for system APIs and interfaces +// that are either undocumented or are documented but not included in any +// Platform SDK header files. +#ifndef CHROME_FRAME_EXTRA_SYSTEM_APIS_H_ +#define CHROME_FRAME_EXTRA_SYSTEM_APIS_H_ + +// This is an interface provided by the WebBrowser object. It allows us to +// notify the browser of navigation events. MSDN documents this interface +// (see http://msdn2.microsoft.com/en-us/library/aa752109(VS.85).aspx) +// but this is not included in any Platform SDK header file. +class __declspec(uuid("54A8F188-9EBD-4795-AD16-9B4945119636")) +IWebBrowserEventsService : public IUnknown { + public: + STDMETHOD(FireBeforeNavigate2Event)(VARIANT_BOOL *cancel) = 0; + STDMETHOD(FireNavigateComplete2Event)(VOID) = 0; + STDMETHOD(FireDownloadBeginEvent)(VOID) = 0; + STDMETHOD(FireDownloadCompleteEvent)(VOID) = 0; + STDMETHOD(FireDocumentCompleteEvent)(VOID) = 0; +}; + +// This interface is used in conjunction with the IWebBrowserEventsService +// interface. The web browser queries us for this interface when we invoke +// one of the IWebBrowserEventsService methods. This interface supplies the +// WebBrowser with a URL to use for the events. MSDN documents this interface +// (see http://msdn2.microsoft.com/en-us/library/aa752103(VS.85).aspx) +// but this is not included in any Platform SDK header file. +class __declspec(uuid("{87CC5D04-EAFA-4833-9820-8F986530CC00}")) +IWebBrowserEventsUrlService : public IUnknown { + public: + STDMETHOD(GetUrlForEvents)(BSTR *url) = 0; +}; + +#endif // CHROME_FRAME_EXTRA_SYSTEM_APIS_H_ diff --git a/chrome_frame/ff_30_privilege_check.cc b/chrome_frame/ff_30_privilege_check.cc new file mode 100644 index 0000000..e2b6369 --- /dev/null +++ b/chrome_frame/ff_30_privilege_check.cc @@ -0,0 +1,99 @@ +// Copyright (c) 2009 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 relies on the 1.9 version of the unfrozen interfaces +// "nsIScriptSecurityManager" and "nsIScriptObjectPrincipal" +// from gecko 1.9, which means that this implementation is specific to +// FireFox 3.0 and any other browsers built from the same gecko version. +// See [http://en.wikipedia.org/wiki/Gecko_(layout_engine] +// It's a good bet that nsIScriptSecurityManager will change for gecko +// 1.9.1 and FireFox 3.5, in which case we'll need another instance of this +// code for the 3.5 version of FireFox. + +// Gecko headers need this on Windows. +#define XP_WIN +#include "chrome_frame/script_security_manager.h" +#include "third_party/xulrunner-sdk/win/include/dom/nsIScriptObjectPrincipal.h" +#include "third_party/xulrunner-sdk/win/include/xpcom/nsIServiceManager.h" + +// These are needed to work around typedef conflicts in chrome headers. +#define _UINT32 +#define _INT32 + +#include "chrome_frame/np_browser_functions.h" +#include "chrome_frame/scoped_ns_ptr_win.h" +#include "chrome_frame/ns_associate_iid_win.h" +#include "base/logging.h" + +ASSOCIATE_IID(NS_ISERVICEMANAGER_IID_STR, nsIServiceManager); + +namespace { +// Unfortunately no NS_ISCRIPTOBJECTPRINCIPAL_IID_STR +// defined for this interface +nsIID IID_nsIScriptObjectPrincipal = NS_ISCRIPTOBJECTPRINCIPAL_IID; +} // namespace + +// Returns true iff we're being instantiated into a document +// that has the system principal's privileges +bool IsFireFoxPrivilegedInvocation(NPP instance) { + ScopedNsPtr<nsIServiceManager> service_manager; + NPError nperr = npapi::GetValue(instance, NPNVserviceManager, + service_manager.Receive()); + if (nperr != NPERR_NO_ERROR || !service_manager.get()) + return false; + DCHECK(service_manager); + + // Get the document. + ScopedNsPtr<nsISupports> window; + nperr = npapi::GetValue(instance, NPNVDOMWindow, window.Receive()); + if (nperr != NPERR_NO_ERROR || !window.get()) + return false; + DCHECK(window); + + // This interface allows us access to the window's principal. + ScopedNsPtr<nsIScriptObjectPrincipal, &IID_nsIScriptObjectPrincipal> + script_object_principal; + nsresult err = script_object_principal.QueryFrom(window); + if (NS_FAILED(err) || !script_object_principal.get()) + return false; + DCHECK(script_object_principal); + + // For regular HTML windows, this will be a principal encoding the + // document's origin. For browser XUL, this will be the all-powerful + // system principal. + nsIPrincipal* window_principal = script_object_principal->GetPrincipal(); + DCHECK(window_principal); + if (!window_principal) + return false; + + // Get the script security manager. + ScopedNsPtr<nsIScriptSecurityManager_FF35> security_manager_ff35; + PRBool is_system = PR_FALSE; + + err = service_manager->GetServiceByContractID( + NS_SCRIPTSECURITYMANAGER_CONTRACTID, + nsIScriptSecurityManager_FF35::GetIID(), + reinterpret_cast<void**>(security_manager_ff35.Receive())); + if (NS_SUCCEEDED(err) && security_manager_ff35.get()) { + err = security_manager_ff35->IsSystemPrincipal(window_principal, + &is_system); + if (NS_FAILED(err)) + is_system = PR_FALSE; + } else { + ScopedNsPtr<nsIScriptSecurityManager_FF30> security_manager_ff30; + err = service_manager->GetServiceByContractID( + NS_SCRIPTSECURITYMANAGER_CONTRACTID, + nsIScriptSecurityManager_FF30::GetIID(), + reinterpret_cast<void**>(security_manager_ff30.Receive())); + if (NS_SUCCEEDED(err) && security_manager_ff30.get()) { + err = security_manager_ff30->IsSystemPrincipal(window_principal, + &is_system); + } + + if (NS_FAILED(err)) + is_system = PR_FALSE; + } + + return is_system == PR_TRUE; +} diff --git a/chrome_frame/ff_privilege_check.h b/chrome_frame/ff_privilege_check.h new file mode 100644 index 0000000..a7b5c00 --- /dev/null +++ b/chrome_frame/ff_privilege_check.h @@ -0,0 +1,14 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_FRAME_FF_PRIVILEGE_CHECK_H_ +#define CHROME_FRAME_FF_PRIVILEGE_CHECK_H_ + +// Returns true iff we're being invoked in a privileged document +// in FireFox 3.x. +// An example privileged document is when the plugin is instantiated +// in browser chrome by XUL. +bool IsFireFoxPrivilegedInvocation(NPP instance); + +#endif // CHROME_FRAME_FF_PRIVILEGE_CHECK_H_ diff --git a/chrome_frame/find_dialog.cc b/chrome_frame/find_dialog.cc new file mode 100644 index 0000000..15aaff7 --- /dev/null +++ b/chrome_frame/find_dialog.cc @@ -0,0 +1,107 @@ +// Copyright (c) 2009 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. + +#include "chrome_frame/find_dialog.h" + +#include <Richedit.h> + +#include "chrome_frame/chrome_frame_automation.h" + +const int kMaxFindChars = 1024; + +HHOOK CFFindDialog::msg_hook_ = NULL; + +CFFindDialog::CFFindDialog() {} + +void CFFindDialog::Init(ChromeFrameAutomationClient* automation_client) { + automation_client_ = automation_client; +} + +LRESULT CFFindDialog::OnDestroy(UINT msg, WPARAM wparam, LPARAM lparam, + BOOL& handled) { + UninstallMessageHook(); + return 0; +} + +LRESULT CFFindDialog::OnFind(WORD wNotifyCode, WORD wID, HWND hWndCtl, + BOOL& bHandled) { + wchar_t buffer[kMaxFindChars + 1]; + GetDlgItemText(IDC_FIND_TEXT, buffer, kMaxFindChars); + std::wstring find_text(buffer); + + bool match_case = IsDlgButtonChecked(IDC_MATCH_CASE) == BST_CHECKED; + bool search_down = IsDlgButtonChecked(IDC_DIRECTION_DOWN) == BST_CHECKED; + + automation_client_->FindInPage(find_text, + search_down ? FWD : BACK, + match_case ? CASE_SENSITIVE : IGNORE_CASE, + false); + + return 0; +} + +LRESULT CFFindDialog::OnCancel(WORD wNotifyCode, WORD wID, HWND hWndCtl, + BOOL& bHandled) { + DestroyWindow(); + return 0; +} + +LRESULT CFFindDialog::OnInitDialog(UINT msg, WPARAM wparam, LPARAM lparam, + BOOL& handled) { + // Init() must be called before Create() or DoModal()! + DCHECK(automation_client_); + + InstallMessageHook(); + SendDlgItemMessage(IDC_FIND_TEXT, EM_EXLIMITTEXT, 0, kMaxFindChars); + BOOL result = CheckRadioButton(IDC_DIRECTION_DOWN, IDC_DIRECTION_UP, + IDC_DIRECTION_DOWN); + + HWND text_field = GetDlgItem(IDC_FIND_TEXT); + ::SetFocus(text_field); + + return FALSE; // we set the focus ourselves. +} + +LRESULT CALLBACK CFFindDialog::GetMsgProc(int code, WPARAM wparam, + LPARAM lparam) { + // Mostly borrowed from http://support.microsoft.com/kb/q187988/ + // and http://www.codeproject.com/KB/atl/cdialogmessagehook.aspx. + LPMSG msg = reinterpret_cast<LPMSG>(lparam);
+ if (code >= 0 && wparam == PM_REMOVE &&
+ msg->message >= WM_KEYFIRST && msg->message <= WM_KEYLAST) {
+ HWND hwnd = GetActiveWindow();
+ if (::IsWindow(hwnd) && ::IsDialogMessage(hwnd, msg)) {
+ // The value returned from this hookproc is ignored, and it cannot
+ // be used to tell Windows the message has been handled. To avoid
+ // further processing, convert the message to WM_NULL before
+ // returning.
+ msg->hwnd = NULL;
+ msg->message = WM_NULL;
+ msg->lParam = 0L;
+ msg->wParam = 0;
+ }
+ }
+
+ // Passes the hook information to the next hook procedure in
+ // the current hook chain.
+ return ::CallNextHookEx(msg_hook_, code, wparam, lparam); +} + +bool CFFindDialog::InstallMessageHook() { + // Make sure we only call this once. + DCHECK(msg_hook_ == NULL); + msg_hook_ = ::SetWindowsHookEx(WH_GETMESSAGE, &CFFindDialog::GetMsgProc,
+ _AtlBaseModule.m_hInst, GetCurrentThreadId());
+ DCHECK(msg_hook_ != NULL); + return msg_hook_ != NULL; +} + +bool CFFindDialog::UninstallMessageHook() { + DCHECK(msg_hook_ != NULL); + BOOL result = ::UnhookWindowsHookEx(msg_hook_); + DCHECK(result); + msg_hook_ = NULL; + + return result != FALSE; +} diff --git a/chrome_frame/find_dialog.h b/chrome_frame/find_dialog.h new file mode 100644 index 0000000..9e7cafe --- /dev/null +++ b/chrome_frame/find_dialog.h @@ -0,0 +1,54 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_FRAME_FIND_DIALOG_H_ +#define CHROME_FRAME_FIND_DIALOG_H_ + +#include <atlbase.h> +#include <atlwin.h> + +#include "base/ref_counted.h" +#include "resource.h" +#include "grit/chrome_frame_resources.h" + +class ChromeFrameAutomationClient; + +class CFFindDialog : public CDialogImpl<CFFindDialog> { + public: + enum { IDD = IDD_FIND_DIALOG }; + + BEGIN_MSG_MAP(CFFindDialog) + MESSAGE_HANDLER(WM_DESTROY, OnDestroy) + MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog) + COMMAND_ID_HANDLER(IDOK, OnFind) + COMMAND_ID_HANDLER(IDCANCEL, OnCancel) + END_MSG_MAP() + + CFFindDialog(); + void Init(ChromeFrameAutomationClient* automation_client); + + LRESULT OnDestroy(UINT msg, WPARAM wparam, + LPARAM lparam, BOOL& handled); // NOLINT + LRESULT OnFind(WORD wNotifyCode, WORD wID, + HWND hWndCtl, BOOL& bHandled); // NOLINT + LRESULT OnCancel(WORD wNotifyCode, WORD wID, + HWND hWndCtl, BOOL& bHandled); // NOLINT + LRESULT OnInitDialog(UINT msg, WPARAM wparam, + LPARAM lparam, BOOL& handled); // NOLINT + + private: + + // Since the message loop we expect to run in isn't going to be nicely + // calling IsDialogMessage(), we need to hook the wnd proc and call it + // ourselves. See http://support.microsoft.com/kb/q187988/ + bool InstallMessageHook(); + bool UninstallMessageHook(); + static LRESULT CALLBACK GetMsgProc(int code, WPARAM wparam, LPARAM lparam); + static HHOOK msg_hook_; + + // We don't own these, and they must exist at least as long as we do. + ChromeFrameAutomationClient* automation_client_; +}; + +#endif // CHROME_FRAME_FIND_DIALOG_H_ diff --git a/chrome_frame/frame.html b/chrome_frame/frame.html new file mode 100644 index 0000000..d63d3ac --- /dev/null +++ b/chrome_frame/frame.html @@ -0,0 +1,20 @@ +<html> +<!-- TODO(slightlyoff): Move to tests directory? --> +<head> +<title>Script test</title> +<script> +function OnLoad() { + var host = window.externalHost; + host.onmessage = OnHostMessage; + host.ForwardMessageToExternalHost("Hello from ChromeFrame"); +} + +function OnHostMessage(text) { + window.alert("In ChromeFrame: \r\n Message from host: " + text); +} +</script> +</head> +<body onload="OnLoad();"> +Test script +</body> +</html> diff --git a/chrome_frame/frame_w_controls.html b/chrome_frame/frame_w_controls.html new file mode 100644 index 0000000..d80642f --- /dev/null +++ b/chrome_frame/frame_w_controls.html @@ -0,0 +1,29 @@ +<html> +<!-- TODO(slightlyoff): Move to tests directory? --> +<head> +<title>Script test</title> +<script> +function msg(txt) { + window.document.getElementById("my_text").innerText = txt; +} + +function OnLoad() { + var host = window.externalHost; + host.ForwardMessageToExternalHost("OnChromeFrameMessage", + "Hello from ChromeFrame"); +} + +function OnHostMessage(text) { + msg("In ChromeFrame: \r\n Message from host: " + text); +} +</script> +</head> +<body onload="OnLoad();"> +Here's an edit field: <input type="text"><br> +Here's another: <input type="text"><br> +<p> +Message:<br> +<pre><div id="my_text"></div></pre> +</p> +</body> +</html> diff --git a/chrome_frame/function_stub.h b/chrome_frame/function_stub.h new file mode 100644 index 0000000..55f0c5e --- /dev/null +++ b/chrome_frame/function_stub.h @@ -0,0 +1,241 @@ +// Copyright (c) 2009 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. + + +#ifndef CHROME_FRAME_FUNCTION_STUB_H_ +#define CHROME_FRAME_FUNCTION_STUB_H_ + +#include <windows.h> +#include "base/logging.h" + +// IMPORTANT: The struct below must be byte aligned. +#pragma pack(push) +#pragma pack(1) + +#ifndef _M_IX86 +#error Only x86 supported right now. +#endif + +extern "C" IMAGE_DOS_HEADER __ImageBase; + +// This struct is assembly code + signature. The purpose of the struct is to be +// able to hook an existing function with our own and store information such +// as the original function pointer with the code stub. Typically this is used +// for patching entries of a vtable or e.g. a globally registered wndproc +// for a class as opposed to a window. +// When unhooking, you can just call the BypassStub() function and leave the +// stub in memory. This unhooks your function while leaving the (potential) +// chain of patches intact. +// +// @note: This class is meant for __stdcall calling convention and +// it uses eax as a temporary variable. The struct can +// be improved in the future to save eax before the +// operation and then restore it. +// +// For instance if the function prototype is: +// +// @code +// LRESULT WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); +// @endcode +// +// and we would like to add one static argument to make it, say: +// +// @code +// LRESULT MyNewWndProc(WNDPROC original, HWND hwnd, UINT msg, +// WPARAM wparam, LPARAM lparam); +// @endcode +// +// That can be achieved by wrapping the function up with a FunctionStub: +// +// @code +// FunctionStub* stub = FunctionStub::Create(original_wndproc, MyNewWndProc); +// SetClassLongPtr(wnd, GCLP_WNDPROC, stub->code()); +// @endcode +struct FunctionStub { + private: + typedef enum AsmConstants { + POP_EAX = 0x58, + PUSH = 0x68, + PUSH_EAX = 0x50, + JUMP_RELATIVE = 0xE9 + }; + + FunctionStub(uintptr_t extra_argument, void* dest) + : signature_(reinterpret_cast<HMODULE>(&__ImageBase)) { + Opcodes::Hook& hook = code_.hook_; + hook.pop_return_addr_ = POP_EAX; + hook.push_ = PUSH; + hook.arg_ = extra_argument; + hook.push_return_addr_ = PUSH_EAX; + hook.jump_to_ = JUMP_RELATIVE; + + // Calculate the target jump to the destination function. + hook.target_ = CalculateRelativeJump(dest, &hook.jump_to_); + + // Allow the process to execute this struct as code. + DWORD old_protect = 0; + // Allow reads too since we want read-only member variable access at + // all times. + ::VirtualProtect(this, sizeof(FunctionStub), PAGE_EXECUTE_READ, + &old_protect); + } + + ~FunctionStub() { + // No more execution rights. + DWORD old_protect = 0; + ::VirtualProtect(this, sizeof(FunctionStub), PAGE_READWRITE, &old_protect); + } + + // Calculates the target value for a relative jump. + // The function assumes that the size of the opcode is 1 byte. + inline uintptr_t CalculateRelativeJump(const void* absolute_to, + const void* absolute_from) const { + return (reinterpret_cast<uintptr_t>(absolute_to) - + reinterpret_cast<uintptr_t>(absolute_from)) - + (sizeof(uint8) + sizeof(uintptr_t)); + } + + // Does the opposite of what CalculateRelativeJump does, which is to + // calculate back the absolute address that previously was relative to + // some other address. + inline uintptr_t CalculateAbsoluteAddress(const void* relative_to, + uintptr_t relative_address) const { + return relative_address + sizeof(uint8) + sizeof(uintptr_t) + + reinterpret_cast<uintptr_t>(relative_to); + } + + // Used to identify function stubs that belong to this module. + HMODULE signature_; + + // IMPORTANT: Do not change the order of member variables + union Opcodes { + // Use this struct when the stub forwards the call to our hook function + // providing an extra argument. + struct Hook { + uint8 pop_return_addr_; // pop eax + uint8 push_; // push arg ; push... + uintptr_t arg_; // ; extra argument + uint8 push_return_addr_; // push eax ; push the return address + uint8 jump_to_; // jmp ; jump... + uintptr_t target_; // ; to the hook function + } hook_; + // When the stub is bypassed, we jump directly to a given target, + // usually the originally hooked function. + struct Bypassed { + uint8 jump_to_; // jmp to + uintptr_t target_; // relative target. + } bypassed_; + }; + + Opcodes code_; + + public: + // Neutralizes this stub and converts it to a direct jump to a new target. + void BypassStub(void* new_target) { + DWORD old_protect = 0; + // Temporarily allow us to write to member variables + ::VirtualProtect(this, sizeof(FunctionStub), PAGE_EXECUTE_READWRITE, + &old_protect); + + // Now, just change the first 5 bytes to jump directly to the new target. + Opcodes::Bypassed& bypassed = code_.bypassed_; + bypassed.jump_to_ = JUMP_RELATIVE; + bypassed.target_ = CalculateRelativeJump(new_target, &bypassed.jump_to_); + + // Restore the previous protection flags. + ::VirtualProtect(this, sizeof(FunctionStub), old_protect, &old_protect); + + // Flush the instruction cache just in case. + ::FlushInstructionCache(::GetCurrentProcess(), this, sizeof(FunctionStub)); + } + + // @returns the argument supplied in the call to @ref Create + inline uintptr_t argument() const { + DCHECK(code_.hook_.pop_return_addr_ == POP_EAX) << "stub has been disabled"; + return code_.hook_.arg_; + } + + inline bool is_bypassed() const { + return code_.bypassed_.jump_to_ == JUMP_RELATIVE; + } + + inline uintptr_t absolute_target() const { + DCHECK(code_.hook_.pop_return_addr_ == POP_EAX) << "stub has been disabled"; + return CalculateAbsoluteAddress( + reinterpret_cast<const void*>(&code_.hook_.jump_to_), + code_.hook_.target_); + } + + // Returns true if the stub is valid and enabled. + // Don't call this method after bypassing the stub. + inline bool is_valid() const { + return signature_ == reinterpret_cast<HMODULE>(&__ImageBase) && + code_.hook_.pop_return_addr_ == POP_EAX; + } + + inline PROC code() const { + return reinterpret_cast<PROC>(const_cast<Opcodes*>(&code_)); + } + + // Use to create a new function stub as shown above. + // + // @param extra_argument The static argument to pass to the function. + // @param dest Target function to which the stub applies. + // @returns NULL if an error occurs, otherwise a pointer to the + // function stub. + // + // TODO(tommi): Change this so that VirtualAlloc isn't called for + // every stub. Instead we should re-use each allocation for + // multiple stubs. In practice I'm guessing that there would + // only be one allocation per process, since each allocation actually + // allocates at least one page of memory (4K). Size of FunctionStub + // is 12 bytes, so one page could house 341 function stubs. + // When stubs are created frequently, VirtualAlloc could fail + // and last error is ERROR_NOT_ENOUGH_MEMORY (8). + static FunctionStub* Create(uintptr_t extra_argument, void* dest) { + DCHECK(dest); + + // Use VirtualAlloc to get memory for the stub. This gives us a + // whole page that we don't share with anyone else. + // Initially the memory must be READWRITE to allow for construction + // PAGE_EXECUTE is set in constructor. + FunctionStub* ret = reinterpret_cast<FunctionStub*>(VirtualAlloc(NULL, + sizeof(FunctionStub), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); + + if (!ret) { + NOTREACHED(); + } else { + // Construct + ret->FunctionStub::FunctionStub(extra_argument, dest); + } + + return ret; + } + + static FunctionStub* FromCode(void* address) { + Opcodes* code = reinterpret_cast<Opcodes*>(address); + if (code->hook_.pop_return_addr_ == POP_EAX) { + FunctionStub* stub = reinterpret_cast<FunctionStub*>( + reinterpret_cast<uint8*>(address) - sizeof(HMODULE)); + if (stub->is_valid()) + return stub; + } + + return NULL; + } + + // Deallocates a FunctionStub. The stub must not be in use on any thread! + static bool Destroy(FunctionStub* stub) { + DCHECK(stub != NULL); + FunctionStub* to_free = reinterpret_cast<FunctionStub*>(stub); + to_free->FunctionStub::~FunctionStub(); + BOOL success = VirtualFree(to_free, sizeof(FunctionStub), MEM_DECOMMIT); + DCHECK(success) << "VirtualFree"; + return success != FALSE; + } +}; + +#pragma pack(pop) + +#endif // CHROME_FRAME_FUNCTION_STUB_H_ diff --git a/chrome_frame/host.html b/chrome_frame/host.html new file mode 100644 index 0000000..a3da73c --- /dev/null +++ b/chrome_frame/host.html @@ -0,0 +1,47 @@ +<HTML> +<!-- TODO(slightlyoff): Move to tests directory? --> +<HEAD> +<TITLE> Chrome Frame Test </TITLE> +<SCRIPT type="text/javascript"> +function GetChromeFrame() { + var chromeFrame = window.document.ChromeFrame + return chromeFrame; +} + +function OnChromeFrameMessage(text) { + window.alert("In host: \r\nMessage from ChromeFrame: " + text); + + var chromeFrame = GetChromeFrame(); + chromeFrame.PostMessageToFrame("Hello from host"); + return "OK"; +} + +function OnNavigate() { + var url = document.getElementById('inputurl'); + var chromeFrame = GetChromeFrame(); + chromeFrame.src = url.value; +} + +function onLoad() { + var chromeFrame = GetChromeFrame(); + chromeFrame.onmessage = OnChromeFrameMessage; +} + +</SCRIPT> +</HEAD> +<BODY onload="onLoad();"> +Chrome Frame Test activex +<br><br> +<input id="inputurl" type="text" name="URL"> +<input type="submit" value="Navigate" onClick="OnNavigate();"> +<center> +<OBJECT ID="ChromeFrame" WIDTH=500 HEIGHT=500 CODEBASE="http://www.google.com" + CLASSID="CLSID:E0A900DF-9611-4446-86BD-4B1D47E7DB2A"> + <PARAM NAME="src" VALUE="http://www.google.com"> + <embed ID="ChromeFramePlugin" WIDTH=500 HEIGHT=500 NAME="ChromeFrame" + SRC="http://www.google.com" TYPE="application/chromeframe"> + </embed> +</OBJECT> +</center> +</BODY> +</HTML> diff --git a/chrome_frame/host_w_controls.html b/chrome_frame/host_w_controls.html new file mode 100644 index 0000000..0e44fc5 --- /dev/null +++ b/chrome_frame/host_w_controls.html @@ -0,0 +1,97 @@ +<HTML> +<!-- TODO(slightlyoff): Move to tests directory? --> +<HEAD> +<TITLE> Chrome Frame Test </TITLE> +<SCRIPT type="text/javascript"> +function msg(txt) { + try { + document.getElementById("my_text").innerHTML = txt; + } catch(e) { + alert("error"); + } +} + +function GetChromeFrame() { + var chromeFrame = window.document.ChromeFrame + return chromeFrame; +} + +function OnChromeFrameMessage(text) { + msg("In host: \r\nMessage from ChromeFrame: " + text); + + var chromeFrame = GetChromeFrame(); + chromeFrame.PostMessageToFrame("OnHostMessage", "Hello from host"); + return "OK"; +} + +function OnNavigate() { + var url = document.getElementById('inputurl'); + GetChromeFrame().src = url.value; +} + +function OnFocus() { + msg("OnFocus"); +} + +window.onload = function() { + var chromeFrame = GetChromeFrame(); + var url = location.href; + url = url.substr(0, url.lastIndexOf('/') + 1) + "frame_w_controls.html"; + chromeFrame.src = url; + + try { + var cf = document.getElementById('ChromeFrame'); + cf.addEventListener("focus", OnFocus, true); + cf.addEventListener("blur", function() { msg('blur'); }, true); + msg("ready"); + } catch(e) { + alert("error"); + } +} + +function setFocusToCf() { + var cf = document.getElementById('ChromeFrame'); + cf.focus(); + // alert(cf.hasFocus()); + return true; +} + +</SCRIPT> +<style> +/* CSS magic to avoid the focus rect */ +object:focus { + outline: 0; +} +</style> +<!-- +object:focus { outline: none; } +:focus { outline: none } +a:focus { outline: 1px dotted invert } +--> +</HEAD> +<BODY> +Chrome Frame Test activex +<br><br> +<input id="inputurl" type="text" name="URL"> +<input type="submit" value="Navigate" onClick="OnNavigate();"> +<center> +<OBJECT ID="ChromeFrame" tabindex="0" + WIDTH="500" + HEIGHT="300" + CODEBASE="http://www.google.com" + CLASSID="CLSID:E0A900DF-9611-4446-86BD-4B1D47E7DB2A"> + <!-- <PARAM NAME="BackColor" VALUE="100"> --> + <!-- <PARAM NAME="src" VALUE="file:///z:/code/debug/test.html"> --> + <embed ID="ChromeFramePlugin" WIDTH=500 HEIGHT=300 NAME="ChromeFrame" + SRC="http://www.google.com" TYPE="application/chromeframe"> + </embed> +</OBJECT> +<p>To test the focus: <input id="fake_edit" type="text" name="fake"></p> +<p><button onclick="return setFocusToCf();">SetFocusToCF</button></p> +<p> +Message:<br> +<pre><p id="my_text"></p></pre> +</p> +</center> +</BODY> +</HTML> diff --git a/chrome_frame/html_utils.cc b/chrome_frame/html_utils.cc new file mode 100644 index 0000000..60fdd7e --- /dev/null +++ b/chrome_frame/html_utils.cc @@ -0,0 +1,281 @@ +// Copyright (c) 2009 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.
+//
+#include "chrome_frame/html_utils.h"
+
+#include "base/string_util.h"
+#include "base/string_tokenizer.h"
+
+const wchar_t* kQuotes = L"\"'";
+
+HTMLScanner::StringRange::StringRange() {
+}
+
+HTMLScanner::StringRange::StringRange(StrPos start, StrPos end)
+ : start_(start), end_(end) {
+}
+
+bool HTMLScanner::StringRange::LowerCaseEqualsASCII(const char* other) const {
+ return ::LowerCaseEqualsASCII(start_, end_, other);
+}
+
+bool HTMLScanner::StringRange::Equals(const wchar_t* other) const {
+ int ret = wcsncmp(&start_[0], other, end_ - start_);
+ if (ret == 0)
+ ret = (other[end_ - start_] == L'\0') ? 0 : -1;
+ return ret == 0;
+}
+
+std::wstring HTMLScanner::StringRange::Copy() const {
+ return std::wstring(start_, end_);
+}
+
+bool HTMLScanner::StringRange::GetTagName(std::wstring* tag_name) const {
+ if (*start_ != L'<') {
+ LOG(ERROR) << "Badly formatted tag found";
+ return false;
+ }
+
+ StrPos name_start = start_;
+ name_start++;
+ while (name_start < end_ && IsWhitespace(*name_start))
+ name_start++;
+
+ if (name_start >= end_) {
+ // We seem to have a degenerate tag (i.e. < >). Return false here.
+ return false;
+ }
+
+ StrPos name_end = name_start + 1;
+ while (name_end < end_ && !IsWhitespace(*name_end))
+ name_end++;
+
+ if (name_end > end_) {
+ // This looks like an improperly formatted tab ('<foo'). Return false here.
+ return false;
+ }
+
+ tag_name->assign(name_start, name_end);
+ return true;
+}
+
+
+bool HTMLScanner::StringRange::GetTagAttribute(const wchar_t* attribute_name,
+ StringRange* attribute_value) const {
+ if (NULL == attribute_name || NULL == attribute_value) {
+ NOTREACHED();
+ return false;
+ }
+
+ // Use this so we can use the convenience method LowerCaseEqualsASCII()
+ // from string_util.h.
+ std::string search_name_ascii(WideToASCII(attribute_name));
+
+ WStringTokenizer tokenizer(start_, end_, L" =/");
+ tokenizer.set_options(WStringTokenizer::RETURN_DELIMS);
+
+ // Set up the quote chars so that we get quoted attribute values as single
+ // tokens.
+ tokenizer.set_quote_chars(L"\"'");
+
+ const bool PARSE_STATE_NAME = true;
+ const bool PARSE_STATE_VALUE = false;
+ bool parse_state = PARSE_STATE_NAME;
+
+ // Used to skip the first token, which is the tag name.
+ bool first_token_skipped = false;
+
+ // This is set during a loop iteration in which an '=' sign was spotted.
+ // It is used to filter out degenerate tags such as:
+ // <meta foo==bar>
+ bool last_token_was_delim = false;
+
+ // Set this if the attribute name has been found that we might then
+ // pick up the value in the next loop iteration.
+ bool attribute_name_found = false;
+
+ while (tokenizer.GetNext()) {
+ // If we have a whitespace delimiter, just keep going. Cases of this should
+ // be reduced by the CollapseWhitespace call. If we have an '=' character,
+ // we update our state and reiterate.
+ if (tokenizer.token_is_delim()) {
+ if (*tokenizer.token_begin() == L'=') {
+ if (last_token_was_delim) {
+ // Looks like we have a badly formed tag, just stop parsing now.
+ return false;
+ }
+ parse_state = !parse_state;
+ last_token_was_delim = true;
+ }
+ continue;
+ }
+
+ last_token_was_delim = false;
+
+ // The first non-delimiter token is the tag name, which we don't want.
+ if (!first_token_skipped) {
+ first_token_skipped = true;
+ continue;
+ }
+
+ if (PARSE_STATE_NAME == parse_state) {
+ // We have a tag name, check to see if it matches our target name:
+ if (::LowerCaseEqualsASCII(tokenizer.token_begin(), tokenizer.token_end(),
+ search_name_ascii.c_str())) {
+ attribute_name_found = true;
+ continue;
+ }
+ } else if (PARSE_STATE_VALUE == parse_state && attribute_name_found) {
+ attribute_value->start_ = tokenizer.token_begin();
+ attribute_value->end_ = tokenizer.token_end();
+
+ // Unquote the attribute value if need be.
+ attribute_value->UnQuote();
+
+ return true;
+ } else if (PARSE_STATE_VALUE == parse_state) {
+ // If we haven't found the attribute name we want yet, ignore this token
+ // and go back to looking for our name.
+ parse_state = PARSE_STATE_NAME;
+ }
+ }
+
+ return false;
+}
+
+bool HTMLScanner::StringRange::UnQuote() {
+ if (start_ + 2 > end_) {
+ // String's too short to be quoted, bail.
+ return false;
+ }
+
+ if ((*start_ == L'\'' && *(end_ - 1) == L'\'') ||
+ (*start_ == L'"' && *(end_ - 1) == L'"')) {
+ start_ = start_ + 1;
+ end_ = end_ - 1;
+ return true;
+ }
+
+ return false;
+}
+
+HTMLScanner::HTMLScanner(const wchar_t* html_string)
+ : html_string_(CollapseWhitespace(html_string, true)),
+ quotes_(kQuotes) {
+}
+
+void HTMLScanner::GetTagsByName(const wchar_t* name, StringRangeList* tag_list,
+ const wchar_t* stop_tag) {
+ DCHECK(NULL != name);
+ DCHECK(NULL != tag_list);
+ DCHECK(NULL != stop_tag);
+
+ StringRange remaining_html(html_string_.begin(), html_string_.end());
+
+ std::wstring search_name(name);
+ TrimWhitespace(search_name, TRIM_ALL, &search_name);
+
+ // Use this so we can use the convenience method LowerCaseEqualsASCII()
+ // from string_util.h.
+ std::string search_name_ascii(WideToASCII(search_name));
+ std::string stop_tag_ascii(WideToASCII(stop_tag));
+
+ StringRange current_tag;
+ std::wstring current_name;
+ while (NextTag(&remaining_html, ¤t_tag)) {
+ if (current_tag.GetTagName(¤t_name)) {
+ if (LowerCaseEqualsASCII(current_name, search_name_ascii.c_str())) {
+ tag_list->push_back(current_tag);
+ } else if (LowerCaseEqualsASCII(current_name, stop_tag_ascii.c_str())) {
+ // We hit the stop tag so it's time to go home.
+ break;
+ }
+ }
+ }
+}
+
+struct ScanState {
+ bool in_quote;
+ bool in_escape;
+ wchar_t quote_char;
+ ScanState() : in_quote(false), in_escape(false) {}
+};
+
+bool HTMLScanner::IsQuote(wchar_t c) {
+ return quotes_.find(c) != std::wstring::npos;
+}
+
+bool HTMLScanner::IsHTMLCommentClose(StringRange* html_string, StrPos pos) {
+ if (pos < html_string->end_ && pos > html_string->start_ + 2 &&
+ *pos == L'>') {
+ return *(pos-1) == L'-' && *(pos-2) == L'-';
+ }
+ return false;
+}
+
+bool HTMLScanner::NextTag(StringRange* html_string, StringRange* tag) {
+ DCHECK(NULL != html_string);
+ DCHECK(NULL != tag);
+
+ tag->start_ = html_string->start_;
+ while (tag->start_ < html_string->end_ && *tag->start_ != L'<') {
+ tag->start_++;
+ }
+
+ // we went past the end of the string.
+ if (tag->start_ >= html_string->end_) {
+ return false;
+ }
+
+ tag->end_ = tag->start_ + 1;
+
+ // Get the tag name to see if we are in an HTML comment. If we are, then
+ // don't consider quotes. This should work for example:
+ // <!-- foo ' --> <meta foo='bar'>
+ std::wstring tag_name;
+ StringRange start_range(tag->start_, html_string->end_);
+ start_range.GetTagName(&tag_name);
+ if (StartsWith(tag_name, L"!--", true)) {
+ // We're inside a comment tag, keep going until we get out of it.
+ while (tag->end_ < html_string->end_ &&
+ !IsHTMLCommentClose(html_string, tag->end_)) {
+ tag->end_++;
+ }
+ } else {
+ // Properly handle quoted strings within non-comment tags by maintaining
+ // some state while scanning. Specifically, we have to maintain state on
+ // whether we are inside a string, what the string terminating character
+ // will be and whether we are inside an escape sequence.
+ ScanState state;
+ while (tag->end_ < html_string->end_) {
+ if (state.in_quote) {
+ if (state.in_escape) {
+ state.in_escape = false;
+ } else if (*tag->end_ == '\\') {
+ state.in_escape = true;
+ } else if (*tag->end_ == state.quote_char) {
+ state.in_quote = false;
+ }
+ } else {
+ state.in_quote = IsQuote(state.quote_char = *tag->end_);
+ }
+
+ if (!state.in_quote && *tag->end_ == L'>') {
+ break;
+ }
+ tag->end_++;
+ }
+ }
+
+ // We hit the end_ but found no matching tag closure. Consider this an
+ // incomplete tag and do not report it.
+ if (tag->end_ >= html_string->end_)
+ return false;
+
+ // Modify html_string to point to just beyond the end_ of the current tag.
+ html_string->start_ = tag->end_ + 1;
+
+ return true;
+}
+
diff --git a/chrome_frame/html_utils.h b/chrome_frame/html_utils.h new file mode 100644 index 0000000..c670b00 --- /dev/null +++ b/chrome_frame/html_utils.h @@ -0,0 +1,120 @@ +// Copyright (c) 2009 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 utility functions for working with html.
+
+#ifndef CHROME_FRAME_HTML_UTILS_H_
+#define CHROME_FRAME_HTML_UTILS_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "testing/gtest/include/gtest/gtest_prod.h"
+
+// Forward declarations
+class HtmlUtilUnittest;
+
+//
+// Class designed to take a string of HTML and extract from it named
+// attribute values from named tags.
+//
+// Caveat: this class currently doesn't handle multi-word UTF-16 encoded
+// characters. Doesn't handle implies that any data following such a
+// character could possibly be misinterpreted.
+//
+class HTMLScanner {
+ public:
+ typedef std::wstring::const_iterator StrPos;
+
+ // Structure maintaining const_iterators into html_string_.
+ class StringRange {
+ friend class HTMLScanner;
+ public:
+ StringRange();
+ StringRange(StrPos start, StrPos end);
+
+ bool LowerCaseEqualsASCII(const char* other) const;
+ bool Equals(const wchar_t* other) const;
+
+ // Copies the data described by StringRange into destination.
+ std::wstring Copy() const;
+
+ // If this StringRange represents a tag, this method extracts the name of
+ // the tag and sticks it in tag_name.
+ // Returns true if the tag name was successfully extracted.
+ // Returns false if this string doesn't look like a valid tag.
+ bool GetTagName(std::wstring* tag_name) const;
+
+ // From a given string range, uses a string tokenizer to extract the value
+ // of the named attribute if a simple scan finds that the attribute name is
+ // present.
+ //
+ // Returns true if the named attribute can be located and it has a value
+ // which has been placed in attribute_value.
+ //
+ // Note that the attribute value is unquoted here as well, so that
+ // GetTagAttribute(*<foo bar="baz">*, L"bar", *out_value*) will stick
+ // 'bar' in out_value and not '"bar"'.
+ //
+ // Returns false if the named attribute is not present in the tag or if it
+ // did not have a value.
+ //
+ bool GetTagAttribute(const wchar_t* attribute_name,
+ StringRange* attribute_value) const;
+
+ // Unquotes a StringRange by removing a matching pair of either ' or "
+ // characters from the beginning and end of the string if present.
+ // Returns true if string was modified, false otherwise.
+ bool UnQuote();
+ private:
+ StrPos start_;
+ StrPos end_;
+ };
+
+ typedef std::vector<StringRange> StringRangeList;
+
+ // html_string must be a null-terminated string containing the HTML
+ // to be scanned.
+ explicit HTMLScanner(const wchar_t* html_string);
+
+ // Returns the set of ranges denoting HTML tags that match the given name.
+ // If stop_tag_name is given, then as soon as a tag with this name is
+ // encountered this method will return.
+ void GetTagsByName(const wchar_t* name, StringRangeList* tag_list,
+ const wchar_t* stop_tag_name);
+
+ private:
+ friend class HtmlUtilUnittest;
+ FRIEND_TEST(HtmlUtilUnittest, BasicTest);
+
+ // Given html_string which represents the remaining html range, this method
+ // returns the next tag in tag and advances html_string to one character after
+ // the end of tag. This method is intended to be called repeatedly to extract
+ // all of the tags in sequence.
+ //
+ // Returns true if another tag was found and 'tag' was populated with a valid
+ // range.
+ // Returns false if we have reached the end of the html data.
+ bool NextTag(StringRange* html_string, StringRange* tag);
+
+ // Returns true if c can be found in quotes_, false otherwise
+ bool IsQuote(wchar_t c);
+
+ // Returns true if pos refers to the last character in an HTML comment in a
+ // string described by html_string, false otherwise.
+ // For example with html_string describing <!-- foo> -->, pos must refer to
+ // the last > for this method to return true.
+ bool IsHTMLCommentClose(StringRange* html_string, StrPos pos);
+
+ // We store a (CollapsedWhitespace'd) copy of the html data.
+ const std::wstring html_string_;
+
+ // Store the string of quote characters to avoid repeated construction.
+ const std::wstring quotes_;
+
+ DISALLOW_COPY_AND_ASSIGN(HTMLScanner);
+};
+
+#endif // CHROME_FRAME_HTML_UTILS_H_
diff --git a/chrome_frame/icu_stubs.cc b/chrome_frame/icu_stubs.cc new file mode 100644 index 0000000..2f3abcf --- /dev/null +++ b/chrome_frame/icu_stubs.cc @@ -0,0 +1,169 @@ +// Copyright (c) 2009 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. + +#include "base/logging.h" +#include "base/string_util.h" +#include "googleurl/src/url_canon.h" + +#include <windows.h> + +//////////////////////////////////////////////////////////////////////////////// +// Avoid dependency on string_util_icu.cc (which pulls in icu). + +std::string WideToAnsiDirect(const wchar_t* wide, size_t wide_len) { + std::string ret; + char* write = WriteInto(&ret, wide_len + 1); + for (size_t i = 0; i < wide_len; ++i) { + // We can only convert characters below 0x80 directly from wide to ansi. + DCHECK(wide[i] <= 127) << "can't convert"; + write[i] = static_cast<char>(wide[i]); + } + + write[wide_len] = '\0'; + + return ret; +} + +bool WideToUTF8(const wchar_t* wide, size_t wide_len, std::string* utf8) { + DCHECK(utf8); + + // Add a cutoff. If it's all ASCII, convert it directly + size_t i; + for (i = 0; i < wide_len; ++i) { + if (wide[i] > 127) + break; + } + + // If we made it to the end without breaking, then it's all ANSI, so do a + // quick convert + if (i == wide_len) { + *utf8 = WideToAnsiDirect(wide, wide_len); + return true; + } + + // Figure out how long the string is + int size = WideCharToMultiByte(CP_UTF8, 0, wide, wide_len + 1, NULL, 0, NULL, + NULL); + + if (size > 0) { + WideCharToMultiByte(CP_UTF8, 0, wide, wide_len + 1, WriteInto(utf8, size), + size, NULL, NULL); + } + + return (size > 0); +} + +std::string WideToUTF8(const std::wstring& wide) { + std::string ret; + if (!wide.empty()) { + // Ignore the success flag of this call, it will do the best it can for + // invalid input, which is what we want here. + WideToUTF8(wide.data(), wide.length(), &ret); + } + return ret; +} + +bool UTF8ToWide(const char* src, size_t src_len, std::wstring* output) { + DCHECK(output); + + if (src_len == 0) { + output->clear(); + return true; + } + + int wide_chars = MultiByteToWideChar(CP_UTF8, 0, src, src_len, NULL, 0); + if (!wide_chars) { + NOTREACHED(); + return false; + } + + wide_chars++; // make room for L'\0' + // Note that WriteInto will fill the string with '\0', so in the case + // where the input string is not \0 terminated, we will still be ensured + // that the output string will be. + if (!MultiByteToWideChar(CP_UTF8, 0, src, src_len, + WriteInto(output, wide_chars), wide_chars)) { + NOTREACHED(); + output->clear(); + return false; + } + + return true; +} + +std::wstring UTF8ToWide(const base::StringPiece& utf8) { + std::wstring ret; + if (!utf8.empty()) + UTF8ToWide(utf8.data(), utf8.length(), &ret); + return ret; +} + +#ifdef WCHAR_T_IS_UTF16 +string16 UTF8ToUTF16(const std::string& utf8) { + std::wstring ret; + if (!utf8.empty()) + UTF8ToWide(utf8.data(), utf8.length(), &ret); + return ret; +} +#else +#error Need WCHAR_T_IS_UTF16 +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Replace ICU dependent functions in googleurl. +/*#define __UTF_H__ +#include "third_party/icu38/public/common/unicode/utf16.h" +#define U_IS_SURROGATE(c) (((c)&0xfffff800)==0xd800) +extern const char16 kUnicodeReplacementCharacter;*/ + +namespace url_canon { + +bool IDNToASCII(const char16* src, int src_len, CanonOutputW* output) { + // We should only hit this when the user attempts to navigate + // CF to an invalid URL. + DLOG(WARNING) << __FUNCTION__ << " not implemented"; + return false; +} + +bool ReadUTFChar(const char* str, int* begin, int length, + unsigned* code_point_out) { + // We should only hit this when the user attempts to navigate + // CF to an invalid URL. + DLOG(WARNING) << __FUNCTION__ << " not implemented"; + + // TODO(tommi): consider if we can use something like + // http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ + return false; +} + +bool ReadUTFChar(const char16* str, int* begin, int length, + unsigned* code_point) { +/* + if (U16_IS_SURROGATE(str[*begin])) { + if (!U16_IS_SURROGATE_LEAD(str[*begin]) || *begin + 1 >= length || + !U16_IS_TRAIL(str[*begin + 1])) { + // Invalid surrogate pair. + *code_point = kUnicodeReplacementCharacter; + return false; + } else { + // Valid surrogate pair. + *code_point = U16_GET_SUPPLEMENTARY(str[*begin], str[*begin + 1]); + (*begin)++; + } + } else { + // Not a surrogate, just one 16-bit word. + *code_point = str[*begin]; + } + + if (U_IS_UNICODE_CHAR(*code_point)) + return true; + + // Invalid code point. + *code_point = kUnicodeReplacementCharacter; + return false;*/ + CHECK(false); + return false; +} + +} // namespace url_canon diff --git a/chrome_frame/ie8_types.h b/chrome_frame/ie8_types.h new file mode 100644 index 0000000..c683471 --- /dev/null +++ b/chrome_frame/ie8_types.h @@ -0,0 +1,27 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_FRAME_IE8_TYPES_H_ +#define CHROME_FRAME_IE8_TYPES_H_ + +#include <urlmon.h> + +#ifndef __IInternetBindInfoEx_INTERFACE_DEFINED__ +#define __IInternetBindInfoEx_INTERFACE_DEFINED__ + +#define IID_IInternetBindInfoEx (__uuidof(IInternetBindInfoEx)) + +MIDL_INTERFACE("A3E015B7-A82C-4DCD-A150-569AEEED36AB") +IInternetBindInfoEx : public IInternetBindInfo { + public: + virtual HRESULT STDMETHODCALLTYPE GetBindInfoEx( + DWORD *grfBINDF, + BINDINFO *pbindinfo, + DWORD *grfBINDF2, + DWORD *pdwReserved) = 0; +}; + +#endif __IInternetBindInfoEx_INTERFACE_DEFINED__ + +#endif // CHROME_FRAME_IE8_TYPES_H_ diff --git a/chrome_frame/iids.cc b/chrome_frame/iids.cc new file mode 100644 index 0000000..681250a --- /dev/null +++ b/chrome_frame/iids.cc @@ -0,0 +1,10 @@ +// Copyright (c) 2009 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. + +// Pull in IIDs, CLSIDs etc from our .idl file. +#include "chrome_tab.h" + +extern "C" { +#include "chrome_tab_i.c" +} diff --git a/chrome_frame/in_place_menu.h b/chrome_frame/in_place_menu.h new file mode 100644 index 0000000..9742896 --- /dev/null +++ b/chrome_frame/in_place_menu.h @@ -0,0 +1,231 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef CHROME_FRAME_IN_PLACE_MENU_H_ +#define CHROME_FRAME_IN_PLACE_MENU_H_ + +// in_place_menu.h : menu merging implementation +// +// This file is a modified version of the menu.h file, which is +// part of the ActiveDoc MSDN sample. The modifications are largely +// conversions to Google coding guidelines. Below is the original header +// from the file. + +// This is a part of the Active Template Library. +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// This source code is only intended as a supplement to the +// Active Template Library Reference and related +// electronic documentation provided with the library. +// See these sources for detailed information regarding the +// Active Template Library product. + +#include "base/logging.h" +#include "base/scoped_comptr_win.h" + +template <class T> +class InPlaceMenu { + public: + InPlaceMenu() : shared_menu_(NULL), ole_menu_(NULL), our_menu_(NULL) { + } + + ~InPlaceMenu() { + InPlaceMenuDestroy(); + } + + HRESULT InPlaceMenuCreate(LPCWSTR menu_name) { + // We might already have an in-place menu set, because we set menus + // IOleDocumentView::UIActivate as well as in + // IOleInPlaceActiveObject::OnDocWindowActivate. If we have already + // done our work, just return silently + if (ole_menu_ || shared_menu_) + return S_OK; + + ScopedComPtr<IOleInPlaceFrame> in_place_frame; + GetInPlaceFrame(in_place_frame.Receive()); + // We have no IOleInPlaceFrame, no menu merging possible + if (!in_place_frame) { + NOTREACHED(); + return E_FAIL; + } + // Create a blank menu and ask the container to add + // its menus into the OLEMENUGROUPWIDTHS structure + shared_menu_ = ::CreateMenu(); + OLEMENUGROUPWIDTHS mgw = {0}; + HRESULT hr = in_place_frame->InsertMenus(shared_menu_, &mgw); + if (FAILED(hr)) { + ::DestroyMenu(shared_menu_); + shared_menu_ = NULL; + return hr; + } + // Insert our menus + our_menu_ = LoadMenu(_AtlBaseModule.GetResourceInstance(),menu_name); + MergeMenus(shared_menu_, our_menu_, &mgw.width[0], 1); + // Send the menu to the client + ole_menu_ = (HMENU)OleCreateMenuDescriptor(shared_menu_, &mgw); + T* t = static_cast<T*>(this); + in_place_frame->SetMenu(shared_menu_, ole_menu_, t->m_hWnd); + return S_OK; + } + + HRESULT InPlaceMenuDestroy() { + ScopedComPtr<IOleInPlaceFrame> in_place_frame; + GetInPlaceFrame(in_place_frame.Receive()); + if (in_place_frame) { + in_place_frame->RemoveMenus(shared_menu_); + in_place_frame->SetMenu(NULL, NULL, NULL); + } + if (ole_menu_) { + OleDestroyMenuDescriptor(ole_menu_); + ole_menu_ = NULL; + } + if (shared_menu_) { + UnmergeMenus(shared_menu_, our_menu_); + DestroyMenu(shared_menu_); + shared_menu_ = NULL; + } + if (our_menu_) { + DestroyMenu(our_menu_); + our_menu_ = NULL; + } + return S_OK; + } + + void MergeMenus(HMENU shared_menu, HMENU source_menu, LONG* menu_widths, + unsigned int width_index) { + // Copy the popups from the source menu + // Insert at appropriate spot depending on width_index + DCHECK(width_index == 0 || width_index == 1); + int position = 0; + if (width_index == 1) + position = (int)menu_widths[0]; + int group_width = 0; + int menu_items = GetMenuItemCount(source_menu); + for (int index = 0; index < menu_items; index++) { + // Get the HMENU of the popup + HMENU popup_menu = ::GetSubMenu(source_menu, index); + // Separators move us to next group + UINT state = GetMenuState(source_menu, index, MF_BYPOSITION); + if (!popup_menu && (state & MF_SEPARATOR)) { + // Servers should not touch past 5 + DCHECK(width_index <= 5); + menu_widths[width_index] = group_width; + group_width = 0; + if (width_index < 5) + position += static_cast<int>(menu_widths[width_index+1]); + width_index += 2; + } else { + // Get the menu item text + TCHAR item_text[256] = {0}; + int text_length = GetMenuString(source_menu, index, item_text, + ARRAYSIZE(item_text), MF_BYPOSITION); + // Popups are handled differently than normal menu items + if (popup_menu) { + if (::GetMenuItemCount(popup_menu) != 0) { + // Strip the HIBYTE because it contains a count of items + state = LOBYTE(state) | MF_POPUP; // Must be popup + // Non-empty popup -- add it to the shared menu bar + InsertMenu(shared_menu, position, state|MF_BYPOSITION, + reinterpret_cast<UINT_PTR>(popup_menu), item_text); + ++position; + ++group_width; + } + } else if (text_length > 0) { + // only non-empty items are added + DCHECK(item_text[0] != 0); + // here the state does not contain a count in the HIBYTE + InsertMenu(shared_menu, position, state|MF_BYPOSITION, + GetMenuItemID(source_menu, index), item_text); + ++position; + ++group_width; + } + } + } + } + + void UnmergeMenus(HMENU shared_menu, HMENU source_menu) { + int our_item_count = GetMenuItemCount(source_menu); + int shared_item_count = GetMenuItemCount(shared_menu); + + for (int index = shared_item_count - 1; index >= 0; index--) { + // Check the popup menus + HMENU popup_menu = ::GetSubMenu(shared_menu, index); + if (popup_menu) { + // If it is one of ours, remove it from the shared menu + for (int sub_index = 0; sub_index < our_item_count; sub_index++) { + if (::GetSubMenu(source_menu, sub_index) == popup_menu) { + // Remove the menu from hMenuShared + RemoveMenu(shared_menu, index, MF_BYPOSITION); + break; + } + } + } + } + } + + protected: + HRESULT GetInPlaceFrame(IOleInPlaceFrame** in_place_frame) { + if (!in_place_frame) { + NOTREACHED(); + return E_POINTER; + } + T* t = static_cast<T*>(this); + HRESULT hr = E_FAIL; + if (!t->in_place_frame_) { + // We weren't given an IOleInPlaceFrame pointer, so + // we'll have to get it ourselves. + if (t->m_spInPlaceSite) { + t->frame_info_.cb = sizeof(OLEINPLACEFRAMEINFO); + ScopedComPtr<IOleInPlaceUIWindow> in_place_ui_window; + RECT position_rect = {0}; + RECT clip_rect = {0}; + hr = t->m_spInPlaceSite->GetWindowContext(in_place_frame, + in_place_ui_window.Receive(), + &position_rect, &clip_rect, + &t->frame_info_); + } + } else { + *in_place_frame = t->in_place_frame_; + (*in_place_frame)->AddRef(); + hr = S_OK; + } + return hr; + } + + protected: + // The OLE menu descriptor created by the OleCreateMenuDescriptor + HMENU ole_menu_; + // The shared menu that we pass to IOleInPlaceFrame::SetMenu + HMENU shared_menu_; + // Our menu resource that we want to insert + HMENU our_menu_; +}; + +#endif // CHROME_FRAME_IN_PLACE_MENU_H_ + diff --git a/chrome_frame/installer_util/test.txt b/chrome_frame/installer_util/test.txt new file mode 100644 index 0000000..30d74d2 --- /dev/null +++ b/chrome_frame/installer_util/test.txt @@ -0,0 +1 @@ +test
\ No newline at end of file diff --git a/chrome_frame/np_browser_functions.cc b/chrome_frame/np_browser_functions.cc new file mode 100644 index 0000000..9cc6f77 --- /dev/null +++ b/chrome_frame/np_browser_functions.cc @@ -0,0 +1,512 @@ +// Copyright (c) 2009 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. + +#include "chrome_frame/np_browser_functions.h" + +#include "base/logging.h" + +namespace npapi { + +// global function pointers (within this namespace) for the NPN functions. +union NpVersion { + struct { + uint8 major; + uint8 minor; + } v; + uint16 version; +}; + +NpVersion g_version = {0}; + +NPN_GetURLProcPtr g_geturl = NULL; +NPN_PostURLProcPtr g_posturl = NULL; +NPN_RequestReadProcPtr g_requestread = NULL; +NPN_NewStreamProcPtr g_newstream = NULL; +NPN_WriteProcPtr g_write = NULL; +NPN_DestroyStreamProcPtr g_destroystream = NULL; +NPN_StatusProcPtr g_status = NULL; +NPN_UserAgentProcPtr g_useragent = NULL; +NPN_MemAllocProcPtr g_memalloc = NULL; +NPN_MemFreeProcPtr g_memfree = NULL; +NPN_MemFlushProcPtr g_memflush = NULL; +NPN_ReloadPluginsProcPtr g_reloadplugins = NULL; +NPN_GetJavaEnvProcPtr g_getJavaEnv = NULL; +NPN_GetJavaPeerProcPtr g_getJavaPeer = NULL; +NPN_GetURLNotifyProcPtr g_geturlnotify = NULL; +NPN_PostURLNotifyProcPtr g_posturlnotify = NULL; +NPN_GetValueProcPtr g_getvalue = NULL; +NPN_SetValueProcPtr g_setvalue = NULL; +NPN_InvalidateRectProcPtr g_invalidaterect = NULL; +NPN_InvalidateRegionProcPtr g_invalidateregion = NULL; +NPN_ForceRedrawProcPtr g_forceredraw = NULL; +NPN_GetStringIdentifierProcPtr g_getstringidentifier = NULL; +NPN_GetStringIdentifiersProcPtr g_getstringidentifiers = NULL; +NPN_GetIntIdentifierProcPtr g_getintidentifier = NULL; +NPN_IdentifierIsStringProcPtr g_identifierisstring = NULL; +NPN_UTF8FromIdentifierProcPtr g_utf8fromidentifier = NULL; +NPN_IntFromIdentifierProcPtr g_intfromidentifier = NULL; +NPN_CreateObjectProcPtr g_createobject = NULL; +NPN_RetainObjectProcPtr g_retainobject = NULL; +NPN_ReleaseObjectProcPtr g_releaseobject = NULL; +NPN_InvokeProcPtr g_invoke = NULL; +NPN_InvokeDefaultProcPtr g_invoke_default = NULL; +NPN_EvaluateProcPtr g_evaluate = NULL; +NPN_GetPropertyProcPtr g_getproperty = NULL; +NPN_SetPropertyProcPtr g_setproperty = NULL; +NPN_RemovePropertyProcPtr g_removeproperty = NULL; +NPN_HasPropertyProcPtr g_hasproperty = NULL; +NPN_HasMethodProcPtr g_hasmethod = NULL; +NPN_ReleaseVariantValueProcPtr g_releasevariantvalue = NULL; +NPN_SetExceptionProcPtr g_setexception = NULL; +NPN_PushPopupsEnabledStateProcPtr g_pushpopupsenabledstate = NULL; +NPN_PopPopupsEnabledStateProcPtr g_poppopupsenabledstate = NULL; +NPN_EnumerateProcPtr g_enumerate = NULL; +NPN_PluginThreadAsyncCallProcPtr g_pluginthreadasynccall = NULL; +NPN_ConstructProcPtr g_construct = NULL; +NPN_GetValueForURLProcPtr g_getvalueforurl = NULL; +NPN_SetValueForURLProcPtr g_setvalueforurl = NULL; +NPN_GetAuthenticationInfoProcPtr g_getauthenticationinfo = NULL; + +// Must be called prior to calling any of the browser functions below. +void InitializeBrowserFunctions(NPNetscapeFuncs* functions) { + CHECK(functions); + DCHECK(g_geturl == NULL || g_geturl == functions->geturl); + + g_version.version = functions->version; + + g_geturl = functions->geturl; + g_posturl = functions->posturl; + g_requestread = functions->requestread; + g_newstream = functions->newstream; + g_write = functions->write; + g_destroystream = functions->destroystream; + g_status = functions->status; + g_useragent = functions->uagent; + g_memalloc = functions->memalloc; + g_memfree = functions->memfree; + g_memflush = functions->memflush; + g_reloadplugins = functions->reloadplugins; + g_getJavaEnv = functions->getJavaEnv; + g_getJavaPeer = functions->getJavaPeer; + g_geturlnotify = functions->geturlnotify; + g_posturlnotify = functions->posturlnotify; + g_getvalue = functions->getvalue; + g_setvalue = functions->setvalue; + g_invalidaterect = functions->invalidaterect; + g_invalidateregion = functions->invalidateregion; + g_forceredraw = functions->forceredraw; + g_getstringidentifier = functions->getstringidentifier; + g_getstringidentifiers = functions->getstringidentifiers; + g_getintidentifier = functions->getintidentifier; + g_identifierisstring = functions->identifierisstring; + g_utf8fromidentifier = functions->utf8fromidentifier; + g_intfromidentifier = functions->intfromidentifier; + g_createobject = functions->createobject; + g_retainobject = functions->retainobject; + g_releaseobject = functions->releaseobject; + g_invoke = functions->invoke; + g_invoke_default = functions->invokeDefault; + g_evaluate = functions->evaluate; + g_getproperty = functions->getproperty; + g_setproperty = functions->setproperty; + g_removeproperty = functions->removeproperty; + g_hasproperty = functions->hasproperty; + g_hasmethod = functions->hasmethod; + g_releasevariantvalue = functions->releasevariantvalue; + g_setexception = functions->setexception; + g_pushpopupsenabledstate = functions->pushpopupsenabledstate; + g_poppopupsenabledstate = functions->poppopupsenabledstate; + g_enumerate = functions->enumerate; + g_pluginthreadasynccall = functions->pluginthreadasynccall; + g_construct = functions->construct; + + if (g_version.v.minor >= NPVERS_HAS_URL_AND_AUTH_INFO) { + g_getvalueforurl = functions->getvalueforurl; + g_setvalueforurl = functions->setvalueforurl; + g_getauthenticationinfo = functions->getauthenticationinfo; + } +} + +void UninitializeBrowserFunctions() { + g_version.version = 0; + + // We skip doing this in the official build as it doesn't serve much purpose +// during shutdown. The reason for it being here in the other types of builds +// is to spot potential browser bugs whereby the browser leaves living objects +// in our DLL after shutdown has been called. In theory those objects could +// trigger a call to the browser functions after shutdown has been called +// and for non official builds we want that to simply crash. +// For official builds we leave the function pointers around since they +// continue to valid. + g_geturl = NULL; + g_posturl = NULL; + g_requestread = NULL; + g_newstream = NULL; + g_write = NULL; + g_destroystream = NULL; + g_status = NULL; + g_useragent = NULL; + g_memalloc = NULL; + g_memfree = NULL; + g_memflush = NULL; + g_reloadplugins = NULL; + g_getJavaEnv = NULL; + g_getJavaPeer = NULL; + g_geturlnotify = NULL; + g_posturlnotify = NULL; + g_getvalue = NULL; + g_setvalue = NULL; + g_invalidaterect = NULL; + g_invalidateregion = NULL; + g_forceredraw = NULL; + g_getstringidentifier = NULL; + g_getstringidentifiers = NULL; + g_getintidentifier = NULL; + g_identifierisstring = NULL; + g_utf8fromidentifier = NULL; + g_intfromidentifier = NULL; + g_createobject = NULL; + g_retainobject = NULL; + g_releaseobject = NULL; + g_invoke = NULL; + g_invoke_default = NULL; + g_evaluate = NULL; + g_getproperty = NULL; + g_setproperty = NULL; + g_removeproperty = NULL; + g_hasproperty = NULL; + g_hasmethod = NULL; + g_releasevariantvalue = NULL; + g_setexception = NULL; + g_pushpopupsenabledstate = NULL; + g_poppopupsenabledstate = NULL; + g_enumerate = NULL; + g_pluginthreadasynccall = NULL; + g_construct = NULL; + g_getvalueforurl = NULL; + g_setvalueforurl = NULL; + g_getauthenticationinfo = NULL; +} + +bool IsInitialized() { + // We only check one function for convenience. + return g_getvalue != NULL; +} + +// Function stubs for functions that the host browser implements. +uint8 VersionMinor() { + return g_version.v.minor; +} + +uint8 VersionMajor() { + return g_version.v.major; +} + +NPError GetURL(NPP instance, const char* URL, const char* window) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_geturl(instance, URL, window); +} + +NPError PostURL(NPP instance, const char* URL, const char* window, uint32 len, + const char* buf, NPBool file) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_posturl(instance, URL, window, len, buf, file); +} + +NPError RequestRead(NPStream* stream, NPByteRange* rangeList) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_requestread(stream, rangeList); +} + +NPError NewStream(NPP instance, NPMIMEType type, const char* window, + NPStream** stream) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_newstream(instance, type, window, stream); +} + +int32 Write(NPP instance, NPStream* stream, int32 len, void* buffer) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_write(instance, stream, len, buffer); +} + +NPError DestroyStream(NPP instance, NPStream* stream, NPReason reason) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_destroystream(instance, stream, reason); +} + +void Status(NPP instance, const char* message) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_status(instance, message); +} + +const char* UserAgent(NPP instance) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_useragent(instance); +} + +void* MemAlloc(uint32 size) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_memalloc(size); +} + +void MemFree(void* ptr) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_memfree(ptr); +} + +uint32 MemFlush(uint32 size) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_memflush(size); +} + +void ReloadPlugins(NPBool reloadPages) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_reloadplugins(reloadPages); +} + +void* GetJavaEnv() { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_getJavaEnv(); +} + +void* GetJavaPeer(NPP instance) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_getJavaPeer(instance); +} + +NPError GetURLNotify(NPP instance, const char* URL, const char* window, + void* notifyData) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_geturlnotify(instance, URL, window, notifyData); +} + +NPError PostURLNotify(NPP instance, const char* URL, const char* window, + uint32 len, const char* buf, NPBool file, + void* notifyData) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_posturlnotify(instance, URL, window, len, buf, file, notifyData); +} + +NPError GetValue(NPP instance, NPNVariable variable, void* ret_value) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_getvalue(instance, variable, ret_value); +} + +NPError SetValue(NPP instance, NPPVariable variable, void* value) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_setvalue(instance, variable, value); +} + +void InvalidateRect(NPP instance, NPRect* rect) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_invalidaterect(instance, rect); +} + +void InvalidateRegion(NPP instance, NPRegion region) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_invalidateregion(instance, region); +} + +void ForceRedraw(NPP instance) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_forceredraw(instance); +} + +void ReleaseVariantValue(NPVariant* variant) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_releasevariantvalue(variant); +} + +NPIdentifier GetStringIdentifier(const NPUTF8* name) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_getstringidentifier(name); +} + +void GetStringIdentifiers(const NPUTF8** names, int32_t nameCount, + NPIdentifier* identifiers) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_getstringidentifiers(names, nameCount, identifiers); +} + +NPIdentifier GetIntIdentifier(int32_t intid) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_getintidentifier(intid); +} + +int32_t IntFromIdentifier(NPIdentifier identifier) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_intfromidentifier(identifier); +} + +bool IdentifierIsString(NPIdentifier identifier) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_identifierisstring(identifier); + +} + +NPUTF8* UTF8FromIdentifier(NPIdentifier identifier) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_utf8fromidentifier(identifier); + +} + +NPObject* CreateObject(NPP instance, NPClass* aClass) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_createobject(instance, aClass); + +} + +NPObject* RetainObject(NPObject* obj) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_retainobject(obj); + +} + +void ReleaseObject(NPObject* obj) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_releaseobject(obj); + +} + +bool Invoke(NPP npp, NPObject* obj, NPIdentifier methodName, + const NPVariant* args, unsigned argCount, NPVariant* result) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_invoke(npp, obj, methodName, args, argCount, result); +} + +bool InvokeDefault(NPP npp, NPObject* obj, const NPVariant* args, + unsigned argCount, NPVariant* result) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_invoke_default(npp, obj, args, argCount, result); +} + +bool Evaluate(NPP npp, NPObject* obj, NPString* script, NPVariant* result) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_evaluate(npp, obj, script, result); +} + +bool GetProperty(NPP npp, NPObject* obj, NPIdentifier propertyName, + NPVariant* result) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_getproperty(npp, obj, propertyName, result); +} + +bool SetProperty(NPP npp, NPObject* obj, NPIdentifier propertyName, + const NPVariant* value) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_setproperty(npp, obj, propertyName, value); +} + +bool HasProperty(NPP npp, NPObject* npobj, NPIdentifier propertyName) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_hasproperty(npp, npobj, propertyName); +} + +bool HasMethod(NPP npp, NPObject* npobj, NPIdentifier methodName) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_hasmethod(npp, npobj, methodName); +} + +bool RemoveProperty(NPP npp, NPObject* obj, NPIdentifier propertyName) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_removeproperty(npp, obj, propertyName); +} + +void SetException(NPObject* obj, const NPUTF8* message) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_setexception(obj, message); +} + +void PushPopupsEnabledState(NPP npp, NPBool enabled) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_pushpopupsenabledstate(npp, enabled); +} + +void PopPopupsEnabledState(NPP npp) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_poppopupsenabledstate(npp); +} + +bool Enumerate(NPP npp, NPObject* obj, NPIdentifier** identifier, + uint32_t* count) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_enumerate(npp, obj, identifier, count); +} + +void PluginThreadAsyncCall(NPP instance, void (*func)(void*), void* userData) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_pluginthreadasynccall(instance, func, userData); +} + +bool Construct(NPP npp, NPObject* obj, const NPVariant* args, uint32_t argCount, + NPVariant* result) { + DCHECK(IsInitialized()) << __FUNCTION__; + return g_construct(npp, obj, args, argCount, result); +} + +NPError GetValueForURL(NPP instance, NPNURLVariable variable, const char* url, + char** value, uint32* len) { + DCHECK(IsInitialized()) << __FUNCTION__; + DCHECK(npapi::VersionMinor() >= NPVERS_HAS_URL_AND_AUTH_INFO); + if (!g_getvalueforurl) { + NOTREACHED(); + return NPERR_INCOMPATIBLE_VERSION_ERROR; + } + + return g_getvalueforurl(instance, variable, url, value, len); +} + +NPError SetValueForURL(NPP instance, NPNURLVariable variable, const char* url, + const char* value, uint32 len) { + DCHECK(IsInitialized()) << __FUNCTION__; + DCHECK(npapi::VersionMinor() >= NPVERS_HAS_URL_AND_AUTH_INFO); + if (g_setvalueforurl) { + NOTREACHED(); + return NPERR_INCOMPATIBLE_VERSION_ERROR; + } + + return g_setvalueforurl(instance, variable, url, value, len); +} + +NPError GetAuthenticationInfo(NPP instance, const char* protocol, + const char* host, int32 port, const char* scheme, + const char *realm, char** username, uint32* ulen, + char** password, uint32* plen) { + DCHECK(IsInitialized()) << __FUNCTION__; + DCHECK(npapi::VersionMinor() >= NPVERS_HAS_URL_AND_AUTH_INFO); + if (g_getauthenticationinfo) { + NOTREACHED(); + return NPERR_INCOMPATIBLE_VERSION_ERROR; + } + + return g_getauthenticationinfo(instance, protocol, host, port, scheme, + realm, username, ulen, password, plen); +} + +std::string StringFromIdentifier(NPIdentifier identifier) { + std::string ret; + NPUTF8* utf8 = UTF8FromIdentifier(identifier); + if (utf8) { + ret = utf8; + MemFree(utf8); + } + return ret; +} + +} // namespace npapi + +void AllocateStringVariant(const std::string& str, NPVariant* var) { + DCHECK(var); + + int len = str.length(); + NPUTF8* buffer = reinterpret_cast<NPUTF8*>(npapi::MemAlloc(len + 1)); + if (buffer) { + buffer[len] = '\0'; + memcpy(buffer, str.c_str(), len); + STRINGN_TO_NPVARIANT(buffer, len, *var); + } else { + NULL_TO_NPVARIANT(*var); + } +} + diff --git a/chrome_frame/np_browser_functions.h b/chrome_frame/np_browser_functions.h new file mode 100644 index 0000000..ec29345 --- /dev/null +++ b/chrome_frame/np_browser_functions.h @@ -0,0 +1,246 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_FRAME_NP_BROWSER_FUNCTIONS_H_ +#define CHROME_FRAME_NP_BROWSER_FUNCTIONS_H_ + +#include "base/logging.h" +#include "third_party/WebKit/WebCore/bridge/npapi.h" +#include "third_party/WebKit/WebCore/plugins/npfunctions.h" + +namespace npapi { + +// Must be called prior to calling any of the browser functions below. +void InitializeBrowserFunctions(NPNetscapeFuncs* functions); +void UninitializeBrowserFunctions(); + +// Returns true iff InitializeBrowserFunctions has been called successully. +bool IsInitialized(); + +// Function stubs for functions that the host browser implements. + +uint8 VersionMinor(); +uint8 VersionMajor(); + +NPError GetURL(NPP instance, const char* URL, const char* window); + +NPError PostURL(NPP instance, const char* URL, const char* window, uint32 len, + const char* buf, NPBool file); + +NPError RequestRead(NPStream* stream, NPByteRange* rangeList); + +NPError NewStream(NPP instance, NPMIMEType type, const char* window, + NPStream** stream); + +int32 Write(NPP instance, NPStream* stream, int32 len, void* buffer); + +NPError DestroyStream(NPP instance, NPStream* stream, NPReason reason); + +void Status(NPP instance, const char* message); + +const char* UserAgent(NPP instance); + +void* MemAlloc(uint32 size); + +void MemFree(void* ptr); + +uint32 MemFlush(uint32 size); + +void ReloadPlugins(NPBool reloadPages); + +void* GetJavaEnv(); + +void* GetJavaPeer(NPP instance); + +NPError GetURLNotify(NPP instance, const char* URL, const char* window, + void* notifyData); + +NPError PostURLNotify(NPP instance, const char* URL, const char* window, + uint32 len, const char* buf, NPBool file, + void* notifyData); + +NPError GetValue(NPP instance, NPNVariable variable, void* ret_value); + +NPError SetValue(NPP instance, NPPVariable variable, void* value); + +void InvalidateRect(NPP instance, NPRect* rect); + +void InvalidateRegion(NPP instance, NPRegion region); + +void ForceRedraw(NPP instance); + +void ReleaseVariantValue(NPVariant* variant); + +NPIdentifier GetStringIdentifier(const NPUTF8* name); + +void GetStringIdentifiers(const NPUTF8** names, int32_t nameCount, + NPIdentifier* identifiers); + +NPIdentifier GetIntIdentifier(int32_t intid); + +int32_t IntFromIdentifier(NPIdentifier identifier); + +bool IdentifierIsString(NPIdentifier identifier); + +NPUTF8* UTF8FromIdentifier(NPIdentifier identifier); + +NPObject* CreateObject(NPP, NPClass* aClass); + +NPObject* RetainObject(NPObject* obj); + +void ReleaseObject(NPObject* obj); + +bool Invoke(NPP npp, NPObject* obj, NPIdentifier methodName, + const NPVariant* args, unsigned argCount, NPVariant* result); + +bool InvokeDefault(NPP npp, NPObject* obj, const NPVariant* args, + unsigned argCount, NPVariant* result); + +bool Evaluate(NPP npp, NPObject* obj, NPString* script, NPVariant* result); + +bool GetProperty(NPP npp, NPObject* obj, NPIdentifier propertyName, + NPVariant* result); + +bool SetProperty(NPP npp, NPObject* obj, NPIdentifier propertyName, + const NPVariant* value); + +bool HasProperty(NPP npp, NPObject* npobj, NPIdentifier propertyName); + +bool HasMethod(NPP npp, NPObject* npobj, NPIdentifier methodName); + +bool RemoveProperty(NPP npp, NPObject* obj, NPIdentifier propertyName); + +void SetException(NPObject* obj, const NPUTF8* message); + +void PushPopupsEnabledState(NPP npp, NPBool enabled); + +void PopPopupsEnabledState(NPP npp); + +bool Enumerate(NPP npp, NPObject* obj, NPIdentifier** identifier, + uint32_t* count); + +void PluginThreadAsyncCall(NPP instance, void (*func)(void*), void* userData); + +bool Construct(NPP npp, NPObject* obj, const NPVariant* args, uint32_t argCount, + NPVariant* result); + +NPError GetValueForURL(NPP instance, NPNURLVariable variable, const char* url, + char** value, uint32* len); +NPError SetValueForURL(NPP instance, NPNURLVariable variable, const char* url, + const char* value, uint32 len); +NPError GetAuthenticationInfo(NPP instance, const char* protocol, + const char* host, int32 port, const char* scheme, + const char *realm, char** username, uint32* ulen, + char** password, uint32* plen); +uint32 ScheduleTimer(NPP instance, uint32 interval, NPBool repeat, + void (*timerFunc)(NPP npp, uint32 timerID)); +void UnscheduleTimer(NPP instance, uint32 timerID); +NPError PopUpContextMenu(NPP instance, NPMenu* menu); +NPBool ConvertPoint(NPP instance, double sourceX, double sourceY, + NPCoordinateSpace sourceSpace, double *destX, + double *destY, NPCoordinateSpace destSpace); + +// Helper routine that wraps UTF8FromIdentifier to convert a string identifier +// to an STL string. It's not super efficient since it could possibly do two +// heap allocations (STL string has a stack based buffer for smaller strings). +// For debugging purposes it is useful. +std::string StringFromIdentifier(NPIdentifier identifier); + +} // namespace npapi + +// Simple helper class for freeing NPVariants at the end of a scope. +class ScopedNpVariant : public NPVariant { + public: + ScopedNpVariant() { + VOID_TO_NPVARIANT(*this); + } + + ~ScopedNpVariant() { + Free(); + } + + void Free() { + npapi::ReleaseVariantValue(this); + VOID_TO_NPVARIANT(*this); + } + + private: + DISALLOW_COPY_AND_ASSIGN(ScopedNpVariant); +}; + +// Simple helper class for freeing NPObjects at the end of a scope. +template <typename NpoType = NPObject> +class ScopedNpObject { + public: + ScopedNpObject() : npo_(NULL) { + } + + explicit ScopedNpObject(NpoType* npo) : npo_(npo) { + } + + ~ScopedNpObject() { + Free(); + } + + NpoType* get() const { + return npo_; + } + + operator NpoType*() const { + return npo_; + } + + NpoType* operator->() const { + return npo_; + } + + ScopedNpObject<NpoType>& operator=(NpoType* npo) { + if (npo != npo_) { + DCHECK(npo_ == NULL); + npapi::RetainObject(npo); + npo_ = npo; + } + return *this; + } + + void Free() { + if (npo_) { + npapi::ReleaseObject(npo_); + npo_ = NULL; + } + } + + NpoType** Receive() { + DCHECK(npo_ == NULL) << "Object leak. Pointer must be NULL"; + return &npo_; + } + + NpoType* Detach() { + NpoType* p = npo_; + npo_ = NULL; + return p; + } + + void Attach(NpoType* p) { + DCHECK(npo_ == NULL); + npo_ = p; + } + + NpoType* Copy() const { + if (npo_ != NULL) + npapi::RetainObject(npo_); + return npo_; + } + + private: + NpoType* npo_; + DISALLOW_COPY_AND_ASSIGN(ScopedNpObject); +}; + +// Allocates a new NPUTF8 string and assigns it to the variant. +// If memory allocation fails, the variant type will be set to NULL. +// The memory allocation is done via the npapi browser functions. +void AllocateStringVariant(const std::string& str, NPVariant* var); + +#endif // CHROME_FRAME_NP_BROWSER_FUNCTIONS_H_ diff --git a/chrome_frame/np_event_listener.cc b/chrome_frame/np_event_listener.cc new file mode 100644 index 0000000..ff5479a --- /dev/null +++ b/chrome_frame/np_event_listener.cc @@ -0,0 +1,367 @@ +// Copyright (c) 2009 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. + +#include "chrome_frame/np_event_listener.h" + +#include "base/string_util.h" + +#include "third_party/xulrunner-sdk/win/include/string/nsEmbedString.h" +#include "third_party/xulrunner-sdk/win/include/dom/nsIDOMElement.h" +#include "third_party/xulrunner-sdk/win/include/dom/nsIDOMEventTarget.h" +#include "third_party/xulrunner-sdk/win/include/dom/nsIDOMEvent.h" + +#include "chrome_frame/scoped_ns_ptr_win.h" +#include "chrome_frame/ns_associate_iid_win.h" + +ASSOCIATE_IID(NS_IDOMELEMENT_IID_STR, nsIDOMElement); +ASSOCIATE_IID(NS_IDOMNODE_IID_STR, nsIDOMNode); +ASSOCIATE_IID(NS_IDOMEVENTTARGET_IID_STR, nsIDOMEventTarget); +ASSOCIATE_IID(NS_IDOMEVENTLISTENER_IID_STR, nsIDOMEventListener); + +DomEventListener::DomEventListener(NpEventDelegate* delegate) + : NpEventListenerBase<DomEventListener>(delegate) { +} + +DomEventListener::~DomEventListener() { +} + +// We implement QueryInterface etc ourselves in order to avoid +// extra dependencies brought on by the NS_IMPL_* macros. +NS_IMETHODIMP DomEventListener::QueryInterface(REFNSIID iid, void** ptr) { + DCHECK(thread_id_ == ::GetCurrentThreadId()); + nsresult res = NS_NOINTERFACE; + + if (memcmp(&iid, &__uuidof(nsIDOMEventListener), sizeof(nsIID)) == 0 || + memcmp(&iid, &__uuidof(nsISupports), sizeof(nsIID)) == 0) { + *ptr = static_cast<nsIDOMEventListener*>(this); + AddRef(); + res = NS_OK; + } + + return res; +} + +NS_IMETHODIMP DomEventListener::HandleEvent(nsIDOMEvent *event) { + DCHECK(thread_id_ == ::GetCurrentThreadId()); + DCHECK(event); + + nsEmbedString tag; + event->GetType(tag); + delegate_->OnEvent(WideToUTF8(tag.get()).c_str()); + + return NS_OK; +} + +bool DomEventListener::Subscribe(NPP instance, + const char* event_names[], + int event_name_count) { + DCHECK(event_names); + DCHECK(event_name_count > 0); + + ScopedNsPtr<nsIDOMElement> element; + bool ret = GetObjectElement(instance, element.Receive()); + if (ret) { + ScopedNsPtr<nsIDOMEventTarget> target; + target.QueryFrom(element); + if (target) { + for (int i = 0; i < event_name_count && ret; ++i) { + nsEmbedString name(ASCIIToWide(event_names[i]).c_str()); + // See NPObjectEventListener::Subscribe (below) for a note on why + // we set the useCapture parameter to PR_FALSE. + nsresult res = target->AddEventListener(name, this, PR_FALSE); + DCHECK(res == NS_OK) << "AddEventListener: " << event_names[i]; + ret = NS_SUCCEEDED(res); + } + } else { + DLOG(ERROR) << "failed to get nsIDOMEventTarget"; + ret = false; + } + } + + return ret; +} + +bool DomEventListener::Unsubscribe(NPP instance, + const char* event_names[], + int event_name_count) { + DCHECK(event_names); + DCHECK(event_name_count > 0); + + ScopedNsPtr<nsIDOMElement> element; + bool ret = GetObjectElement(instance, element.Receive()); + if (ret) { + ScopedNsPtr<nsIDOMEventTarget> target; + target.QueryFrom(element); + if (target) { + for (int i = 0; i < event_name_count && ret; ++i) { + nsEmbedString name(ASCIIToWide(event_names[i]).c_str()); + nsresult res = target->RemoveEventListener(name, this, PR_FALSE); + DCHECK(res == NS_OK) << "RemoveEventListener: " << event_names[i]; + ret = NS_SUCCEEDED(res) && ret; + } + } else { + DLOG(ERROR) << "failed to get nsIDOMEventTarget"; + ret = false; + } + } + + return ret; +} + +bool DomEventListener::GetObjectElement(NPP instance, nsIDOMElement** element) { + DCHECK(element); + + ScopedNsPtr<nsIDOMElement> elem; + // Fetching the dom element works in Firefox, but is not implemented + // in webkit. + npapi::GetValue(instance, NPNVDOMElement, elem.Receive()); + if (!elem.get()) { + DLOG(INFO) << "Failed to get NPNVDOMElement"; + return false; + } + + nsEmbedString tag; + nsresult res = elem->GetTagName(tag); + if (NS_SUCCEEDED(res)) { + if (LowerCaseEqualsASCII(tag.get(), tag.get() + tag.Length(), "embed")) { + ScopedNsPtr<nsIDOMNode> parent; + elem->GetParentNode(parent.Receive()); + if (parent) { + elem.Release(); + res = parent.QueryInterface(elem.Receive()); + DCHECK(NS_SUCCEEDED(res)); + } + } + } else { + NOTREACHED() << " GetTagName"; + } + + *element = elem.Detach(); + + return *element != NULL; +} + +/////////////////////////////////// +// NPObjectEventListener + +NPObjectEventListener::NPObjectEventListener(NpEventDelegate* delegate) + : NpEventListenerBase<NPObjectEventListener>(delegate) { +} + +NPObjectEventListener::~NPObjectEventListener() { + DLOG_IF(ERROR, npo_.get() == NULL); +} + +NPObject* NPObjectEventListener::GetObjectElement(NPP instance) { + NPObject* object = NULL; + // We can't trust the return value from getvalue. + // In Opera, the return value can be false even though the correct + // object is returned. + npapi::GetValue(instance, NPNVPluginElementNPObject, &object); + + if (object) { + NPIdentifier* ids = GetCachedStringIds(); + NPVariant var; + if (npapi::GetProperty(instance, object, ids[TAG_NAME], &var)) { + DCHECK(NPVARIANT_IS_STRING(var)); + const NPString& np_tag = NPVARIANT_TO_STRING(var); + std::string tag(np_tag.UTF8Characters, np_tag.UTF8Length); + npapi::ReleaseVariantValue(&var); + + if (lstrcmpiA(tag.c_str(), "embed") == 0) { + // We've got the <embed> element but we really want + // the <object> element. + if (npapi::GetProperty(instance, object, ids[PARENT_ELEMENT], &var)) { + DCHECK(NPVARIANT_IS_OBJECT(var)); + npapi::ReleaseObject(object); + object = NPVARIANT_TO_OBJECT(var); + } + } else { + DLOG(INFO) << __FUNCTION__ << " got " << tag; + } + } else { + DLOG(INFO) << __FUNCTION__ << " failed to get the element's tag"; + } + } else { + DLOG(WARNING) << __FUNCTION__ << " failed to get NPNVPluginElementNPObject"; + } + + return object; +} + +// Implementation of NpEventListener +bool NPObjectEventListener::Subscribe(NPP instance, + const char* event_names[], + int event_name_count) { + DCHECK(event_names); + DCHECK(event_name_count > 0); + DCHECK(npo_.get() == NULL); + + ScopedNpObject<> plugin_element(GetObjectElement(instance)); + if (!plugin_element.get()) + return false; + + // This object seems to be getting leaked :-( + bool ret = false; + npo_.Attach(reinterpret_cast<Npo*>( + npapi::CreateObject(instance, PluginClass()))); + + if (!npo_.get()) { + NOTREACHED() << "createobject"; + } else { + npo_->Initialize(this); + ret = true; + + NPIdentifier* ids = GetCachedStringIds(); + + NPVariant args[3]; + OBJECT_TO_NPVARIANT(npo_, args[1]); + // We don't want to set 'capture' (last parameter) to true. + // If we do, then in Opera, we'll simply not get callbacks unless + // the target <object> or <embed> element we're syncing with has its + // on[event] property assigned to some function handler. weird. + // Ideally though we'd like to set capture to true since we'd like to + // only be triggered for this particular object (and not for bubbling + // events, but alas it's not meant to be. + BOOLEAN_TO_NPVARIANT(false, args[2]); + for (int i = 0; i < event_name_count; ++i) { + ScopedNpVariant result; + STRINGZ_TO_NPVARIANT(event_names[i], args[0]); + ret = npapi::Invoke(instance, plugin_element, ids[ADD_EVENT_LISTENER], + args, arraysize(args), &result) && ret; + if (!ret) { + DLOG(WARNING) << __FUNCTION__ << " invoke failed for " + << event_names[i]; + break; + } + } + } + + return ret; +} + +bool NPObjectEventListener::Unsubscribe(NPP instance, + const char* event_names[], + int event_name_count) { + DCHECK(event_names); + DCHECK(event_name_count > 0); + DCHECK(npo_.get() != NULL); + + ScopedNpObject<> plugin_element(GetObjectElement(instance)); + if (!plugin_element.get()) + return false; + + NPIdentifier* ids = GetCachedStringIds(); + + NPVariant args[3]; + OBJECT_TO_NPVARIANT(npo_, args[1]); + BOOLEAN_TO_NPVARIANT(false, args[2]); + for (int i = 0; i < event_name_count; ++i) { + // TODO(tommi): look into why chrome isn't releasing the reference + // count here. As it stands the reference count doesn't go down + // and as a result, the NPO gets leaked. + ScopedNpVariant result; + STRINGZ_TO_NPVARIANT(event_names[i], args[0]); + bool ret = npapi::Invoke(instance, plugin_element, + ids[REMOVE_EVENT_LISTENER], args, arraysize(args), &result); + DLOG_IF(ERROR, !ret) << __FUNCTION__ << " invoke: " << ret; + } + + npo_.Free(); + + return true; +} + +void NPObjectEventListener::HandleEvent(Npo* npo, NPObject* event) { + DCHECK(npo); + DCHECK(event); + + NPIdentifier* ids = GetCachedStringIds(); + ScopedNpVariant result; + bool ret = npapi::GetProperty(npo->npp(), event, ids[TYPE], &result); + DCHECK(ret) << "getproperty(type)"; + if (ret) { + DCHECK(NPVARIANT_IS_STRING(result)); + // Opera doesn't zero terminate its utf8 strings. + const NPString& type = NPVARIANT_TO_STRING(result); + std::string zero_terminated(type.UTF8Characters, type.UTF8Length); + DLOG(INFO) << "handleEvent: " << zero_terminated; + delegate_->OnEvent(zero_terminated.c_str()); + } +} + +NPClass* NPObjectEventListener::PluginClass() { + static NPClass _np_class = { + NP_CLASS_STRUCT_VERSION, + reinterpret_cast<NPAllocateFunctionPtr>(AllocateObject), + reinterpret_cast<NPDeallocateFunctionPtr>(DeallocateObject), + NULL, // invalidate + reinterpret_cast<NPHasMethodFunctionPtr>(HasMethod), + reinterpret_cast<NPInvokeFunctionPtr>(Invoke), + NULL, // InvokeDefault, + NULL, // HasProperty, + NULL, // GetProperty, + NULL, // SetProperty, + NULL // construct + }; + + return &_np_class; +} + +bool NPObjectEventListener::HasMethod(NPObjectEventListener::Npo* npo, + NPIdentifier name) { + NPIdentifier* ids = GetCachedStringIds(); + if (name == ids[HANDLE_EVENT]) + return true; + + return false; +} + +bool NPObjectEventListener::Invoke(NPObjectEventListener::Npo* npo, + NPIdentifier name, const NPVariant* args, + uint32_t arg_count, NPVariant* result) { + NPIdentifier* ids = GetCachedStringIds(); + if (name != ids[HANDLE_EVENT]) + return false; + + if (arg_count != 1 || !NPVARIANT_IS_OBJECT(args[0])) { + NOTREACHED(); + } else { + NPObject* ev = NPVARIANT_TO_OBJECT(args[0]); + npo->listener()->HandleEvent(npo, ev); + } + + return true; +} + +NPObject* NPObjectEventListener::AllocateObject(NPP instance, + NPClass* class_name) { + return new Npo(instance); +} + +void NPObjectEventListener::DeallocateObject(NPObjectEventListener::Npo* npo) { + delete npo; +} + +NPIdentifier* NPObjectEventListener::GetCachedStringIds() { + static NPIdentifier _identifiers[IDENTIFIER_COUNT] = {0}; + if (!_identifiers[0]) { + const NPUTF8* identifier_names[] = { + "handleEvent", + "type", + "addEventListener", + "removeEventListener", + "tagName", + "parentElement", + }; + COMPILE_ASSERT(arraysize(identifier_names) == arraysize(_identifiers), + mismatched_array_size); + npapi::GetStringIdentifiers(identifier_names, IDENTIFIER_COUNT, + _identifiers); + for (int i = 0; i < IDENTIFIER_COUNT; ++i) { + DCHECK(_identifiers[i] != 0); + } + } + return _identifiers; +} diff --git a/chrome_frame/np_event_listener.h b/chrome_frame/np_event_listener.h new file mode 100644 index 0000000..18cb4eb --- /dev/null +++ b/chrome_frame/np_event_listener.h @@ -0,0 +1,187 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_FRAME_NP_EVENT_LISTENER_H_ +#define CHROME_FRAME_NP_EVENT_LISTENER_H_ + +#include "base/logging.h" + +#include "chrome_frame/utils.h" +#include "chrome_frame/np_browser_functions.h" + +// Avoid conflicts with basictypes and the gecko sdk. +// (different definitions of uint32). +#define NO_NSPR_10_SUPPORT +#include "third_party/xulrunner-sdk/win/include/dom/nsIDOMEventListener.h" + + +class nsIDOMElement; + +class NpEventDelegate { + public: + virtual void OnEvent(const char* event_name) = 0; +}; + +class NpEventListener { + public: + NS_IMETHOD_(nsrefcnt) AddRef() = 0; + NS_IMETHOD_(nsrefcnt) Release() = 0; + virtual bool Subscribe(NPP instance, + const char* event_names[], + int event_name_count) = 0; + virtual bool Unsubscribe(NPP instance, + const char* event_names[], + int event_name_count) = 0; +}; + +// A little helper class to implement simple ref counting +// and assert on single threadedness. +template <class T> +class NpEventListenerBase : public NpEventListener { + public: + NpEventListenerBase(NpEventDelegate* delegate) + : ref_count_(0), delegate_(delegate) { + DCHECK(delegate_); + thread_id_ = ::GetCurrentThreadId(); + } + + ~NpEventListenerBase() { + DCHECK(thread_id_ == ::GetCurrentThreadId()); + } + + NS_IMETHOD_(nsrefcnt) AddRef() { + DCHECK(thread_id_ == ::GetCurrentThreadId()); + ref_count_++; + return ref_count_; + } + + NS_IMETHOD_(nsrefcnt) Release() { + DCHECK(thread_id_ == ::GetCurrentThreadId()); + ref_count_--; + + if (!ref_count_) { + T* me = static_cast<T*>(this); + delete me; + return 0; + } + + return ref_count_; + } + + protected: + nsrefcnt ref_count_; + NpEventDelegate* delegate_; + AddRefModule module_ref_; + // used to DCHECK on expected single-threaded usage + unsigned long thread_id_; +}; + +// Implements nsIDOMEventListener in order to receive events from DOM +// elements inside an HTML page. +class DomEventListener + : public nsIDOMEventListener, + public NpEventListenerBase<DomEventListener> { + public: + DomEventListener(NpEventDelegate* delegate); + ~DomEventListener(); + + // Implementation of NpEventListener + virtual bool Subscribe(NPP instance, + const char* event_names[], + int event_name_count); + virtual bool Unsubscribe(NPP instance, + const char* event_names[], + int event_name_count); + protected: + // We implement QueryInterface etc ourselves in order to avoid + // extra dependencies brought on by the NS_IMPL_* macros. + NS_IMETHOD QueryInterface(REFNSIID iid, void** ptr); + NS_IMETHOD_(nsrefcnt) AddRef() { + return NpEventListenerBase<DomEventListener>::AddRef(); + } + + NS_IMETHOD_(nsrefcnt) Release() { + return NpEventListenerBase<DomEventListener>::Release(); + } + + // Implementation of nsIDOMEventListener + NS_IMETHOD HandleEvent(nsIDOMEvent *event); + + private: + static bool GetObjectElement(NPP instance, nsIDOMElement** element); + + private: + DISALLOW_COPY_AND_ASSIGN(DomEventListener); +}; + +class NPObjectEventListener + : public NpEventListenerBase<NPObjectEventListener> { + public: + NPObjectEventListener(NpEventDelegate* delegate); + ~NPObjectEventListener(); + + // Implementation of NpEventListener + virtual bool Subscribe(NPP instance, + const char* event_names[], + int event_name_count); + virtual bool Unsubscribe(NPP instance, + const char* event_names[], + int event_name_count); + + protected: + // NPObject structure which is exposed by NPObjectEventListener. + class Npo : public NPObject { + public: + Npo(NPP npp) : npp_(npp), listener_(NULL) { + } + + void Initialize(NPObjectEventListener* listener) { + listener_ = listener; + } + + inline NPObjectEventListener* listener() const { + return listener_; + } + + inline NPP npp() const { + return npp_; + } + + protected: + NPP npp_; + NPObjectEventListener* listener_; + AddRefModule module_ref_; + }; + + static NPClass* PluginClass(); + + static bool HasMethod(Npo* npo, NPIdentifier name); + static bool Invoke(Npo* npo, NPIdentifier name, const NPVariant* args, + uint32_t arg_count, NPVariant* result); + static NPObject* AllocateObject(NPP instance, NPClass* class_name); + static void DeallocateObject(Npo* npo); + + typedef enum { + HANDLE_EVENT, + TYPE, + ADD_EVENT_LISTENER, + REMOVE_EVENT_LISTENER, + TAG_NAME, + PARENT_ELEMENT, + IDENTIFIER_COUNT, + } CachedStringIdentifiers; + + static NPIdentifier* GetCachedStringIds(); + + void HandleEvent(Npo* npo, NPObject* event); + static NPObject* GetObjectElement(NPP instance); + + private: + // Our NPObject. + ScopedNpObject<Npo> npo_; + + DISALLOW_COPY_AND_ASSIGN(NPObjectEventListener); +}; + +#endif // CHROME_FRAME_NP_EVENT_LISTENER_H_ diff --git a/chrome_frame/np_proxy_service.cc b/chrome_frame/np_proxy_service.cc new file mode 100644 index 0000000..cb26dac --- /dev/null +++ b/chrome_frame/np_proxy_service.cc @@ -0,0 +1,306 @@ +// Copyright (c) 2009 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. + +#include "base/string_util.h" +#include "chrome/common/automation_constants.h" +#include "chrome/common/json_value_serializer.h" +#include "chrome_frame/np_proxy_service.h" +#include "chrome_frame/np_browser_functions.h" + +#include "net/proxy/proxy_config.h" + +#include "third_party/xulrunner-sdk/win/include/xpcom/nsXPCOM.h" +#include "third_party/xulrunner-sdk/win/include/xpcom/nsIObserverService.h" +#include "third_party/xulrunner-sdk/win/include/xpcom/nsISupportsUtils.h" +#include "third_party/xulrunner-sdk/win/include/xpcom/nsStringAPI.h" + +ASSOCIATE_IID(NS_IOBSERVERSERVICE_IID_STR, nsIObserverService); +ASSOCIATE_IID(NS_IPREFBRANCH_IID_STR, nsIPrefBranch); + +// Firefox preference names. +const char* kProxyObserverRoot = "network."; +const char* kProxyObserverBranch = "proxy."; +const char* kProxyType = "proxy.type"; +const char* kProxyAutoconfigUrl = "proxy.autoconfig_url"; +const char* kProxyBypassList = "proxy.no_proxies_on"; + +const int kInvalidIntPref = -1; + +// These are the proxy schemes that Chrome knows about at the moment. +// SOCKS is a notable ommission here, this will need to be updated when +// Chrome supports SOCKS proxies. +const NpProxyService::ProxyNames NpProxyService::kProxyInfo[] = { + {"http", "proxy.http", "proxy.http_port"}, + {"https", "proxy.ssl", "proxy.ssl_port"}, + {"ftp", "proxy.ftp", "proxy.ftp_port"} }; + +NpProxyService::NpProxyService(void) + : type_(PROXY_CONFIG_LAST), auto_detect_(false), no_proxy_(false), + system_config_(false), automation_client_(NULL) { +} + +NpProxyService::~NpProxyService(void) { +} + +bool NpProxyService::Initialize(NPP instance, + ChromeFrameAutomationClient* automation_client) { + DCHECK(automation_client); + automation_client_ = automation_client; + + // Get the pref service + bool result = false; + ScopedNsPtr<nsISupports> service_manager_base; + npapi::GetValue(instance, NPNVserviceManager, service_manager_base.Receive()); + if (service_manager_base != NULL) { + service_manager_.QueryFrom(service_manager_base); + if (service_manager_.get() == NULL) { + DLOG(ERROR) << "Failed to create ServiceManager. This only works in FF."; + } else { + service_manager_->GetServiceByContractID( + NS_PREFSERVICE_CONTRACTID, NS_GET_IID(nsIPrefService), + reinterpret_cast<void**>(pref_service_.Receive())); + if (!pref_service_) { + DLOG(ERROR) << "Failed to create PreferencesService"; + } else { + result = InitializePrefBranch(pref_service_); + } + } + } + return result; +} + +bool NpProxyService::InitializePrefBranch(nsIPrefService* pref_service) { + DCHECK(pref_service); + // Note that we cannot persist a reference to the pref branch because we + // also act as an observer of changes to the branch. As per + // nsIPrefBranch2.h, this would result in a circular reference between us + // and the pref branch, which can impede cleanup. There are workarounds, + // but let's try just not caching the branch reference for now. + bool result = false; + ScopedNsPtr<nsIPrefBranch> pref_branch; + + pref_service->ReadUserPrefs(nsnull); + pref_service->GetBranch(kProxyObserverRoot, pref_branch.Receive()); + + if (!pref_branch) { + DLOG(ERROR) << "Failed to get nsIPrefBranch"; + } else { + if (!ReadProxySettings(pref_branch.get())) { + DLOG(ERROR) << "Could not read proxy settings."; + } else { + observer_pref_branch_.QueryFrom(pref_branch); + if (!observer_pref_branch_) { + DLOG(ERROR) << "Failed to get observer nsIPrefBranch2"; + } else { + nsresult res = observer_pref_branch_->AddObserver(kProxyObserverBranch, + this, PR_FALSE); + result = NS_SUCCEEDED(res); + } + } + } + return result; +} + +bool NpProxyService::UnInitialize() { + // Fail early if this was never created - we may not be running on FF. + if (!pref_service_) + return false; + + // Unhook ourselves as an observer. + nsresult res = NS_ERROR_FAILURE; + if (observer_pref_branch_) + res = observer_pref_branch_->RemoveObserver(kProxyObserverBranch, this); + + return NS_SUCCEEDED(res); +} + +NS_IMETHODIMP NpProxyService::Observe(nsISupports* subject, const char* topic, + const PRUnichar* data) { + if (!subject || !topic) { + NOTREACHED(); + return NS_ERROR_UNEXPECTED; + } + + std::string topic_str(topic); + nsresult res = NS_OK; + if (topic_str == NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) { + // Looks like our proxy settings changed. We need to reload! + // I have observed some extremely strange behaviour here. Specifically, + // we are supposed to be able to QI |subject| and get from it an + // nsIPrefBranch from which we can query new values. This has erratic + // behaviour, specifically subject starts returning null on all member + // queries. So I am using the cached nsIPrefBranch2 (that we used to add + // the observer) to do the querying. + if (NS_SUCCEEDED(res)) { + if (!ReadProxySettings(observer_pref_branch_)) { + res = NS_ERROR_UNEXPECTED; + } else { + std::string proxy_settings; + if (GetProxyValueJSONString(&proxy_settings)) + automation_client_->SetProxySettings(proxy_settings); + } + } + } else { + NOTREACHED(); + } + + return res; +} + +std::string NpProxyService::GetStringPref(nsIPrefBranch* pref_branch, + const char* pref_name) { + nsCString pref_string; + std::string result; + nsresult rv = pref_branch->GetCharPref(pref_name, getter_Copies(pref_string)); + if (SUCCEEDED(rv) && pref_string.get()) { + result = pref_string.get(); + } + return result; +} + +int NpProxyService::GetIntPref(nsIPrefBranch* pref_branch, + const char* pref_name) { + PRInt32 pref_int; + int result = kInvalidIntPref; + nsresult rv = pref_branch->GetIntPref(pref_name, &pref_int); + if (SUCCEEDED(rv)) { + result = pref_int; + } + return result; +} + +bool NpProxyService::GetBoolPref(nsIPrefBranch* pref_branch, + const char* pref_name) { + PRBool pref_bool; + bool result = false; + nsresult rv = pref_branch->GetBoolPref(pref_name, &pref_bool); + if (SUCCEEDED(rv)) { + result = pref_bool == PR_TRUE; + } + return result; +} + +void NpProxyService::Reset() { + type_ = PROXY_CONFIG_LAST; + auto_detect_ = false; + no_proxy_ = false; + system_config_ = false; + manual_proxies_.clear(); + pac_url_.clear(); + proxy_bypass_list_.clear(); +} + +bool NpProxyService::ReadProxySettings(nsIPrefBranch* pref_branch) { + DCHECK(pref_branch); + + // Clear our current settings. + Reset(); + type_ = GetIntPref(pref_branch, kProxyType); + if (type_ == kInvalidIntPref) { + NOTREACHED(); + return false; + } + + switch (type_) { + case PROXY_CONFIG_DIRECT: + case PROXY_CONFIG_DIRECT4X: + no_proxy_ = true; + break; + case PROXY_CONFIG_SYSTEM: + // _SYSTEM is documented as "Use system settings if available, otherwise + // DIRECT". It isn't clear under what circumstances system settings would + // be unavailable, but I'll special-case this nonetheless and have + // GetProxyValueJSONString() return empty if we get this proxy type. + DLOG(WARNING) << "Received PROXY_CONFIG_SYSTEM proxy type."; + system_config_ = true; + break; + case PROXY_CONFIG_WPAD: + auto_detect_ = true; + break; + case PROXY_CONFIG_PAC: + pac_url_ = GetStringPref(pref_branch, kProxyAutoconfigUrl); + break; + case PROXY_CONFIG_MANUAL: + // Read in the values for each of the known schemes. + for (int i = 0; i < arraysize(kProxyInfo); i++) { + ManualProxyEntry entry; + entry.url = GetStringPref(pref_branch, kProxyInfo[i].pref_name); + entry.port = GetIntPref(pref_branch, kProxyInfo[i].port_pref_name); + if (!entry.url.empty() && entry.port != kInvalidIntPref) { + entry.scheme = kProxyInfo[i].chrome_scheme; + manual_proxies_.push_back(entry); + } + } + + // Also pick up the list of URLs we bypass proxies for. + proxy_bypass_list_ = GetStringPref(pref_branch, kProxyBypassList); + break; + default: + NOTREACHED(); + return false; + } + return true; +} + +DictionaryValue* NpProxyService::BuildProxyValueSet() { + scoped_ptr<DictionaryValue> proxy_settings_value(new DictionaryValue); + + if (auto_detect_) { + proxy_settings_value->SetBoolean(automation::kJSONProxyAutoconfig, + auto_detect_); + } + + if (no_proxy_) { + proxy_settings_value->SetBoolean(automation::kJSONProxyNoProxy, no_proxy_); + } + + if (!pac_url_.empty()) { + proxy_settings_value->SetString(automation::kJSONProxyPacUrl, pac_url_); + } + + if (!proxy_bypass_list_.empty()) { + proxy_settings_value->SetString(automation::kJSONProxyBypassList, + proxy_bypass_list_); + } + + // Fill in the manual proxy settings. Build a string representation that + // corresponds to the format of the input parameter to + // ProxyConfig::ProxyRules::ParseFromString. + std::string manual_proxy_settings; + ManualProxyList::const_iterator iter(manual_proxies_.begin()); + for (; iter != manual_proxies_.end(); iter++) { + DCHECK(!iter->scheme.empty()); + DCHECK(!iter->url.empty()); + DCHECK(iter->port != kInvalidIntPref); + manual_proxy_settings += iter->scheme; + manual_proxy_settings += "="; + manual_proxy_settings += iter->url; + manual_proxy_settings += ":"; + manual_proxy_settings += IntToString(iter->port); + manual_proxy_settings += ";"; + } + + if (!manual_proxy_settings.empty()) { + proxy_settings_value->SetString(automation::kJSONProxyServer, + manual_proxy_settings); + } + + return proxy_settings_value.release(); +} + +bool NpProxyService::GetProxyValueJSONString(std::string* output) { + DCHECK(output); + output->empty(); + + // If we detected a PROXY_CONFIG_SYSTEM config type or failed to obtain the + // pref service then return false here to make Chrome continue using its + // default proxy settings. + if (system_config_ || !pref_service_) + return false; + + scoped_ptr<DictionaryValue> proxy_settings_value(BuildProxyValueSet()); + + JSONStringValueSerializer serializer(output); + return serializer.Serialize(*static_cast<Value*>(proxy_settings_value.get())); +} diff --git a/chrome_frame/np_proxy_service.h b/chrome_frame/np_proxy_service.h new file mode 100644 index 0000000..c0a85c8 --- /dev/null +++ b/chrome_frame/np_proxy_service.h @@ -0,0 +1,137 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_FRAME_NP_PROXY_SERVICE_H_ +#define CHROME_FRAME_NP_PROXY_SERVICE_H_ + +#include <string> +#include <vector> +#include "base/values.h" +#include "base/scoped_ptr.h" + +// Avoid conflicts with basictypes and the gecko sdk. +// (different definitions of uint32). +#define NO_NSPR_10_SUPPORT + +#include "chrome_frame/chrome_frame_automation.h" +#include "chrome_frame/ns_associate_iid_win.h" +#include "chrome_frame/ns_isupports_impl.h" +#include "chrome_frame/scoped_ns_ptr_win.h" +#include "third_party/WebKit/WebCore/bridge/npapi.h" +#include "third_party/xulrunner-sdk/win/include/xpcom/nsIObserver.h" +#include "third_party/xulrunner-sdk/win/include/pref/nsIPrefBranch2.h" +#include "third_party/xulrunner-sdk/win/include/pref/nsIPrefService.h" +#include "third_party/xulrunner-sdk/win/include/xpcom/nsIServiceManager.h" + +ASSOCIATE_IID(NS_IOBSERVER_IID_STR, nsIObserver); +ASSOCIATE_IID(NS_ISERVICEMANAGER_IID_STR, nsIServiceManager); +ASSOCIATE_IID(NS_IPREFSERVICE_IID_STR, nsIPrefService); +ASSOCIATE_IID(NS_IPREFBRANCH2_IID_STR, nsIPrefBranch2); + +class nsIServiceManager; +class nsIPrefService; +class nsIPrefBranch2; + +// This class reads in proxy settings from firefox. +// TODO(robertshield): The change notification code is currently broken. +// Fix it and implement calling back through to the automation proxy with +// proxy updates. +class NpProxyService : public NsISupportsImplBase<NpProxyService>, + public nsIObserver { + public: + // These values correspond to the integer network.proxy.type preference. + enum ProxyConfigType { + PROXY_CONFIG_DIRECT, + PROXY_CONFIG_MANUAL, + PROXY_CONFIG_PAC, + PROXY_CONFIG_DIRECT4X, + PROXY_CONFIG_WPAD, + PROXY_CONFIG_SYSTEM, // use system settings if available, otherwise DIRECT + PROXY_CONFIG_LAST + }; + + // nsISupports + NS_IMETHODIMP_(nsrefcnt) AddRef(void) { + return NsISupportsImplBase<NpProxyService>::AddRef(); + } + + NS_IMETHODIMP_(nsrefcnt) Release(void) { + return NsISupportsImplBase<NpProxyService>::Release(); + } + + NS_IMETHOD QueryInterface(REFNSIID iid, void** ptr) { + nsresult res = + NsISupportsImplBase<NpProxyService>::QueryInterface(iid, ptr); + if (NS_FAILED(res) && + memcmp(&iid, &__uuidof(nsIObserver), sizeof(nsIID)) == 0) { + *ptr = static_cast<nsIObserver*>(this); + AddRef(); + res = NS_OK; + } + return res; + } + + // nsIObserver + NS_IMETHOD Observe(nsISupports* subject, const char* topic, + const PRUnichar* data); + + NpProxyService(); + ~NpProxyService(); + + virtual bool Initialize(NPP instance, + ChromeFrameAutomationClient* automation_client); + bool UnInitialize(); + + // Places the current Firefox settings as a JSON string suitable for posting + // over to Chromium into output. Returns true if the settings were correctly + // serialized into a JSON string, false otherwise. + // TODO(robertshield): I haven't yet nailed down how much of this should go + // here and how much should go in the AutomationProxy. Will do that in a + // near-future patch. + bool GetProxyValueJSONString(std::string* output); + + private: + bool InitializePrefBranch(nsIPrefService* pref_service); + bool ReadProxySettings(nsIPrefBranch* pref_branch); + + std::string GetStringPref(nsIPrefBranch* pref_branch, const char* pref_name); + int GetIntPref(nsIPrefBranch* pref_branch, const char* pref_name); + bool GetBoolPref(nsIPrefBranch* pref_branch, const char* pref_name); + + void Reset(); + DictionaryValue* BuildProxyValueSet(); + + ChromeFrameAutomationClient* automation_client_; + + ScopedNsPtr<nsIServiceManager> service_manager_; + ScopedNsPtr<nsIPrefService> pref_service_; + ScopedNsPtr<nsIPrefBranch2> observer_pref_branch_; + + struct ProxyNames { + // Proxy type (http, https, ftp, etc...). + const char* chrome_scheme; + // Firefox preference name of the URL for this proxy type. + const char* pref_name; + // Firefox preference name for the port for this proxy type. + const char* port_pref_name; + }; + static const ProxyNames kProxyInfo[]; + + struct ManualProxyEntry { + std::string scheme; + std::string url; + int port; + }; + typedef std::vector<ManualProxyEntry> ManualProxyList; + + bool system_config_; + bool auto_detect_; + bool no_proxy_; + int type_; + std::string pac_url_; + std::string proxy_bypass_list_; + ManualProxyList manual_proxies_; +}; + +#endif // CHROME_FRAME_NP_PROXY_SERVICE_H_ diff --git a/chrome_frame/npapi_url_request.cc b/chrome_frame/npapi_url_request.cc new file mode 100644 index 0000000..33052a1 --- /dev/null +++ b/chrome_frame/npapi_url_request.cc @@ -0,0 +1,157 @@ +// Copyright 2009, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome_frame/npapi_url_request.h" + +#include "base/string_util.h" +#include "chrome_frame/np_browser_functions.h" +#include "net/base/net_errors.h" + +int NPAPIUrlRequest::instance_count_ = 0; + +NPAPIUrlRequest::NPAPIUrlRequest(NPP instance) + : ref_count_(0), instance_(instance), stream_(NULL), + pending_read_size_(0), + status_(URLRequestStatus::FAILED, net::ERR_FAILED), + thread_(PlatformThread::CurrentId()) { + DLOG(INFO) << "Created request. Count: " << ++instance_count_; +} + +NPAPIUrlRequest::~NPAPIUrlRequest() { + DLOG(INFO) << "Deleted request. Count: " << --instance_count_; +} + +// NPAPIUrlRequest member defines. +bool NPAPIUrlRequest::Start() { + NPError result = NPERR_GENERIC_ERROR; + DLOG(INFO) << "Starting URL request: " << url(); + if (LowerCaseEqualsASCII(method(), "get")) { + // TODO(joshia): if we have extra headers for HTTP GET, then implement + // it using XHR + result = npapi::GetURLNotify(instance_, url().c_str(), NULL, this); + } else if (LowerCaseEqualsASCII(method(), "post")) { + result = npapi::PostURLNotify(instance_, url().c_str(), NULL, + extra_headers().length(), extra_headers().c_str(), false, this); + } else { + NOTREACHED() << "PluginUrlRequest only supports 'GET'/'POST'"; + } + + if (NPERR_NO_ERROR == result) { + request_handler()->AddRequest(this); + } else { + int os_error = net::ERR_FAILED; + switch (result) { + case NPERR_INVALID_URL: + os_error = net::ERR_INVALID_URL; + break; + default: + break; + } + + OnResponseEnd(URLRequestStatus(URLRequestStatus::FAILED, os_error)); + return false; + } + + return true; +} + +void NPAPIUrlRequest::Stop() { + DLOG(INFO) << "Finished request: Url - " << url() << " result: " + << static_cast<int>(status_.status()); + if (stream_) { + npapi::DestroyStream(instance_, stream_, NPRES_USER_BREAK); + stream_ = NULL; + } + + request_handler()->RemoveRequest(this); + if (!status_.is_io_pending()) + OnResponseEnd(status_); +} + +bool NPAPIUrlRequest::Read(int bytes_to_read) { + pending_read_size_ = bytes_to_read; + return true; +} + +bool NPAPIUrlRequest::OnStreamCreated(const char* mime_type, NPStream* stream) { + stream_ = stream; + status_.set_status(URLRequestStatus::IO_PENDING); + // TODO(iyengar) + // Add support for passing persistent cookies and information about any URL + // redirects to Chrome. + OnResponseStarted(mime_type, stream->headers, stream->end, + base::Time::FromTimeT(stream->lastmodified), std::string(), + std::string(), 0); + return true; +} + +void NPAPIUrlRequest::OnStreamDestroyed(NPReason reason) { + URLRequestStatus::Status status = URLRequestStatus::FAILED; + switch (reason) { + case NPRES_DONE: + status_.set_status(URLRequestStatus::SUCCESS); + status_.set_os_error(0); + break; + case NPRES_USER_BREAK: + status_.set_status(URLRequestStatus::CANCELED); + status_.set_os_error(net::ERR_ABORTED); + break; + case NPRES_NETWORK_ERR: + default: + status_.set_status(URLRequestStatus::FAILED); + status_.set_os_error(net::ERR_CONNECTION_CLOSED); + break; + } +} + +int NPAPIUrlRequest::OnWriteReady() { + return pending_read_size_; +} + +int NPAPIUrlRequest::OnWrite(void* buffer, int len) { + pending_read_size_ = 0; + OnReadComplete(buffer, len); + return len; +} + +STDMETHODIMP_(ULONG) NPAPIUrlRequest::AddRef() { + DCHECK_EQ(PlatformThread::CurrentId(), thread_); + ++ref_count_; + return ref_count_; +} + +STDMETHODIMP_(ULONG) NPAPIUrlRequest::Release() { + DCHECK_EQ(PlatformThread::CurrentId(), thread_); + unsigned long ret = --ref_count_; + if (!ret) + delete this; + + return ret; +} + diff --git a/chrome_frame/npapi_url_request.h b/chrome_frame/npapi_url_request.h new file mode 100644 index 0000000..9151fcf --- /dev/null +++ b/chrome_frame/npapi_url_request.h @@ -0,0 +1,45 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_FRAME_NPAPI_URL_REQUEST_H_ +#define CHROME_FRAME_NPAPI_URL_REQUEST_H_ + +#include "base/platform_thread.h" +#include "chrome_frame/plugin_url_request.h" +#include "third_party/WebKit/WebCore/bridge/npapi.h" + +class NPAPIUrlRequest : public PluginUrlRequest { + public: + explicit NPAPIUrlRequest(NPP instance); + ~NPAPIUrlRequest(); + + virtual bool Start(); + virtual void Stop(); + virtual bool Read(int bytes_to_read); + + // Called from NPAPI + bool OnStreamCreated(const char* mime_type, NPStream* stream); + void OnStreamDestroyed(NPReason reason); + int OnWriteReady(); + int OnWrite(void* buffer, int len); + + // Thread unsafe implementation of ref counting, since + // this will be called on the plugin UI thread only. + virtual unsigned long API_CALL AddRef(); + virtual unsigned long API_CALL Release(); + + private: + unsigned long ref_count_; + NPP instance_; + NPStream* stream_; + size_t pending_read_size_; + URLRequestStatus status_; + + PlatformThreadId thread_; + static int instance_count_; + DISALLOW_COPY_AND_ASSIGN(NPAPIUrlRequest); +}; + +#endif // CHROME_FRAME_NPAPI_URL_REQUEST_H_ + diff --git a/chrome_frame/ns_associate_iid_win.h b/chrome_frame/ns_associate_iid_win.h new file mode 100644 index 0000000..db6c234 --- /dev/null +++ b/chrome_frame/ns_associate_iid_win.h @@ -0,0 +1,15 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_FRAME_NS_ASSOCIATE_IID_WIN_H_ +#define CHROME_FRAME_NS_ASSOCIATE_IID_WIN_H_ + +#define ASSOCIATE_IID(iid, class_name) class __declspec(uuid(iid)) class_name + +// Associate IIDs with ns interfaces for convenience. +// This makes ScopedNsPtr more user friendly and avoids the nsIXyz::GetIID() +// calls which can't be used in templates (runtime vs compile time). +ASSOCIATE_IID("00000000-0000-0000-c000-000000000046", nsISupports); + +#endif // CHROME_FRAME_NS_ASSOCIATE_IID_WIN_H_ diff --git a/chrome_frame/ns_isupports_impl.h b/chrome_frame/ns_isupports_impl.h new file mode 100644 index 0000000..4873163 --- /dev/null +++ b/chrome_frame/ns_isupports_impl.h @@ -0,0 +1,68 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_FRAME_NS_ISUPPORTS_IMPL_H_ +#define CHROME_FRAME_NS_ISUPPORTS_IMPL_H_ + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/platform_thread.h" +#include "chrome_frame/utils.h" +#include "third_party/xulrunner-sdk/win/include/xpcom/nscore.h" +#include "third_party/xulrunner-sdk/win/include/xpcom/nsid.h" +#include "third_party/xulrunner-sdk/win/include/xpcom/nsISupportsBase.h" + +// A simple single-threaded implementation of methods needed to support +// nsISupports. Must be inherited by classes that also inherit from nsISupports. +template<class Derived> +class NsISupportsImplBase { + public: + NsISupportsImplBase() : nsiimpl_ref_count_(0) { + nsiimpl_thread_id_ = PlatformThread::CurrentId(); + } + + virtual ~NsISupportsImplBase() { + DCHECK(nsiimpl_thread_id_ == PlatformThread::CurrentId()); + } + + NS_IMETHOD QueryInterface(REFNSIID iid, void** ptr) { + DCHECK(nsiimpl_thread_id_ == PlatformThread::CurrentId()); + nsresult res = NS_NOINTERFACE; + + if (memcmp(&iid, &__uuidof(nsISupports), sizeof(nsIID)) == 0) { + *ptr = static_cast<nsISupports*>(static_cast<typename Derived*>(this)); + AddRef(); + res = NS_OK; + } + + return res; + } + + NS_IMETHOD_(nsrefcnt) AddRef() { + DCHECK(nsiimpl_thread_id_ == PlatformThread::CurrentId()); + nsiimpl_ref_count_++; + return nsiimpl_ref_count_; + } + + NS_IMETHOD_(nsrefcnt) Release() { + DCHECK(nsiimpl_thread_id_ == PlatformThread::CurrentId()); + nsiimpl_ref_count_--; + + if (!nsiimpl_ref_count_) { + Derived* me = static_cast<Derived*>(this); + delete me; + return 0; + } + + return nsiimpl_ref_count_; + } + + protected: + nsrefcnt nsiimpl_ref_count_; + AddRefModule nsiimpl_module_ref_; + // used to DCHECK on expected single-threaded usage + uint64 nsiimpl_thread_id_; +}; + +#endif // CHROME_FRAME_NS_ISUPPORTS_IMPL_H_ diff --git a/chrome_frame/ole_document_impl.h b/chrome_frame/ole_document_impl.h new file mode 100644 index 0000000..4c346d8 --- /dev/null +++ b/chrome_frame/ole_document_impl.h @@ -0,0 +1,253 @@ +// Copyright (c) 2009 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. + +// TODO(slightlyoff): Add any required LICENSE block changes for MSFT code +// inclusion. + +// ole_document_impl.h : IOleDocument implementation +// +// This file is a modified version of the OleDocument.h file, which is +// part of the ActiveDoc MSDN sample. The modifications are largely +// conversions to Google coding guidelines. Below if the original header +// from the file. + +// This is a part of the Active Template Library. +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// This source code is only intended as a supplement to the +// Active Template Library Reference and related +// electronic documentation provided with the library. +// See these sources for detailed information regarding the +// Active Template Library product. + +#ifndef CHROME_FRAME_OLE_DOCUMENT_IMPL_H_ +#define CHROME_FRAME_OLE_DOCUMENT_IMPL_H_ + +// TODO(sanjeevr): Revisit this impl file and cleanup dependencies +#include <atlbase.h> +#include <docobj.h> + +#include "base/logging.h" + +////////////////////////////////////////////////////////////////////////////// +// IOleDocumentImpl +template <class T> +class ATL_NO_VTABLE IOleDocumentImpl : public IOleDocument { + public: + STDMETHOD(CreateView)(IOleInPlaceSite* in_place_site, + IStream* stream, + DWORD reserved , + IOleDocumentView** new_view) { + DLOG(INFO) << __FUNCTION__; + if (new_view == NULL) + return E_POINTER; + T* t = static_cast<T*>(this); + // If we've already created a view then we can't create another as we + // currently only support the ability to create one view + if (t->m_spInPlaceSite) { + return E_FAIL; + } + IOleDocumentView* view; + t->GetUnknown()->QueryInterface(IID_IOleDocumentView, + reinterpret_cast<void**>(&view)); + // If we support IOleDocument we should support IOleDocumentView + ATLENSURE(view != NULL); + // If they've given us a site then use it + if (in_place_site != NULL) { + view->SetInPlaceSite(in_place_site); + } + // If they have given us an IStream pointer then use it to + // initialize the view + if (stream != NULL) { + view->ApplyViewState(stream); + } + // Return the view + *new_view = view; + return S_OK; + } + + STDMETHOD(GetDocMiscStatus)(DWORD* status) { + DLOG(INFO) << __FUNCTION__; + if (NULL == status) { + return E_POINTER; + } + *status = DOCMISC_NOFILESUPPORT; + return S_OK; + } + + STDMETHOD(EnumViews)(IEnumOleDocumentViews** enum_views, + IOleDocumentView** view) { + DLOG(INFO) << __FUNCTION__; + if (view == NULL) + return E_POINTER; + T* t = static_cast<T*>(this); + // We only support one view + return t->_InternalQueryInterface(IID_IOleDocumentView, + reinterpret_cast<void**>(view)); + } +}; + +////////////////////////////////////////////////////////////////////////////// +// IOleDocumentViewImpl + +template <class T> +class ATL_NO_VTABLE IOleDocumentViewImpl : public IOleDocumentView { + public: + STDMETHOD(SetInPlaceSite)(IOleInPlaceSite* in_place_site) { + DLOG(INFO) << __FUNCTION__; + T* t = static_cast<T*>(this); + if (t->m_spInPlaceSite) { + // If we already have a site get rid of it + UIActivate(FALSE); + HRESULT hr = t->InPlaceDeactivate(); + if (FAILED(hr)) { + return hr; + } + DCHECK(!t->m_bInPlaceActive); + } + if (in_place_site != NULL) { + t->m_spInPlaceSite.Release(); + in_place_site->QueryInterface( + IID_IOleInPlaceSiteWindowless, + reinterpret_cast<void **>(&t->m_spInPlaceSite)); + if (!t->m_spInPlaceSite) { + // TODO(sanjeevr): This is a super-hack because m_spInPlaceSite + // is an IOleInPlaceSiteWindowless pointer and we are setting + // an IOleInPlaceSite pointer into it. The problem is that ATL + // (CComControlBase) uses this in a schizophrenic manner based + // on the m_bWndLess flag. Ouch, ouch, ouch! Find a way to clean + // this up. + // Disclaimer: I did not invent this hack, it exists in the MSDN + // sample from where this code has been derived and it also exists + // in ATL itself (look at atlctl.h line 938). + t->m_spInPlaceSite = + reinterpret_cast<IOleInPlaceSiteWindowless*>(in_place_site); + } + } + return S_OK; + } + + STDMETHOD(GetInPlaceSite)(IOleInPlaceSite** in_place_site) { + DLOG(INFO) << __FUNCTION__; + if (in_place_site == NULL) { + return E_POINTER; + } + T* t = static_cast<T*>(this); + return t->m_spInPlaceSite->QueryInterface( + IID_IOleInPlaceSite, + reinterpret_cast<LPVOID *>(in_place_site)); + } + + STDMETHOD(GetDocument)(IUnknown** document) { + DLOG(INFO) << __FUNCTION__; + if (document == NULL) { + return E_POINTER; + } + T* t = static_cast<T*>(this); + *document = t->GetUnknown(); + (*document)->AddRef(); + return S_OK; + } + + STDMETHOD(SetRect)(LPRECT view_rect) { + static bool is_resizing = false; + if (is_resizing) { + return S_OK; + } + is_resizing = true; + DLOG(INFO) << __FUNCTION__ << " " << view_rect->left << "," << + view_rect->top << "," << view_rect->right << "," << view_rect->bottom; + T* t = static_cast<T*>(this); + t->SetObjectRects(view_rect, view_rect); + is_resizing = false; + return S_OK; + } + + STDMETHOD(GetRect)(LPRECT view_rect) { + DLOG(INFO) << __FUNCTION__; + if (view_rect == NULL) { + return E_POINTER; + } + T* t = static_cast<T*>(this); + *view_rect = t->m_rcPos; + return S_OK; + } + + STDMETHOD(SetRectComplex)(LPRECT view_rect, + LPRECT hscroll_rect, + LPRECT vscroll_rect, + LPRECT size_box_rect) { + DLOG(INFO) << __FUNCTION__ << " not implemented"; + return E_NOTIMPL; + } + + STDMETHOD(Show)(BOOL show) { + DLOG(INFO) << __FUNCTION__; + T* t = static_cast<T*>(this); + HRESULT hr = S_OK; + if (show) { + if (!t->m_bUIActive) + hr = t->ActiveXDocActivate(OLEIVERB_INPLACEACTIVATE); + } else { + hr = t->UIActivate(FALSE); + ::ShowWindow(t->m_hWnd, SW_HIDE); + } + return hr; + } + + STDMETHOD(UIActivate)(BOOL ui_activate) { + DLOG(INFO) << __FUNCTION__; + T* t = static_cast<T*>(this); + HRESULT hr = S_OK; + if (ui_activate) { + // We must know the client site first + if (t->m_spInPlaceSite == NULL) { + return E_UNEXPECTED; + } + if (!t->m_bUIActive) { + hr = t->ActiveXDocActivate(OLEIVERB_UIACTIVATE); + } + } else { + t->InPlaceMenuDestroy(); + // t->DestroyToolbar(); + hr = t->UIDeactivate(); + } + return hr; + } + + STDMETHOD(Open)() { + DLOG(INFO) << __FUNCTION__ << " not implemented"; + return E_NOTIMPL; + } + + STDMETHOD(CloseView)(DWORD reserved) { + DLOG(INFO) << __FUNCTION__; + T* t = static_cast<T*>(this); + t->Show(FALSE); + t->SetInPlaceSite(NULL); + return S_OK; + } + + STDMETHOD(SaveViewState)(LPSTREAM stream) { + DLOG(INFO) << __FUNCTION__ << " not implemented"; + return E_NOTIMPL; + } + + STDMETHOD(ApplyViewState)(LPSTREAM stream) { + DLOG(INFO) << __FUNCTION__ << " not implemented"; + return E_NOTIMPL; + } + + STDMETHOD(Clone)(IOleInPlaceSite* new_in_place_site, + IOleDocumentView** new_view) { + DLOG(INFO) << __FUNCTION__ << " not implemented"; + return E_NOTIMPL; + } + + HRESULT ActiveXDocActivate(LONG verb) { + return E_NOTIMPL; + } +}; + +#endif // CHROME_FRAME_OLE_DOCUMENT_IMPL_H_ diff --git a/chrome_frame/plugin_url_request.cc b/chrome_frame/plugin_url_request.cc new file mode 100644 index 0000000..1d34b0c --- /dev/null +++ b/chrome_frame/plugin_url_request.cc @@ -0,0 +1,65 @@ +// Copyright (c) 2009 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. + +#include "chrome_frame/plugin_url_request.h" + +#include "chrome/test/automation/automation_messages.h" +#include "chrome_frame/np_browser_functions.h" + +PluginUrlRequest::PluginUrlRequest() + : request_handler_(NULL), tab_(0), remote_request_id_(0), + status_(URLRequestStatus::IO_PENDING), + frame_busting_enabled_(false) { +} + +PluginUrlRequest::~PluginUrlRequest() { +} + +bool PluginUrlRequest::Initialize(PluginRequestHandler* request_handler, + int tab, int remote_request_id, const std::string& url, + const std::string& method, const std::string& referrer, + const std::string& extra_headers, net::UploadData* upload_data, + bool enable_frame_busting) { + request_handler_ = request_handler; + tab_ = tab; + remote_request_id_ = remote_request_id; + url_ = url; + method_ = method; + referrer_ = referrer; + extra_headers_ = extra_headers; + upload_data_ = upload_data; + frame_busting_enabled_ = enable_frame_busting; + return true; +} + +void PluginUrlRequest::OnResponseStarted(const char* mime_type, + const char* headers, int size, base::Time last_modified, + const std::string& persistent_cookies, + const std::string& redirect_url, int redirect_status) { + const IPC::AutomationURLResponse response = { + mime_type, + headers ? headers : "", + size, + last_modified, + persistent_cookies, + redirect_url, + redirect_status + }; + request_handler_->Send(new AutomationMsg_RequestStarted(0, tab_, + remote_request_id_, response)); +} + +void PluginUrlRequest::OnResponseEnd(const URLRequestStatus& status) { + DCHECK(!status.is_io_pending()); + DCHECK(status.is_success() || status.os_error()); + request_handler_->Send(new AutomationMsg_RequestEnd(0, tab_, + remote_request_id_, status)); +} + +void PluginUrlRequest::OnReadComplete(const void* buffer, int len) { + std::string data(reinterpret_cast<const char*>(buffer), len); + request_handler_->Send(new AutomationMsg_RequestData(0, tab_, + remote_request_id_, data)); +} + diff --git a/chrome_frame/plugin_url_request.h b/chrome_frame/plugin_url_request.h new file mode 100644 index 0000000..fb28c77 --- /dev/null +++ b/chrome_frame/plugin_url_request.h @@ -0,0 +1,114 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_FRAME_PLUGIN_URL_REQUEST_H_ +#define CHROME_FRAME_PLUGIN_URL_REQUEST_H_ + +#include <string> +#include <vector> + +#include "base/time.h" +#include "ipc/ipc_message.h" +#include "net/base/upload_data.h" +#include "net/url_request/url_request_status.h" +#include "base/ref_counted.h" + +class PluginUrlRequest; + +// Interface for a class that keeps a collection of outstanding +// reqeusts and offers an outgoing channel. +class PluginRequestHandler : public IPC::Message::Sender { + public: + virtual bool AddRequest(PluginUrlRequest* request) = 0; + virtual void RemoveRequest(PluginUrlRequest* request) = 0; +}; + +// A reference counting solution that's compatible with COM IUnknown +class UrlRequestReference { + public: + virtual unsigned long API_CALL AddRef() = 0; + virtual unsigned long API_CALL Release() = 0; +}; + +class PluginUrlRequest : public UrlRequestReference { + public: + PluginUrlRequest(); + ~PluginUrlRequest(); + + bool Initialize(PluginRequestHandler* handler, int tab, + int remote_request_id, const std::string& url, + const std::string& method, const std::string& referrer, + const std::string& extra_headers, + net::UploadData* upload_data, + bool intercept_frame_options); + + // Called in response to automation IPCs + virtual bool Start() = 0; + virtual void Stop() = 0; + virtual bool Read(int bytes_to_read) = 0; + + // Persistent cookies are read from the host browser and passed off to Chrome + // These cookies are sent when we receive a response for every URL request + // initiated by Chrome. Ideally we should only send cookies for the top level + // URL and any subframes. However we don't receive information from Chrome + // about the context for a URL, i.e. whether it is a subframe, etc. + // Additionally cookies for a URL should be sent once for the page. This + // is not done now as it is difficult to track URLs, specifically if they + // are redirected, etc. + void OnResponseStarted(const char* mime_type, const char* headers, int size, + base::Time last_modified, const std::string& peristent_cookies, + const std::string& redirect_url, int redirect_status); + + void OnReadComplete(const void* buffer, int len); + void OnResponseEnd(const URLRequestStatus& status); + + PluginRequestHandler* request_handler() const { + return request_handler_; + } + int id() const { + return remote_request_id_; + } + const std::string& url() const { + return url_; + } + const std::string& method() const { + return method_; + } + const std::string& referrer() const { + return referrer_; + } + const std::string& extra_headers() const { + return extra_headers_; + } + scoped_refptr<net::UploadData> upload_data() { + return upload_data_; + } + + bool is_done() const { + return (URLRequestStatus::IO_PENDING != status_); + } + + void set_url(const std::string& url) { + url_ = url; + } + + protected: + void SendData(); + bool frame_busting_enabled_; + + private: + PluginRequestHandler* request_handler_; + int tab_; + int remote_request_id_; + std::string url_; + std::string method_; + std::string referrer_; + std::string extra_headers_; + scoped_refptr<net::UploadData> upload_data_; + URLRequestStatus::Status status_; +}; + + +#endif // CHROME_FRAME_PLUGIN_URL_REQUEST_H_ + diff --git a/chrome_frame/precompiled.cc b/chrome_frame/precompiled.cc new file mode 100644 index 0000000..6b08ac8 --- /dev/null +++ b/chrome_frame/precompiled.cc @@ -0,0 +1,9 @@ +// Copyright (c) 2009 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. + +// precompiled.cc : source file that includes just the standard includes +// precompiled.pch will be the pre-compiled header +// precompiled.obj will contain the pre-compiled type information + +#include "precompiled.h" diff --git a/chrome_frame/precompiled.h b/chrome_frame/precompiled.h new file mode 100644 index 0000000..0a13651 --- /dev/null +++ b/chrome_frame/precompiled.h @@ -0,0 +1,18 @@ +// Copyright (c) 2009 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. + +// precompiled.h : include file for standard system include files, +// or project specific include files that are used frequently, +// but are changed infrequently + +#ifndef CHROME_FRAME_PRECOMPILED_H_ +#define CHROME_FRAME_PRECOMPILED_H_ + +#include "resource.h" + +#include <atlbase.h> +#include <atlcom.h> +#include <atlctl.h> + +#endif // CHROME_FRAME_PRECOMPILED_H_ diff --git a/chrome_frame/protocol_sink_wrap.cc b/chrome_frame/protocol_sink_wrap.cc new file mode 100644 index 0000000..a567e9f --- /dev/null +++ b/chrome_frame/protocol_sink_wrap.cc @@ -0,0 +1,615 @@ +// Copyright (c) 2009 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. + +#include <htiframe.h> + +#include "chrome_frame/protocol_sink_wrap.h" + +#include "base/scoped_bstr_win.h" +#include "base/logging.h" +#include "base/registry.h" +#include "base/scoped_bstr_win.h" +#include "base/singleton.h" +#include "base/string_util.h" + +#include "chrome_frame/utils.h" +#include "chrome_frame/vtable_patch_manager.h" + +// BINDSTATUS_SERVER_MIMETYPEAVAILABLE == 54. Introduced in IE 8, so +// not in everyone's headers yet. See: +// http://msdn.microsoft.com/en-us/library/ms775133(VS.85,loband).aspx +#ifndef BINDSTATUS_SERVER_MIMETYPEAVAILABLE +#define BINDSTATUS_SERVER_MIMETYPEAVAILABLE 54 +#endif + +static const wchar_t* kChromeMimeType = L"application/chromepage"; +static const char kTextHtmlMimeType[] = "text/html"; + +static const int kInternetProtocolStartIndex = 3; +static const int kInternetProtocolReadIndex = 9; +static const int kInternetProtocolStartExIndex = 13; + +BEGIN_VTABLE_PATCHES(IInternetProtocol) + VTABLE_PATCH_ENTRY(kInternetProtocolStartIndex, ProtocolSinkWrap::OnStart) + VTABLE_PATCH_ENTRY(kInternetProtocolReadIndex, ProtocolSinkWrap::OnRead) +END_VTABLE_PATCHES() + +BEGIN_VTABLE_PATCHES(IInternetProtocolEx) + VTABLE_PATCH_ENTRY(kInternetProtocolStartIndex, ProtocolSinkWrap::OnStart) + VTABLE_PATCH_ENTRY(kInternetProtocolReadIndex, ProtocolSinkWrap::OnRead) + VTABLE_PATCH_ENTRY(kInternetProtocolStartExIndex, ProtocolSinkWrap::OnStartEx) +END_VTABLE_PATCHES() + +// +// ProtocolSinkWrap implementation +// + +// Static map initialization +ProtocolSinkWrap::ProtocolSinkMap ProtocolSinkWrap::sink_map_; +CComAutoCriticalSection ProtocolSinkWrap::sink_map_lock_; + +ProtocolSinkWrap::ProtocolSinkWrap() + : protocol_(NULL), renderer_type_(UNDETERMINED), + buffer_size_(0), buffer_pos_(0), is_saved_result_(false), + result_code_(0), result_error_(0), report_data_recursiveness_(0) { + memset(buffer_, 0, arraysize(buffer_)); +} + +ProtocolSinkWrap::~ProtocolSinkWrap() { + CComCritSecLock<CComAutoCriticalSection> lock(sink_map_lock_); + DCHECK(sink_map_.end() != sink_map_.find(protocol_)); + sink_map_.erase(protocol_); + protocol_ = NULL; + DLOG(INFO) << "ProtocolSinkWrap: active sinks: " << sink_map_.size(); +} + +bool ProtocolSinkWrap::PatchProtocolHandler(const wchar_t* dll, + const CLSID& handler_clsid) { + HMODULE module = ::GetModuleHandle(dll); + if (!module) { + NOTREACHED() << "urlmon is not yet loaded. Error: " << GetLastError(); + return false; + } + + typedef HRESULT (WINAPI* DllGetClassObject_Fn)(REFCLSID, REFIID, LPVOID*); + DllGetClassObject_Fn fn = reinterpret_cast<DllGetClassObject_Fn>( + ::GetProcAddress(module, "DllGetClassObject")); + if (!fn) { + NOTREACHED() << "DllGetClassObject not found in urlmon.dll"; + return false; + } + + ScopedComPtr<IClassFactory> protocol_class_factory; + HRESULT hr = fn(handler_clsid, IID_IClassFactory, + reinterpret_cast<LPVOID*>(protocol_class_factory.Receive())); + if (FAILED(hr)) { + NOTREACHED() << "DllGetclassObject failed. Error: " << hr; + return false; + } + + ScopedComPtr<IInternetProtocol> handler_instance; + hr = protocol_class_factory->CreateInstance(NULL, IID_IInternetProtocol, + reinterpret_cast<void**>(handler_instance.Receive())); + if (FAILED(hr)) { + NOTREACHED() << "ClassFactory::CreateInstance failed for InternetProtocol." + << " Error: " << hr; + return false; + } + + ScopedComPtr<IInternetProtocolEx> ipex; + ipex.QueryFrom(handler_instance); + if (ipex) { + vtable_patch::PatchInterfaceMethods(ipex, IInternetProtocolEx_PatchInfo); + } else { + vtable_patch::PatchInterfaceMethods(handler_instance, + IInternetProtocol_PatchInfo); + } + + return true; +} + +// IInternetProtocol/Ex method implementation. +HRESULT ProtocolSinkWrap::OnStart(InternetProtocol_Start_Fn orig_start, + IInternetProtocol* protocol, LPCWSTR url, IInternetProtocolSink* prot_sink, + IInternetBindInfo* bind_info, DWORD flags, HANDLE_PTR reserved) { + DCHECK(orig_start); + DLOG_IF(INFO, url != NULL) << "OnStart: " << url; + + ScopedComPtr<IInternetProtocolSink> sink_to_use(MaybeWrapSink(protocol, + prot_sink, url)); + return orig_start(protocol, url, sink_to_use, bind_info, flags, reserved); +} + +HRESULT ProtocolSinkWrap::OnStartEx(InternetProtocol_StartEx_Fn orig_start_ex, + IInternetProtocolEx* protocol, IUri* uri, IInternetProtocolSink* prot_sink, + IInternetBindInfo* bind_info, DWORD flags, HANDLE_PTR reserved) { + DCHECK(orig_start_ex); + + ScopedBstr url; + uri->GetPropertyBSTR(Uri_PROPERTY_ABSOLUTE_URI, url.Receive(), 0); + DLOG_IF(INFO, url != NULL) << "OnStartEx: " << url; + + ScopedComPtr<IInternetProtocolSink> sink_to_use(MaybeWrapSink(protocol, + prot_sink, url)); + return orig_start_ex(protocol, uri, sink_to_use, bind_info, flags, reserved); +} + +HRESULT ProtocolSinkWrap::OnRead(InternetProtocol_Read_Fn orig_read, + IInternetProtocol* protocol, void* buffer, ULONG size, ULONG* size_read) { + DCHECK(orig_read); + + scoped_refptr<ProtocolSinkWrap> instance = + ProtocolSinkWrap::InstanceFromProtocol(protocol); + HRESULT hr; + if (instance) { + DCHECK(instance->protocol_ == protocol); + hr = instance->OnReadImpl(buffer, size, size_read, orig_read); + } else { + hr = orig_read(protocol, buffer, size, size_read); + } + + return hr; +} + +bool ProtocolSinkWrap::Initialize(IInternetProtocol* protocol, + IInternetProtocolSink* original_sink, const wchar_t* url) { + DCHECK(original_sink); + delegate_ = original_sink; + protocol_ = protocol; + if (url) + url_ = url; + + CComCritSecLock<CComAutoCriticalSection> lock(sink_map_lock_); + DCHECK(sink_map_.end() == sink_map_.find(protocol)); + sink_map_[protocol] = this; + DLOG(INFO) << "ProtocolSinkWrap: active sinks: " << sink_map_.size(); + return true; +} + +HRESULT WINAPI ProtocolSinkWrap::CheckOutgoingInterface(void* obj, + REFIID iid, LPVOID* ret, DWORD cookie) { + ProtocolSinkWrap* instance = reinterpret_cast<ProtocolSinkWrap*>(obj); + HRESULT hr = E_NOINTERFACE; + if (instance && instance->delegate_) + hr = instance->delegate_->QueryInterface(iid, ret); + +#ifndef NDEBUG + if (SUCCEEDED(hr)) { + wchar_t iid_string[64] = {0}; + StringFromGUID2(iid, iid_string, arraysize(iid_string)); + DLOG(INFO) << "Giving out wrapped interface: " << iid_string; + } +#endif + + return hr; +} + +HRESULT WINAPI ProtocolSinkWrap::IfDelegateSupports(void* obj, + REFIID iid, LPVOID* ret, DWORD cookie) { + HRESULT hr = E_NOINTERFACE; + ProtocolSinkWrap* instance = reinterpret_cast<ProtocolSinkWrap*>(obj); + if (instance && instance->delegate_) { + ScopedComPtr<IUnknown> original; + hr = instance->delegate_->QueryInterface(iid, + reinterpret_cast<void**>(original.Receive())); + if (original) { + IUnknown* supported_interface = reinterpret_cast<IUnknown*>( + reinterpret_cast<DWORD_PTR>(obj) + cookie); + supported_interface->AddRef(); + *ret = supported_interface; + hr = S_OK; + } + } + + return hr; +} + +// IInternetProtocolSink methods +STDMETHODIMP ProtocolSinkWrap::Switch(PROTOCOLDATA* protocol_data) { + HRESULT hr = E_FAIL; + if (delegate_) + hr = delegate_->Switch(protocol_data); + return hr; +} + +STDMETHODIMP ProtocolSinkWrap::ReportProgress(ULONG status_code, + LPCWSTR status_text) { + DLOG(INFO) << "ProtocolSinkWrap::ReportProgress: Code:" << status_code << + " Text: " << (status_text ? status_text : L""); + if ((BINDSTATUS_MIMETYPEAVAILABLE == status_code) || + (BINDSTATUS_VERIFIEDMIMETYPEAVAILABLE == status_code)) { + // If we have a MIMETYPE and that MIMETYPE is not "text/html". we don't + // want to do anything with this. + if (status_text) { + size_t status_text_length = lstrlenW(status_text); + const wchar_t* status_text_end = status_text + std::min( + status_text_length, arraysize(kTextHtmlMimeType) - 1); + if (!LowerCaseEqualsASCII(status_text, status_text_end, + kTextHtmlMimeType)) { + renderer_type_ = OTHER; + } + } + } + + HRESULT hr = E_FAIL; + if (delegate_) + hr = delegate_->ReportProgress(status_code, status_text); + return hr; +} + +STDMETHODIMP ProtocolSinkWrap::ReportData(DWORD flags, ULONG progress, + ULONG max_progress) { + DCHECK(protocol_); + DCHECK(delegate_); + DLOG(INFO) << "ProtocolSinkWrap::ReportData: flags: " << flags << + " progress: " << progress << " progress_max: " << max_progress; + + scoped_refptr<ProtocolSinkWrap> self_ref(this); + + // Maintain a stack depth to make a determination. ReportData is called + // recursively in IE8. If the request can be served in a single Read, the + // situation ends up like this: + // orig_prot + // |--> ProtocolSinkWrap::ReportData (BSCF_FIRSTDATANOTIFICATION) + // |--> orig_prot->Read(...) - 1st read - S_OK and data + // |--> ProtocolSinkWrap::ReportData (BSCF_LASTDATANOTIFICATION) + // |--> orig_prot->Read(...) - 2nd read S_FALSE, 0 bytes + // + // Inner call returns S_FALSE and no data. We try to make a determination + // of render type then and incorrectly set it to 'OTHER' as we don't have + // any data yet. However, we can make a determination in the context of + // outer ReportData since the first read will return S_OK with data. Then + // the next Read in the loop will return S_FALSE and we will enter the + // determination logic. + + // NOTE: We use the report_data_recursiveness_ variable to detect situations + // in which calls to ReportData are re-entrant (such as when the entire + // contents of a page fit inside a single packet). In these cases, we + // don't care about re-entrant calls beyond the second, and so we compare + // report_data_recursiveness_ inside the while loop, making sure we skip + // what would otherwise be spurious calls to ReportProgress(). + report_data_recursiveness_++; + + HRESULT hr = S_OK; + if (is_undetermined()) { + HRESULT hr_read = S_OK; + while (hr_read == S_OK) { + ULONG size_read = 0; + hr_read = protocol_->Read(buffer_ + buffer_size_, + kMaxContentSniffLength - buffer_size_, &size_read); + buffer_size_ += size_read; + + // Attempt to determine the renderer type if we have received + // sufficient data. Do not attempt this when we are called recursively. + if (report_data_recursiveness_ < 2 && (S_FALSE == hr_read) || + (buffer_size_ >= kMaxContentSniffLength)) { + DetermineRendererType(); + if (renderer_type() == CHROME) { + // Workaround for IE 8 and "nosniff". See: + // http://blogs.msdn.com/ie/archive/2008/09/02/ie8-security-part-vi-beta-2-update.aspx + delegate_->ReportProgress( + BINDSTATUS_SERVER_MIMETYPEAVAILABLE, kChromeMimeType); + // For IE < 8. + delegate_->ReportProgress( + BINDSTATUS_VERIFIEDMIMETYPEAVAILABLE, kChromeMimeType); + + delegate_->ReportData( + BSCF_LASTDATANOTIFICATION | BSCF_DATAFULLYAVAILABLE, 0, 0); + } + break; + } + } + } + + // we call original only if the renderer type is other + if (renderer_type() == OTHER) { + hr = delegate_->ReportData(flags, progress, max_progress); + + if (is_saved_result_) { + is_saved_result_ = false; + delegate_->ReportResult(result_code_, result_error_, + result_text_.c_str()); + } + } + + report_data_recursiveness_--; + return hr; +} + +STDMETHODIMP ProtocolSinkWrap::ReportResult(HRESULT result, DWORD error, + LPCWSTR result_text) { + DLOG(INFO) << "ProtocolSinkWrap::ReportResult: result: " << result << + " error: " << error << " Text: " << (result_text ? result_text : L""); + + // If this request failed, we don't want to have anything to do with this. + if (FAILED(result)) + renderer_type_ = OTHER; + + // if we are still not sure about the renderer type, cache the result, + // othewise urlmon will get confused about getting reported about a + // success result for which it never received any data. + if (is_undetermined()) { + is_saved_result_ = true; + result_code_ = result; + result_error_ = error; + if (result_text) + result_text_ = result_text; + return S_OK; + } + + HRESULT hr = E_FAIL; + if (delegate_) + hr = delegate_->ReportResult(result, error, result_text); + + return hr; +} + +// IInternetBindInfoEx +STDMETHODIMP ProtocolSinkWrap::GetBindInfo( + DWORD* flags, BINDINFO* bind_info_ret) { + ScopedComPtr<IInternetBindInfo> bind_info; + HRESULT hr = bind_info.QueryFrom(delegate_); + if (bind_info) + hr = bind_info->GetBindInfo(flags, bind_info_ret); + return hr; +} + +STDMETHODIMP ProtocolSinkWrap::GetBindString(ULONG string_type, + LPOLESTR* string_array, ULONG array_size, ULONG* size_returned) { + ScopedComPtr<IInternetBindInfo> bind_info; + HRESULT hr = bind_info.QueryFrom(delegate_); + if (bind_info) + hr = bind_info->GetBindString(string_type, string_array, + array_size, size_returned); + return hr; +} + +STDMETHODIMP ProtocolSinkWrap::GetBindInfoEx(DWORD *flags, BINDINFO* bind_info, + DWORD* bindf2, DWORD *reserved) { + ScopedComPtr<IInternetBindInfoEx> bind_info_ex; + HRESULT hr = bind_info_ex.QueryFrom(delegate_); + if (bind_info_ex) + hr = bind_info_ex->GetBindInfoEx(flags, bind_info, bindf2, reserved); + return hr; +} + +// IServiceProvider +STDMETHODIMP ProtocolSinkWrap::QueryService(REFGUID service_guid, + REFIID riid, void** service) { + ScopedComPtr<IServiceProvider> service_provider; + HRESULT hr = service_provider.QueryFrom(delegate_); + if (service_provider) + hr = service_provider->QueryService(service_guid, riid, service); + return hr; +} + +// IAuthenticate +STDMETHODIMP ProtocolSinkWrap::Authenticate(HWND* window, + LPWSTR* user_name, LPWSTR* password) { + ScopedComPtr<IAuthenticate> authenticate; + HRESULT hr = authenticate.QueryFrom(delegate_); + if (authenticate) + hr = authenticate->Authenticate(window, user_name, password); + return hr; +} + +// IInternetProtocolEx +STDMETHODIMP ProtocolSinkWrap::Start(LPCWSTR url, + IInternetProtocolSink *protocol_sink, IInternetBindInfo* bind_info, + DWORD flags, HANDLE_PTR reserved) { + ScopedComPtr<IInternetProtocolRoot> protocol; + HRESULT hr = protocol.QueryFrom(delegate_); + if (protocol) + hr = protocol->Start(url, protocol_sink, bind_info, flags, reserved); + return hr; +} + +STDMETHODIMP ProtocolSinkWrap::Continue(PROTOCOLDATA* protocol_data) { + ScopedComPtr<IInternetProtocolRoot> protocol; + HRESULT hr = protocol.QueryFrom(delegate_); + if (protocol) + hr = protocol->Continue(protocol_data); + return hr; +} + +STDMETHODIMP ProtocolSinkWrap::Abort(HRESULT reason, DWORD options) { + ScopedComPtr<IInternetProtocolRoot> protocol; + HRESULT hr = protocol.QueryFrom(delegate_); + if (protocol) + hr = protocol->Abort(reason, options); + return hr; +} + +STDMETHODIMP ProtocolSinkWrap::Terminate(DWORD options) { + ScopedComPtr<IInternetProtocolRoot> protocol; + HRESULT hr = protocol.QueryFrom(delegate_); + if (protocol) + hr = protocol->Terminate(options); + return hr; +} + +STDMETHODIMP ProtocolSinkWrap::Suspend() { + ScopedComPtr<IInternetProtocolRoot> protocol; + HRESULT hr = protocol.QueryFrom(delegate_); + if (protocol) + hr = protocol->Suspend(); + return hr; +} + +STDMETHODIMP ProtocolSinkWrap::Resume() { + ScopedComPtr<IInternetProtocolRoot> protocol; + HRESULT hr = protocol.QueryFrom(delegate_); + if (protocol) + hr = protocol->Resume(); + return hr; +} + +STDMETHODIMP ProtocolSinkWrap::Read(void *buffer, ULONG size, + ULONG* size_read) { + ScopedComPtr<IInternetProtocol> protocol; + HRESULT hr = protocol.QueryFrom(delegate_); + if (protocol) + hr = protocol->Read(buffer, size, size_read); + return hr; +} + +STDMETHODIMP ProtocolSinkWrap::Seek(LARGE_INTEGER move, DWORD origin, + ULARGE_INTEGER* new_pos) { + ScopedComPtr<IInternetProtocol> protocol; + HRESULT hr = protocol.QueryFrom(delegate_); + if (protocol) + hr = protocol->Seek(move, origin, new_pos); + return hr; +} + +STDMETHODIMP ProtocolSinkWrap::LockRequest(DWORD options) { + ScopedComPtr<IInternetProtocol> protocol; + HRESULT hr = protocol.QueryFrom(delegate_); + if (protocol) + hr = protocol->LockRequest(options); + return hr; +} + +STDMETHODIMP ProtocolSinkWrap::UnlockRequest() { + ScopedComPtr<IInternetProtocol> protocol; + HRESULT hr = protocol.QueryFrom(delegate_); + if (protocol) + hr = protocol->UnlockRequest(); + return hr; +} + +STDMETHODIMP ProtocolSinkWrap::StartEx(IUri* uri, + IInternetProtocolSink* protocol_sink, IInternetBindInfo* bind_info, + DWORD flags, HANDLE_PTR reserved) { + ScopedComPtr<IInternetProtocolEx> protocol; + HRESULT hr = protocol.QueryFrom(delegate_); + if (protocol) + hr = protocol->StartEx(uri, protocol_sink, bind_info, flags, reserved); + return hr; +} + +// IInternetPriority +STDMETHODIMP ProtocolSinkWrap::SetPriority(LONG priority) { + ScopedComPtr<IInternetPriority> internet_priority; + HRESULT hr = internet_priority.QueryFrom(delegate_); + if (internet_priority) + hr = internet_priority->SetPriority(priority); + return hr; +} + +STDMETHODIMP ProtocolSinkWrap::GetPriority(LONG* priority) { + ScopedComPtr<IInternetPriority> internet_priority; + HRESULT hr = internet_priority.QueryFrom(delegate_); + if (internet_priority) + hr = internet_priority->GetPriority(priority); + return hr; +} + +// IWrappedProtocol +STDMETHODIMP ProtocolSinkWrap::GetWrapperCode(LONG *code, DWORD_PTR reserved) { + ScopedComPtr<IWrappedProtocol> wrapped_protocol; + HRESULT hr = wrapped_protocol.QueryFrom(delegate_); + if (wrapped_protocol) + hr = wrapped_protocol->GetWrapperCode(code, reserved); + return hr; +} + + +// public IUriContainer +STDMETHODIMP ProtocolSinkWrap::GetIUri(IUri** uri) { + ScopedComPtr<IUriContainer> uri_container; + HRESULT hr = uri_container.QueryFrom(delegate_); + if (uri_container) + hr = uri_container->GetIUri(uri); + return hr; +} + +// Protected helpers + +void ProtocolSinkWrap::DetermineRendererType() { + if (is_undetermined()) { + if (IsOptInUrl(url_.c_str())) { + renderer_type_ = CHROME; + } else { + std::wstring xua_compat_content; + // Note that document_contents_ may have NULL characters in it. While + // browsers may handle this properly, we don't and will stop scanning for + // the XUACompat content value if we encounter one. + DCHECK(buffer_size_ < arraysize(buffer_)); + buffer_[buffer_size_] = 0; + std::wstring html_contents; + // TODO(joshia): detect and handle different content encodings + UTF8ToWide(buffer_, buffer_size_, &html_contents); + UtilGetXUACompatContentValue(html_contents, &xua_compat_content); + if (StrStrI(xua_compat_content.c_str(), kChromeContentPrefix)) { + renderer_type_ = CHROME; + } else { + renderer_type_ = OTHER; + } + } + } +} + +HRESULT ProtocolSinkWrap::OnReadImpl(void* buffer, ULONG size, ULONG* size_read, + InternetProtocol_Read_Fn orig_read) { + // We want to switch the renderer to chrome, we cannot return any + // data now. + if (CHROME == renderer_type()) + return S_FALSE; + + // Serve data from our buffer first. + if (OTHER == renderer_type()) { + const ULONG bytes_to_copy = std::min(buffer_size_ - buffer_pos_, size); + if (bytes_to_copy) { + memcpy(buffer, buffer_ + buffer_pos_, bytes_to_copy); + *size_read = bytes_to_copy; + buffer_pos_ += bytes_to_copy; + return S_OK; + } + } + + return orig_read(protocol_, buffer, size, size_read); +} + +scoped_refptr<ProtocolSinkWrap> ProtocolSinkWrap::InstanceFromProtocol( + IInternetProtocol* protocol) { + CComCritSecLock<CComAutoCriticalSection> lock(sink_map_lock_); + scoped_refptr<ProtocolSinkWrap> instance; + ProtocolSinkMap::iterator it = sink_map_.find(protocol); + if (sink_map_.end() != it) + instance = it->second; + return instance; +} + +HRESULT ProtocolSinkWrap::WebBrowserFromProtocolSink( + IInternetProtocolSink* sink, IWebBrowser2** web_browser) { + // TODO(tommi): GUID_NULL doesn't work when loading from history. + // asking for IID_IHttpNegotiate as the service id works, but + // getting the IWebBrowser2 interface still doesn't work. + ScopedComPtr<IHttpNegotiate> http_negotiate; + HRESULT hr = DoQueryService(GUID_NULL, sink, http_negotiate.Receive()); + if (http_negotiate) + hr = DoQueryService(IID_ITargetFrame2, http_negotiate, web_browser); + + return hr; +} + +ScopedComPtr<IInternetProtocolSink> ProtocolSinkWrap::MaybeWrapSink( + IInternetProtocol* protocol, IInternetProtocolSink* prot_sink, + const wchar_t* url) { + ScopedComPtr<IInternetProtocolSink> sink_to_use; + sink_to_use.QueryFrom(prot_sink); + ScopedComPtr<IWebBrowser2> web_browser; + WebBrowserFromProtocolSink(prot_sink, web_browser.Receive()); + if (web_browser) { + CComObject<ProtocolSinkWrap>* wrap = NULL; + CComObject<ProtocolSinkWrap>::CreateInstance(&wrap); + DCHECK(wrap); + if (wrap->Initialize(protocol, prot_sink, url)) { + sink_to_use = wrap; + } + } + + return sink_to_use; +} diff --git a/chrome_frame/protocol_sink_wrap.h b/chrome_frame/protocol_sink_wrap.h new file mode 100644 index 0000000..e4f9cfb --- /dev/null +++ b/chrome_frame/protocol_sink_wrap.h @@ -0,0 +1,221 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_FRAME_PROTOCOL_SINK_WRAP_H_ +#define CHROME_FRAME_PROTOCOL_SINK_WRAP_H_ + +#include <exdisp.h> +#include <urlmon.h> +#include <atlbase.h> +#include <atlcom.h> +#include <map> +#include <string> + +#include "base/basictypes.h" +#include "base/ref_counted.h" +#include "base/scoped_comptr_win.h" +#include "googleurl/src/gurl.h" +#include "chrome_frame/ie8_types.h" + +// Typedefs for IInternetProtocol and related methods that we patch. +typedef HRESULT (STDMETHODCALLTYPE* InternetProtocol_Start_Fn)( + IInternetProtocol* this_object, LPCWSTR url, + IInternetProtocolSink* prot_sink, IInternetBindInfo* bind_info, + DWORD flags, HANDLE_PTR reserved); +typedef HRESULT (STDMETHODCALLTYPE* InternetProtocol_Read_Fn)( + IInternetProtocol* this_object, void* buffer, ULONG size, + ULONG* size_read); +typedef HRESULT (STDMETHODCALLTYPE* InternetProtocol_StartEx_Fn)( + IInternetProtocolEx* this_object, IUri* uri, + IInternetProtocolSink* prot_sink, IInternetBindInfo* bind_info, + DWORD flags, HANDLE_PTR reserved); +typedef HRESULT (STDMETHODCALLTYPE* InternetProtocolRoot_Continue_Fn)( + IInternetProtocolRoot* me, PROTOCOLDATA* data); + +// A class to wrap protocol sink in IInternetProtocol::Start[Ex] for +// HTTP and HTTPS protocols. +// +// This is an alternative to a mime filter and we have to do this in order +// to inspect initial portion of HTML for 'chrome' meta tag and report +// a different mime type in that case. +// +// We implement several documented interfaces +// supported by the original sink provided by urlmon. There are a few +// undocumented interfaces that we have chosen not to implement +// but delegate simply the QI. +class ProtocolSinkWrap + : public CComObjectRootEx<CComMultiThreadModel>, + public IInternetProtocolSink, + public IInternetBindInfoEx, + public IServiceProvider, + public IAuthenticate, + public IInternetProtocolEx, + public IInternetPriority, + public IWrappedProtocol, + // public IPreBindingSupport, // undocumented + // public ITransProtocolSink, // Undocumented + // public ITransactionInternal, // undocumented + public IUriContainer { + public: + +#define COM_INTERFACE_ENTRY_IF_DELEGATE_SUPPORTS(x) \ + COM_INTERFACE_ENTRY_FUNC(_ATL_IIDOF(x), \ + offsetofclass(x, _ComMapClass), \ + IfDelegateSupports) + +BEGIN_COM_MAP(ProtocolSinkWrap) + COM_INTERFACE_ENTRY(IInternetProtocolSink) + COM_INTERFACE_ENTRY(IInternetBindInfo) + COM_INTERFACE_ENTRY(IInternetBindInfoEx) + COM_INTERFACE_ENTRY(IServiceProvider) + COM_INTERFACE_ENTRY(IAuthenticate) + COM_INTERFACE_ENTRY(IInternetProtocolRoot) + COM_INTERFACE_ENTRY(IInternetProtocol) + COM_INTERFACE_ENTRY(IInternetProtocolEx) + COM_INTERFACE_ENTRY(IInternetPriority) + COM_INTERFACE_ENTRY(IWrappedProtocol) + COM_INTERFACE_ENTRY_IF_DELEGATE_SUPPORTS(IUriContainer) + COM_INTERFACE_ENTRY_FUNC_BLIND(0, CheckOutgoingInterface) +END_COM_MAP() + + ProtocolSinkWrap(); + virtual ~ProtocolSinkWrap(); + + bool Initialize(IInternetProtocol* protocol, + IInternetProtocolSink* original_sink, const wchar_t* url); + + static bool PatchProtocolHandler(const wchar_t* dll, + const CLSID& handler_clsid); + + // IInternetProtocol/Ex patches. + static HRESULT STDMETHODCALLTYPE OnStart(InternetProtocol_Start_Fn orig_start, + IInternetProtocol* protocol, LPCWSTR url, + IInternetProtocolSink* prot_sink, IInternetBindInfo* bind_info, + DWORD flags, HANDLE_PTR reserved); + + static HRESULT STDMETHODCALLTYPE OnStartEx( + InternetProtocol_StartEx_Fn orig_start_ex, IInternetProtocolEx* protocol, + IUri* uri, IInternetProtocolSink* prot_sink, + IInternetBindInfo* bind_info, DWORD flags, HANDLE_PTR reserved); + + static HRESULT STDMETHODCALLTYPE OnRead(InternetProtocol_Read_Fn orig_read, + IInternetProtocol* protocol, void* buffer, ULONG size, ULONG* size_read); + + // IInternetProtocolSink methods + STDMETHOD(Switch)(PROTOCOLDATA* protocol_data); + STDMETHOD(ReportProgress)(ULONG status_code, LPCWSTR status_text); + STDMETHOD(ReportData)(DWORD flags, ULONG progress, ULONG max_progress); + STDMETHOD(ReportResult)(HRESULT result, DWORD error, LPCWSTR result_text); + + // IInternetBindInfoEx + STDMETHOD(GetBindInfo)(DWORD* flags, BINDINFO* bind_info); + STDMETHOD(GetBindString)(ULONG string_type, LPOLESTR* string_array, + ULONG array_size, ULONG* size_returned); + STDMETHOD(GetBindInfoEx)(DWORD *flags, BINDINFO* bind_info, + DWORD* bindf2, DWORD *reserved); + + // IServiceProvider + STDMETHOD(QueryService)(REFGUID service_guid, REFIID riid, void** service); + + // IAuthenticate + STDMETHOD(Authenticate)(HWND* window, LPWSTR* user_name, LPWSTR* password); + + // IInternetProtocolEx + STDMETHOD(Start)(LPCWSTR url, IInternetProtocolSink *protocol_sink, + IInternetBindInfo* bind_info, DWORD flags, HANDLE_PTR reserved); + STDMETHOD(Continue)(PROTOCOLDATA* protocol_data); + STDMETHOD(Abort)(HRESULT reason, DWORD options); + STDMETHOD(Terminate)(DWORD options); + STDMETHOD(Suspend)(); + STDMETHOD(Resume)(); + STDMETHOD(Read)(void *buffer, ULONG size, ULONG* size_read); + STDMETHOD(Seek)(LARGE_INTEGER move, DWORD origin, ULARGE_INTEGER* new_pos); + STDMETHOD(LockRequest)(DWORD options); + STDMETHOD(UnlockRequest)(); + STDMETHOD(StartEx)(IUri* uri, IInternetProtocolSink* protocol_sink, + IInternetBindInfo* bind_info, DWORD flags, HANDLE_PTR reserved); + + // IInternetPriority + STDMETHOD(SetPriority)(LONG priority); + STDMETHOD(GetPriority)(LONG* priority); + + // IWrappedProtocol + STDMETHOD(GetWrapperCode)(LONG *code, DWORD_PTR reserved); + + // public IUriContainer + STDMETHOD(GetIUri)(IUri** uri); + + // IPreBindingSupport, // undocumented + // ITransProtocolSink, // Undocumented + // ITransactionInternal, // undocumented + + protected: + enum RendererType { + UNDETERMINED, + CHROME, + OTHER + }; + + typedef std::map<IInternetProtocol*, ProtocolSinkWrap*> ProtocolSinkMap; + static const int kMaxContentSniffLength = 1024; + + static scoped_refptr<ProtocolSinkWrap> InstanceFromProtocol( + IInternetProtocol* protocol); + static HRESULT WebBrowserFromProtocolSink(IInternetProtocolSink* sink, + IWebBrowser2** web_browser); + static ScopedComPtr<IInternetProtocolSink> MaybeWrapSink( + IInternetProtocol* protocol, IInternetProtocolSink* prot_sink, + const wchar_t* url); + static HRESULT WINAPI CheckOutgoingInterface(void* obj, REFIID iid, + LPVOID* ret, DWORD cookie); + static HRESULT WINAPI IfDelegateSupports(void* obj, REFIID iid, + LPVOID* ret, DWORD cookie); + + void DetermineRendererType(); + HRESULT OnReadImpl(void* buffer, ULONG size, ULONG* size_read, + InternetProtocol_Read_Fn orig_read); + + bool is_undetermined() const { + return (UNDETERMINED == renderer_type_); + } + RendererType renderer_type() const { + return renderer_type_; + } + + // WARNING: Don't use GURL variables here. Please see + // http://b/issue?id=2102171 for details. + + // Remember original sink + CComPtr<IInternetProtocolSink> delegate_; + // Cannot take a reference on the protocol. + IInternetProtocol* protocol_; + RendererType renderer_type_; + + // Buffer for accumulated data including 1 extra for NULL-terminator + char buffer_[kMaxContentSniffLength + 1]; + unsigned long buffer_size_; + unsigned long buffer_pos_; + + // Accumulated result + bool is_saved_result_; + HRESULT result_code_; + DWORD result_error_; + std::wstring result_text_; + // For tracking re-entrency and preventing duplicate Read()s from + // distorting the outcome of ReportData. + int report_data_recursiveness_; + + static ProtocolSinkMap sink_map_; + // TODO(joshia): Replace with Lock + static CComAutoCriticalSection sink_map_lock_; + + std::wstring url_; + + private: + DISALLOW_COPY_AND_ASSIGN(ProtocolSinkWrap); +}; + + +#endif // CHROME_FRAME_PROTOCOL_SINK_WRAP_H_ + diff --git a/chrome_frame/rename_me_to_supplement.gypi b/chrome_frame/rename_me_to_supplement.gypi new file mode 100644 index 0000000..fbe6e55 --- /dev/null +++ b/chrome_frame/rename_me_to_supplement.gypi @@ -0,0 +1,24 @@ +# Copyright (c) 2009 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.
+
+# It is used to override the Chrome Frame appid, as well as
+# add extra required source files and defines. The file is checked in as
+# rename_me_to_supplement.gypi so as to not be active on the official builder.
+# This is required because with these fields present, Chrome will build in
+# Chrome Frame mode, which isn't what the Chrome official builder wants yet.
+#
+# Renaming this file to supplement.gypi will cause gyp to pick it up.
+# supplement.gypi is a magic gyp include file that gets pulled in before any
+# other includes.
+#
+# The official builder will define these extra vars and whatnot in the build
+# scripts themselves.
+{
+ 'variables': {
+ 'google_update_appid': '{8BA986DA-5100-405E-AA35-86F34A02ACBF}',
+ 'extra_installer_util_sources': 1,
+ 'branding': 'Chrome',
+ 'experimental_build_define': 1,
+ },
+}
\ No newline at end of file diff --git a/chrome_frame/resource.h b/chrome_frame/resource.h new file mode 100644 index 0000000..53f51a5 --- /dev/null +++ b/chrome_frame/resource.h @@ -0,0 +1,19 @@ +//{{NO_DEPENDENCIES}} + +// Copyright (c) 2009 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. + +// Microsoft Visual C++ generated include file. +// Used by resources/tlb_resource.rc + +// Default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 204 +#define _APS_NEXT_COMMAND_VALUE 32770 +#define _APS_NEXT_CONTROL_VALUE 205 +#define _APS_NEXT_SYMED_VALUE 108 +#endif +#endif diff --git a/chrome_frame/resources/chrome_frame_resources.grd b/chrome_frame/resources/chrome_frame_resources.grd new file mode 100644 index 0000000..c249ce8 --- /dev/null +++ b/chrome_frame/resources/chrome_frame_resources.grd @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<!-- +Copyright (c) 2009 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. +--> + +<!-- +Embeded strings, branding resource, etc. See chrome_frame_strings.grd +for localizable strings +--> + +<grit latest_public_release="0" current_release="1"> + <outputs> + <output filename="grit/chrome_frame_resources.h" type="rc_header"> + <emit emit_type='prepend'></emit> + </output> + <output filename="chrome_frame_resources.rc" type="rc_all" lang="en"/> + <output filename="chrome_frame_resources.pak" type="data_package" /> + </outputs> + <release seq="1"> + <messages> + <!-- TODO(slightlyoff): should these be in chrome_frame_strings.grd + instead? --> + <message name="IDS_PROJNAME"> + ChromeTab + </message> + <message name="IDS_VERSIONMISMATCH_HEADER"> + ChromeFrame Update. + </message> + <message name="IDS_VERSIONMISMATCH"> + ChromeFrame has been updated. Please restart your browser. Chrome version: <ph name="TODO_0001">%ls<ex>TODO</ex></ph>, Chrome Frame version: <ph name="TODO_0002">%ls<ex>TODO</ex></ph> + </message> + <message name="IDS_VERSIONUNKNOWN"> + Very old + </message> + </messages> + <structures first_id="50000"> + <structure name="IDD_FIND_DIALOG" file="structured_resources.rc" type="dialog" > + </structure> + </structures> + <includes> + <include name="IDB_CHROME_ACTIVE_DOCUMENT" file="../chrome_active_document.bmp" + type="BITMAP" /> + <include name="IDR_BHO" file="../bho.rgs" type="REGISTRY" /> + <include name="IDR_CHROMETAB" file="../chrome_tab.rgs" type="REGISTRY" /> + <include name="IDR_CHROMEPROTOCOL" file="../chrome_protocol.rgs" type="REGISTRY" /> + <include name="IDR_CHROMEACTIVEDOCUMENT" file="../chrome_active_document.rgs" + type="REGISTRY" /> + <include name="IDR_CHROMEFRAME" file="../chrome_frame_activex.rgs" type="REGISTRY" /> + <include name="IDR_CHROMEFRAME_NPAPI" file="../chrome_frame_npapi.rgs" type="REGISTRY" /> + </includes> + </release> +</grit> diff --git a/chrome_frame/resources/chrome_frame_strings.grd b/chrome_frame/resources/chrome_frame_strings.grd new file mode 100644 index 0000000..39bcfd9 --- /dev/null +++ b/chrome_frame/resources/chrome_frame_strings.grd @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<!-- +Copyright (c) 2009 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. +--> + +<!-- Definitions of resources that will be translated for each locale. +--> + +<grit base_dir="." latest_public_release="0" current_release="1" + source_lang_id="en" enc_check="möl"> + <outputs> + <output filename="grit/chrome_frame_strings.h" type="rc_header"> + <emit emit_type='prepend'></emit> + </output> + <output filename="chrome_frame_strings.rc" type="rc_all" lang="en" /> + </outputs> + <translations> + <!-- + <file path="chrome_frame_strings_en-GB.xtb" lang="en-GB" /> + ... + --> + </translations> + <release seq="1" allow_pseudo="false"> + <messages fallback_to_english="true" first_id="30000"> + + <!-- Menus --> + <message name="IDS_CHROME_FRAME_MENU_ABOUT" desc="About Chrome Frame label"> + About Chrome Frame... + </message> + + <!-- General application strings --> + <message name="IDS_CHROME_FRAME_NAME" desc="Official plugin name."> + Google Chrome Frame + </message> + </messages> + </release> +</grit> diff --git a/chrome_frame/resources/structured_resources.rc b/chrome_frame/resources/structured_resources.rc new file mode 100644 index 0000000..22e0337 --- /dev/null +++ b/chrome_frame/resources/structured_resources.rc @@ -0,0 +1,24 @@ +// Copyright (c) 2009 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. + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_FIND_DIALOG DIALOGEX 0, 0, 278, 60 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU +EXSTYLE WS_EX_CONTROLPARENT +CAPTION "Find" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + EDITTEXT IDC_FIND_TEXT,51,7,154,14,ES_AUTOHSCROLL + DEFPUSHBUTTON "&Find Next",IDOK,221,7,50,14 + PUSHBUTTON "Cancel",IDCANCEL,221,24,50,14 + CONTROL "Match &case",IDC_MATCH_CASE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,6,24,52,10 + GROUPBOX "Direction",IDC_STATIC,85,24,119,24 + CONTROL "&Down",IDC_DIRECTION_DOWN,"Button",BS_AUTORADIOBUTTON | WS_GROUP | WS_TABSTOP,101,34,34,10 + CONTROL "&Up",IDC_DIRECTION_UP,"Button",BS_AUTORADIOBUTTON,155,34,38,10 + LTEXT "Fi&nd what:",IDC_STATIC,6,7,35,8 +END diff --git a/chrome_frame/resources/tlb_resource.rc b/chrome_frame/resources/tlb_resource.rc new file mode 100644 index 0000000..b16c9d4 --- /dev/null +++ b/chrome_frame/resources/tlb_resource.rc @@ -0,0 +1,68 @@ +// Copyright (c) 2009 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. + +// NOTE(slightlyoff): the below is generated, and apparently, magical. +// Seemingly innocuous edits totally destroy registration of the DLL. +// Edit at your own peril. + + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "1 TYPELIB ""chrome_tab.tlb""\r\n" + "\0" +END +#else +#include "chrome_tab_version.rc" +#endif // APSTUDIO_INVOKED + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// +1 TYPELIB "chrome_tab.tlb" + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/chrome_frame/scoped_ns_ptr_win.h b/chrome_frame/scoped_ns_ptr_win.h new file mode 100644 index 0000000..91780fd --- /dev/null +++ b/chrome_frame/scoped_ns_ptr_win.h @@ -0,0 +1,149 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_FRAME_SCOPED_NS_PTR_WIN_H_ +#define CHROME_FRAME_SCOPED_NS_PTR_WIN_H_ + +#include "base/logging.h" +#include "base/ref_counted.h" + +#include "third_party/xulrunner-sdk/win/include/xpcom/nsISupports.h" + + +// Utility template to prevent users of ScopedNsPtr from calling AddRef and/or +// Release() without going through the ScopedNsPtr class. +template <class nsInterface> +class BlocknsISupportsMethods : public nsInterface { + private: + NS_IMETHOD QueryInterface(REFNSIID iid, void** object) = 0; + NS_IMETHOD_(nsrefcnt) AddRef() = 0; + NS_IMETHOD_(nsrefcnt) Release() = 0; +}; + +// A smart pointer class for nsISupports. +// Based on ScopedComPtr. +// We have our own class instead of nsCOMPtr. nsCOMPtr has parts of its +// implementation in the xpcomglue lib which we can't use since that lib +// is built with incompatible build flags to ours. +template <class nsInterface, + const nsIID* interface_id = + reinterpret_cast<const nsIID*>(&__uuidof(nsInterface))> +class ScopedNsPtr : public scoped_refptr<nsInterface> { + public: + typedef scoped_refptr<nsInterface> ParentClass; + + ScopedNsPtr() { + } + + explicit ScopedNsPtr(nsInterface* p) : ParentClass(p) { + } + + explicit ScopedNsPtr(const ScopedNsPtr<nsInterface, interface_id>& p) + : ParentClass(p) { + } + + ~ScopedNsPtr() { + // We don't want the smart pointer class to be bigger than the pointer + // it wraps. + COMPILE_ASSERT(sizeof(ScopedNsPtr<nsInterface, interface_id>) == + sizeof(nsInterface*), ScopedNsPtrSize); + } + + // Explicit Release() of the held object. Useful for reuse of the + // ScopedNsPtr instance. + // Note that this function equates to nsISupports::Release and should not + // be confused with e.g. scoped_ptr::release(). + void Release() { + if (ptr_ != NULL) { + ptr_->Release(); + ptr_ = NULL; + } + } + + // Sets the internal pointer to NULL and returns the held object without + // releasing the reference. + nsInterface* Detach() { + nsInterface* p = ptr_; + ptr_ = NULL; + return p; + } + + // Accepts an interface pointer that has already been addref-ed. + void Attach(nsInterface* p) { + DCHECK(ptr_ == NULL); + ptr_ = p; + } + + // Retrieves the pointer address. + // Used to receive object pointers as out arguments (and take ownership). + // The function DCHECKs on the current value being NULL. + // Usage: Foo(p.Receive()); + nsInterface** Receive() { + DCHECK(ptr_ == NULL) << "Object leak. Pointer must be NULL"; + return &ptr_; + } + + template <class Query> + nsresult QueryInterface(Query** p) { + DCHECK(p != NULL); + DCHECK(ptr_ != NULL); + return ptr_->QueryInterface(Query::GetIID(), reinterpret_cast<void**>(p)); + } + + template <class Query> + nsresult QueryInterface(const nsIID& iid, Query** p) { + DCHECK(p != NULL); + DCHECK(ptr_ != NULL); + return ptr_->QueryInterface(iid, reinterpret_cast<void**>(p)); + } + + // Queries |other| for the interface this object wraps and returns the + // error code from the other->QueryInterface operation. + nsresult QueryFrom(nsISupports* other) { + DCHECK(other != NULL); + return other->QueryInterface(iid(), reinterpret_cast<void**>(Receive())); + } + + // Checks if the identity of |other| and this object is the same. + bool IsSameObject(nsISupports* other) { + if (!other && !ptr_) + return true; + + if (!other || !ptr_) + return false; + + nsIID iid = NS_ISUPPORTS_IID; + ScopedNsPtr<nsISupports, iid> my_identity; + QueryInterface(my_identity.Receive()); + + ScopedNsPtr<nsISupports, iid> other_identity; + other->QueryInterface(other_identity.Receive()); + + return static_cast<nsISupports*>(my_identity) == + static_cast<nsISupports*>(other_identity); + } + + // Provides direct access to the interface. + // Here we use a well known trick to make sure we block access to + // IUknown methods so that something bad like this doesn't happen: + // ScopedNsPtr<nsISupports> p(Foo()); + // p->Release(); + // ... later the destructor runs, which will Release() again. + // and to get the benefit of the DCHECKs we add to QueryInterface. + // There's still a way to call these methods if you absolutely must + // by statically casting the ScopedNsPtr instance to the wrapped interface + // and then making the call... but generally that shouldn't be necessary. + BlocknsISupportsMethods<nsInterface>* operator->() const { + DCHECK(ptr_ != NULL); + return reinterpret_cast<BlocknsISupportsMethods<nsInterface>*>(ptr_); + } + + // static methods + + static const nsIID& iid() { + return *interface_id; + } +}; + +#endif // CHROME_FRAME_SCOPED_NS_PTR_WIN_H_ diff --git a/chrome_frame/script_security_manager.h b/chrome_frame/script_security_manager.h new file mode 100644 index 0000000..a586c84 --- /dev/null +++ b/chrome_frame/script_security_manager.h @@ -0,0 +1,785 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef CHROME_FRAME_SCRIPT_SECURITY_MANAGER_H_ +#define CHROME_FRAME_SCRIPT_SECURITY_MANAGER_H_ + +// Gecko headers need this on Windows. +#ifndef XP_WIN +#define XP_WIN +#endif + +#include "chrome_frame/ns_associate_iid_win.h" +#include "third_party/xulrunner-sdk/win/include/caps/nsIScriptSecurityManager.h" + +ASSOCIATE_IID(NS_ISCRIPTSECURITYMANAGER_IID_STR, nsIScriptSecurityManager); + +// Because we need to support both Firefox 3.0.x and 3.5.x, and because Mozilla +// changed these unfrozen interfaces, both are declared here, with specific +// names for specific versions. Doing this makes it easier to adopt new +// version of the interfaces as they evolve in future version of Firefox. + +// The xxx_FF30 declatations below were taken from the file +// nsIScriptSecurityManager.h in the gecko 1.9.0.5 SDK. +// The xxx_FF35 declarations below were taken from the file +// nsIScriptSecurityManager.h in the gecko 1.9.1 SDK. + +#define NS_IXPCSECURITYMANAGER_IID_FF30 \ + {0x31431440, 0xf1ce, 0x11d2, \ + { 0x98, 0x5a, 0x00, 0x60, 0x08, 0x96, 0x24, 0x22 }} + +class NS_NO_VTABLE nsIXPCSecurityManager_FF30 : public nsISupports { + public: + + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IXPCSECURITYMANAGER_IID_FF30) + + /** + * These flags are used when calling nsIXPConnect::SetSecurityManager + */ + enum { HOOK_CREATE_WRAPPER = 1U }; + + enum { HOOK_CREATE_INSTANCE = 2U }; + + enum { HOOK_GET_SERVICE = 4U }; + + enum { HOOK_CALL_METHOD = 8U }; + + enum { HOOK_GET_PROPERTY = 16U }; + + enum { HOOK_SET_PROPERTY = 32U }; + + enum { HOOK_ALL = 63U }; + + /** + * For each of these hooks returning NS_OK means 'let the action continue'. + * Returning an error code means 'veto the action'. XPConnect will return + * JS_FALSE to the js engine if the action is vetoed. The implementor of this + * interface is responsible for setting a JS exception into the JSContext + * if that is appropriate. + */ + /* void CanCreateWrapper (in JSContextPtr aJSContext, in nsIIDRef aIID, + in nsISupports aObj, in nsIClassInfo aClassInfo, + inout voidPtr aPolicy); */ + NS_IMETHOD CanCreateWrapper(JSContext* aJSContext, const nsIID & aIID, + nsISupports* aObj, nsIClassInfo* aClassInfo, + void** aPolicy) = 0; + + /* void CanCreateInstance (in JSContextPtr aJSContext, in nsCIDRef aCID); */ + NS_IMETHOD CanCreateInstance(JSContext* aJSContext, const nsCID& aCID) = 0; + + /* void CanGetService (in JSContextPtr aJSContext, in nsCIDRef aCID); */ + NS_IMETHOD CanGetService(JSContext* aJSContext, const nsCID& aCID) = 0; + + enum { ACCESS_CALL_METHOD = 0U }; + + enum { ACCESS_GET_PROPERTY = 1U }; + + enum { ACCESS_SET_PROPERTY = 2U }; + + /* void CanAccess (in PRUint32 aAction, + in nsAXPCNativeCallContextPtr aCallContext, + in JSContextPtr aJSContext, in JSObjectPtr aJSObject, + in nsISupports aObj, in nsIClassInfo aClassInfo, + in JSVal aName, inout voidPtr aPolicy); */ + NS_IMETHOD CanAccess(PRUint32 aAction, + nsAXPCNativeCallContext* aCallContext, + JSContext* aJSContext, + JSObject* aJSObject, + nsISupports* aObj, + nsIClassInfo* aClassInfo, + jsval aName, void** aPolicy) = 0; + +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsIXPCSecurityManager_FF30, + NS_IXPCSECURITYMANAGER_IID_FF30); + + +#define NS_ISCRIPTSECURITYMANAGER_IID_STR_FF30 \ + "3fffd8e8-3fea-442e-a0ed-2ba81ae197d5" + +#define NS_ISCRIPTSECURITYMANAGER_IID_FF30 \ + {0x3fffd8e8, 0x3fea, 0x442e, \ + { 0xa0, 0xed, 0x2b, 0xa8, 0x1a, 0xe1, 0x97, 0xd5 }} + +/** + * WARNING!! The JEP needs to call GetSubjectPrincipal() + * to support JavaScript-to-Java LiveConnect. So every change to the + * nsIScriptSecurityManager interface (big enough to change its IID) also + * breaks JavaScript-to-Java LiveConnect on mac. + * + * If you REALLY have to change this interface, please mark your bug as + * blocking bug 293973. + */ +class NS_NO_VTABLE NS_SCRIPTABLE nsIScriptSecurityManager_FF30 : + public nsIXPCSecurityManager_FF30 { + public: + + NS_DECLARE_STATIC_IID_ACCESSOR(NS_ISCRIPTSECURITYMANAGER_IID_FF30) + + /** + * Checks whether the running script is allowed to access aProperty. + */ + /* [noscript] void checkPropertyAccess (in JSContextPtr aJSContext, + in JSObjectPtr aJSObject, + in string aClassName, + in JSVal aProperty, + in PRUint32 aAction); */ + NS_IMETHOD CheckPropertyAccess(JSContext* aJSContext, JSObject* aJSObject, + const char* aClassName, jsval aProperty, + PRUint32 aAction) = 0; + + /** + * Checks whether the running script is allowed to connect to aTargetURI + */ + /* [noscript] void checkConnect (in JSContextPtr aJSContext, + in nsIURI aTargetURI, in string aClassName, + in string aProperty); */ + NS_IMETHOD CheckConnect(JSContext* aJSContext, nsIURI* aTargetURI, + const char* aClassName, const char* aProperty) = 0; + + /** + * Check that the script currently running in context "cx" can load "uri". + * + * Will return error code NS_ERROR_DOM_BAD_URI if the load request + * should be denied. + * + * @param cx the JSContext of the script causing the load + * @param uri the URI that is being loaded + */ + /* [noscript] void checkLoadURIFromScript (in JSContextPtr cx, + in nsIURI uri); */ + NS_IMETHOD CheckLoadURIFromScript(JSContext* cx, nsIURI* uri) = 0; + + /** + * Default CheckLoadURI permissions + */ + enum { STANDARD = 0U }; + + enum { LOAD_IS_AUTOMATIC_DOCUMENT_REPLACEMENT = 1U }; + + enum { ALLOW_CHROME = 2U }; + + enum { DISALLOW_INHERIT_PRINCIPAL = 4U }; + + enum { DISALLOW_SCRIPT_OR_DATA = 4U }; + + enum { DISALLOW_SCRIPT = 8U }; + + /** + * Check that content with principal aPrincipal can load "uri". + * + * Will return error code NS_ERROR_DOM_BAD_URI if the load request + * should be denied. + * + * @param aPrincipal the principal identifying the actor causing the load + * @param uri the URI that is being loaded + * @param flags the permission set, see above + */ + /* void checkLoadURIWithPrincipal (in nsIPrincipal aPrincipal, in nsIURI uri, + in unsigned long flags); */ + NS_SCRIPTABLE NS_IMETHOD CheckLoadURIWithPrincipal(nsIPrincipal* aPrincipal, + nsIURI* uri, PRUint32 flags) = 0; + + /** + * Check that content from "from" can load "uri". + * + * Will return error code NS_ERROR_DOM_BAD_URI if the load request + * should be denied. + * + * @param from the URI causing the load + * @param uri the URI that is being loaded + * @param flags the permission set, see above + * + * @deprecated Use checkLoadURIWithPrincipal instead of this function. + */ + /* void checkLoadURI (in nsIURI from, in nsIURI uri, + in unsigned long flags); */ + NS_SCRIPTABLE NS_IMETHOD CheckLoadURI(nsIURI* from, nsIURI* uri, + PRUint32 flags) = 0; + + /** + * Similar to checkLoadURIWithPrincipal but there are two differences: + * + * 1) The URI is a string, not a URI object. + * 2) This function assumes that the URI may still be subject to fixup (and + * hence will check whether fixed-up versions of the URI are allowed to + * load as well); if any of the versions of this URI is not allowed, this + * function will return error code NS_ERROR_DOM_BAD_URI. + */ + /* void checkLoadURIStrWithPrincipal (in nsIPrincipal aPrincipal, + in AUTF8String uri, + in unsigned long flags); */ + NS_SCRIPTABLE NS_IMETHOD CheckLoadURIStrWithPrincipal( + nsIPrincipal* aPrincipal, const nsACString& uri, PRUint32 flags) = 0; + + /** + * Same as CheckLoadURI but takes string arguments for ease of use + * by scripts + * + * @deprecated Use checkLoadURIStrWithPrincipal instead of this function. + */ + /* void checkLoadURIStr (in AUTF8String from, in AUTF8String uri, + in unsigned long flags); */ + NS_SCRIPTABLE NS_IMETHOD CheckLoadURIStr(const nsACString& from, + const nsACString& uri, + PRUint32 flags) = 0; + + /** + * Check that the function 'funObj' is allowed to run on 'targetObj' + * + * Will return error code NS_ERROR_DOM_SECURITY_ERR if the function + * should not run + * + * @param cx The current active JavaScript context. + * @param funObj The function trying to run.. + * @param targetObj The object the function will run on. + */ + /* [noscript] void checkFunctionAccess (in JSContextPtr cx, + in voidPtr funObj, + in voidPtr targetObj); */ + NS_IMETHOD CheckFunctionAccess(JSContext* cx, void* funObj, + void* targetObj) = 0; + + /** + * Return true if content from the given principal is allowed to + * execute scripts. + */ + /* [noscript] boolean canExecuteScripts (in JSContextPtr cx, + in nsIPrincipal principal); */ + NS_IMETHOD CanExecuteScripts(JSContext* cx, nsIPrincipal* principal, + PRBool* _retval) = 0; + + /** + * Return the principal of the innermost frame of the currently + * executing script. Will return null if there is no script + * currently executing. + */ + /* [noscript] nsIPrincipal getSubjectPrincipal (); */ + NS_IMETHOD GetSubjectPrincipal(nsIPrincipal** _retval) = 0; + + /** + * Return the all-powerful system principal. + */ + /* [noscript] nsIPrincipal getSystemPrincipal (); */ + NS_IMETHOD GetSystemPrincipal(nsIPrincipal** _retval) = 0; + + /** + * Return a principal with the specified certificate fingerprint, subject + * name (the full name or concatenated set of names of the entity + * represented by the certificate), pretty name, certificate, and + * codebase URI. The certificate fingerprint and subject name MUST be + * nonempty; otherwise an error will be thrown. Similarly, aCert must + * not be null. + */ + /* [noscript] nsIPrincipal getCertificatePrincipal ( + in AUTF8String aCertFingerprint, in AUTF8String aSubjectName, + in AUTF8String aPrettyName, in nsISupports aCert, in nsIURI aURI); */ + NS_IMETHOD GetCertificatePrincipal(const nsACString& aCertFingerprint, + const nsACString& aSubjectName, + const nsACString& aPrettyName, + nsISupports* aCert, + nsIURI* aURI, nsIPrincipal** _retval) = 0; + + /** + * Return a principal that has the same origin as aURI. + */ + /* nsIPrincipal getCodebasePrincipal (in nsIURI aURI); */ + NS_SCRIPTABLE NS_IMETHOD GetCodebasePrincipal(nsIURI* aURI, + nsIPrincipal **_retval) = 0; + + /** + * Request that 'capability' can be enabled by scripts or applets + * running with 'principal'. Will prompt user if + * necessary. Returns nsIPrincipal::ENABLE_GRANTED or + * nsIPrincipal::ENABLE_DENIED based on user's choice. + */ + /* [noscript] short requestCapability (in nsIPrincipal principal, + in string capability); */ + NS_IMETHOD RequestCapability(nsIPrincipal* principal, + const char* capability, PRInt16* _retval) = 0; + + /** + * Return true if the currently executing script has 'capability' enabled. + */ + /* boolean isCapabilityEnabled (in string capability); */ + NS_SCRIPTABLE NS_IMETHOD IsCapabilityEnabled(const char* capability, + PRBool* _retval) = 0; + + /** + * Enable 'capability' in the innermost frame of the currently executing + * script. + */ + /* void enableCapability (in string capability); */ + NS_SCRIPTABLE NS_IMETHOD EnableCapability(const char* capability) = 0; + + /** + * Remove 'capability' from the innermost frame of the currently + * executing script. Any setting of 'capability' from enclosing + * frames thus comes into effect. + */ + /* void revertCapability (in string capability); */ + NS_SCRIPTABLE NS_IMETHOD RevertCapability(const char* capability) = 0; + + /** + * Disable 'capability' in the innermost frame of the currently executing + * script. + */ + /* void disableCapability (in string capability); */ + NS_SCRIPTABLE NS_IMETHOD DisableCapability(const char* capability) = 0; + + /** + * Allow 'certificateID' to enable 'capability.' Can only be performed + * by code signed by the system certificate. + */ + /* void setCanEnableCapability (in AUTF8String certificateFingerprint, + in string capability, in short canEnable); */ + NS_SCRIPTABLE NS_IMETHOD SetCanEnableCapability( + const nsACString& certificateFingerprint, const char* capability, + PRInt16 canEnable) = 0; + + /** + * Return the principal of the specified object in the specified context. + */ + /* [noscript] nsIPrincipal getObjectPrincipal (in JSContextPtr cx, + in JSObjectPtr obj); */ + NS_IMETHOD GetObjectPrincipal(JSContext* cx, JSObject* obj, + nsIPrincipal** _retval) = 0; + + /** + * Returns true if the principal of the currently running script is the + * system principal, false otherwise. + */ + /* [noscript] boolean subjectPrincipalIsSystem (); */ + NS_IMETHOD SubjectPrincipalIsSystem(PRBool* _retval) = 0; + + /** + * Returns OK if aJSContext and target have the same "origin" + * (scheme, host, and port). + */ + /* [noscript] void checkSameOrigin (in JSContextPtr aJSContext, + in nsIURI aTargetURI); */ + NS_IMETHOD CheckSameOrigin(JSContext* aJSContext, nsIURI* aTargetURI) = 0; + + /** + * Returns OK if aSourceURI and target have the same "origin" + * (scheme, host, and port). + * ReportError flag suppresses error reports for functions that + * don't need reporting. + */ + /* void checkSameOriginURI (in nsIURI aSourceURI, in nsIURI aTargetURI, + in boolean reportError); */ + NS_SCRIPTABLE NS_IMETHOD CheckSameOriginURI(nsIURI* aSourceURI, + nsIURI* aTargetURI, + PRBool reportError) = 0; + + /** + * Returns the principal of the global object of the given context, or null + * if no global or no principal. + */ + /* [noscript] nsIPrincipal getPrincipalFromContext (in JSContextPtr cx); */ + NS_IMETHOD GetPrincipalFromContext(JSContext* cx, + nsIPrincipal** _retval) = 0; + + /** + * Get the principal for the given channel. This will typically be the + * channel owner if there is one, and the codebase principal for the + * channel's URI otherwise. aChannel must not be null. + */ + /* nsIPrincipal getChannelPrincipal (in nsIChannel aChannel); */ + NS_SCRIPTABLE NS_IMETHOD GetChannelPrincipal(nsIChannel* aChannel, + nsIPrincipal** _retval) = 0; + + /** + * Check whether a given principal is a system principal. This allows us + * to avoid handing back the system principal to script while allowing + * script to check whether a given principal is system. + */ + /* boolean isSystemPrincipal (in nsIPrincipal aPrincipal); */ + NS_SCRIPTABLE NS_IMETHOD IsSystemPrincipal(nsIPrincipal* aPrincipal, + PRBool* _retval) = 0; + + /** + * Same as getSubjectPrincipal(), only faster. cx must *never* be + * passed null, and it must be the context on the top of the + * context stack. Does *not* reference count the returned + * principal. + */ + /* [noscript, notxpcom] nsIPrincipal getCxSubjectPrincipal ( + in JSContextPtr cx); */ + NS_IMETHOD_(nsIPrincipal *) GetCxSubjectPrincipal(JSContext* cx) = 0; + +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsIScriptSecurityManager_FF30, + NS_ISCRIPTSECURITYMANAGER_IID_FF30); + +ASSOCIATE_IID(NS_ISCRIPTSECURITYMANAGER_IID_STR_FF30, + nsIScriptSecurityManager_FF30); + + +#define NS_ISCRIPTSECURITYMANAGER_IID_STR_FF35 \ + "f8e350b9-9f31-451a-8c8f-d10fea26b780" + +#define NS_ISCRIPTSECURITYMANAGER_IID_FF35 \ + {0xf8e350b9, 0x9f31, 0x451a, \ + { 0x8c, 0x8f, 0xd1, 0x0f, 0xea, 0x26, 0xb7, 0x80 }} + +#ifndef NS_OUTPARAM +#define NS_OUTPARAM +#endif + +/** + * WARNING!! The JEP needs to call GetSubjectPrincipal() + * to support JavaScript-to-Java LiveConnect. So every change to the + * nsIScriptSecurityManager interface (big enough to change its IID) also + * breaks JavaScript-to-Java LiveConnect on mac. + * + * If you REALLY have to change this interface, please mark your bug as + * blocking bug 293973. + */ +class NS_NO_VTABLE NS_SCRIPTABLE nsIScriptSecurityManager_FF35 : + public nsIXPCSecurityManager_FF30 { + public: + + NS_DECLARE_STATIC_IID_ACCESSOR(NS_ISCRIPTSECURITYMANAGER_IID_FF35) + + /** + * Checks whether the running script is allowed to access aProperty. + */ + /* [noscript] void checkPropertyAccess (in JSContextPtr aJSContext, + in JSObjectPtr aJSObject, + in string aClassName, + in JSVal aProperty, + in PRUint32 aAction); */ + NS_IMETHOD CheckPropertyAccess(JSContext* aJSContext, JSObject* aJSObject, + const char* aClassName, jsval aProperty, + PRUint32 aAction) = 0; + + /** + * Checks whether the running script is allowed to connect to aTargetURI + */ + /* [noscript] void checkConnect (in JSContextPtr aJSContext, + in nsIURI aTargetURI, in string aClassName, + in string aProperty); */ + NS_IMETHOD CheckConnect(JSContext* aJSContext, nsIURI* aTargetURI, + const char* aClassName, const char* aProperty) = 0; + + /** + * Check that the script currently running in context "cx" can load "uri". + * + * Will return error code NS_ERROR_DOM_BAD_URI if the load request + * should be denied. + * + * @param cx the JSContext of the script causing the load + * @param uri the URI that is being loaded + */ + /* [noscript] void checkLoadURIFromScript (in JSContextPtr cx, + in nsIURI uri); */ + NS_IMETHOD CheckLoadURIFromScript(JSContext* cx, nsIURI* uri) = 0; + + /** + * Default CheckLoadURI permissions + */ + enum { STANDARD = 0U }; + + enum { LOAD_IS_AUTOMATIC_DOCUMENT_REPLACEMENT = 1U }; + + enum { ALLOW_CHROME = 2U }; + + enum { DISALLOW_INHERIT_PRINCIPAL = 4U }; + + enum { DISALLOW_SCRIPT_OR_DATA = 4U }; + + enum { DISALLOW_SCRIPT = 8U }; + + /** + * Check that content with principal aPrincipal can load "uri". + * + * Will return error code NS_ERROR_DOM_BAD_URI if the load request + * should be denied. + * + * @param aPrincipal the principal identifying the actor causing the load + * @param uri the URI that is being loaded + * @param flags the permission set, see above + */ + /* void checkLoadURIWithPrincipal (in nsIPrincipal aPrincipal, in nsIURI uri, + in unsigned long flags); */ + NS_SCRIPTABLE NS_IMETHOD CheckLoadURIWithPrincipal(nsIPrincipal* aPrincipal, + nsIURI* uri, + PRUint32 flags) = 0; + + /** + * Check that content from "from" can load "uri". + * + * Will return error code NS_ERROR_DOM_BAD_URI if the load request + * should be denied. + * + * @param from the URI causing the load + * @param uri the URI that is being loaded + * @param flags the permission set, see above + * + * @deprecated Use checkLoadURIWithPrincipal instead of this function. + */ + /* void checkLoadURI (in nsIURI from, in nsIURI uri, + in unsigned long flags); */ + NS_SCRIPTABLE NS_IMETHOD CheckLoadURI(nsIURI* from, nsIURI* uri, + PRUint32 flags) = 0; + + /** + * Similar to checkLoadURIWithPrincipal but there are two differences: + * + * 1) The URI is a string, not a URI object. + * 2) This function assumes that the URI may still be subject to fixup (and + * hence will check whether fixed-up versions of the URI are allowed to + * load as well); if any of the versions of this URI is not allowed, this + * function will return error code NS_ERROR_DOM_BAD_URI. + */ + /* void checkLoadURIStrWithPrincipal (in nsIPrincipal aPrincipal, + in AUTF8String uri, + in unsigned long flags); */ + NS_SCRIPTABLE NS_IMETHOD CheckLoadURIStrWithPrincipal( + nsIPrincipal* aPrincipal, const nsACString& uri, PRUint32 flags) = 0; + + /** + * Same as CheckLoadURI but takes string arguments for ease of use + * by scripts + * + * @deprecated Use checkLoadURIStrWithPrincipal instead of this function. + */ + /* void checkLoadURIStr (in AUTF8String from, in AUTF8String uri, + in unsigned long flags); */ + NS_SCRIPTABLE NS_IMETHOD CheckLoadURIStr(const nsACString& from, + const nsACString & uri, + PRUint32 flags) = 0; + + /** + * Check that the function 'funObj' is allowed to run on 'targetObj' + * + * Will return error code NS_ERROR_DOM_SECURITY_ERR if the function + * should not run + * + * @param cx The current active JavaScript context. + * @param funObj The function trying to run.. + * @param targetObj The object the function will run on. + */ + /* [noscript] void checkFunctionAccess (in JSContextPtr cx, + in voidPtr funObj, + in voidPtr targetObj); */ + NS_IMETHOD CheckFunctionAccess(JSContext* cx, void* funObj, + void* targetObj) = 0; + + /** + * Return true if content from the given principal is allowed to + * execute scripts. + */ + /* [noscript] boolean canExecuteScripts (in JSContextPtr cx, + in nsIPrincipal principal); */ + NS_IMETHOD CanExecuteScripts(JSContext* cx, nsIPrincipal* principal, + PRBool* _retval NS_OUTPARAM) = 0; + + /** + * Return the principal of the innermost frame of the currently + * executing script. Will return null if there is no script + * currently executing. + */ + /* [noscript] nsIPrincipal getSubjectPrincipal (); */ + NS_IMETHOD GetSubjectPrincipal(nsIPrincipal **_retval NS_OUTPARAM) = 0; + + /** + * Return the all-powerful system principal. + */ + /* [noscript] nsIPrincipal getSystemPrincipal (); */ + NS_IMETHOD GetSystemPrincipal(nsIPrincipal **_retval NS_OUTPARAM) = 0; + + /** + * Return a principal with the specified certificate fingerprint, subject + * name (the full name or concatenated set of names of the entity + * represented by the certificate), pretty name, certificate, and + * codebase URI. The certificate fingerprint and subject name MUST be + * nonempty; otherwise an error will be thrown. Similarly, aCert must + * not be null. + */ + /* [noscript] nsIPrincipal getCertificatePrincipal ( + in AUTF8String aCertFingerprint, in AUTF8String aSubjectName, + in AUTF8String aPrettyName, in nsISupports aCert, in nsIURI aURI); */ + NS_IMETHOD GetCertificatePrincipal(const nsACString& aCertFingerprint, + const nsACString& aSubjectName, + const nsACString& aPrettyName, + nsISupports* aCert, nsIURI *aURI, + nsIPrincipal** _retval NS_OUTPARAM) = 0; + + /** + * Return a principal that has the same origin as aURI. + */ + /* nsIPrincipal getCodebasePrincipal (in nsIURI aURI); */ + NS_SCRIPTABLE NS_IMETHOD GetCodebasePrincipal(nsIURI* aURI, + nsIPrincipal** _retval NS_OUTPARAM) = 0; + + /** + * Request that 'capability' can be enabled by scripts or applets + * running with 'principal'. Will prompt user if + * necessary. Returns nsIPrincipal::ENABLE_GRANTED or + * nsIPrincipal::ENABLE_DENIED based on user's choice. + */ + /* [noscript] short requestCapability (in nsIPrincipal principal, + in string capability); */ + NS_IMETHOD RequestCapability(nsIPrincipal* principal, const char* capability, + PRInt16* _retval NS_OUTPARAM) = 0; + + /** + * Return true if the currently executing script has 'capability' enabled. + */ + /* boolean isCapabilityEnabled (in string capability); */ + NS_SCRIPTABLE NS_IMETHOD IsCapabilityEnabled(const char* capability, + PRBool* _retval NS_OUTPARAM) = 0; + + /** + * Enable 'capability' in the innermost frame of the currently executing + * script. + */ + /* void enableCapability (in string capability); */ + NS_SCRIPTABLE NS_IMETHOD EnableCapability(const char* capability) = 0; + + /** + * Remove 'capability' from the innermost frame of the currently + * executing script. Any setting of 'capability' from enclosing + * frames thus comes into effect. + */ + /* void revertCapability (in string capability); */ + NS_SCRIPTABLE NS_IMETHOD RevertCapability(const char* capability) = 0; + + /** + * Disable 'capability' in the innermost frame of the currently executing + * script. + */ + /* void disableCapability (in string capability); */ + NS_SCRIPTABLE NS_IMETHOD DisableCapability(const char* capability) = 0; + + /** + * Allow 'certificateID' to enable 'capability.' Can only be performed + * by code signed by the system certificate. + */ + /* void setCanEnableCapability (in AUTF8String certificateFingerprint, + in string capability, in short canEnable); */ + NS_SCRIPTABLE NS_IMETHOD SetCanEnableCapability( + const nsACString& certificateFingerprint, const char *capability, + PRInt16 canEnable) = 0; + + /** + * Return the principal of the specified object in the specified context. + */ + /* [noscript] nsIPrincipal getObjectPrincipal (in JSContextPtr cx, + in JSObjectPtr obj); */ + NS_IMETHOD GetObjectPrincipal(JSContext* cx, JSObject* obj, + nsIPrincipal** _retval NS_OUTPARAM) = 0; + + /** + * Returns true if the principal of the currently running script is the + * system principal, false otherwise. + */ + /* [noscript] boolean subjectPrincipalIsSystem (); */ + NS_IMETHOD SubjectPrincipalIsSystem(PRBool* _retval NS_OUTPARAM) = 0; + + /** + * Returns OK if aJSContext and target have the same "origin" + * (scheme, host, and port). + */ + /* [noscript] void checkSameOrigin (in JSContextPtr aJSContext, + in nsIURI aTargetURI); */ + NS_IMETHOD CheckSameOrigin(JSContext* aJSContext, nsIURI* aTargetURI) = 0; + + /** + * Returns OK if aSourceURI and target have the same "origin" + * (scheme, host, and port). + * ReportError flag suppresses error reports for functions that + * don't need reporting. + */ + /* void checkSameOriginURI (in nsIURI aSourceURI, in nsIURI aTargetURI, + in boolean reportError); */ + NS_SCRIPTABLE NS_IMETHOD CheckSameOriginURI(nsIURI* aSourceURI, + nsIURI* aTargetURI, + PRBool reportError) = 0; + + /** + * Returns the principal of the global object of the given context, or null + * if no global or no principal. + */ + /* [noscript] nsIPrincipal getPrincipalFromContext (in JSContextPtr cx); */ + NS_IMETHOD GetPrincipalFromContext(JSContext* cx, + nsIPrincipal** _retval NS_OUTPARAM) = 0; + + /** + * Get the principal for the given channel. This will typically be the + * channel owner if there is one, and the codebase principal for the + * channel's URI otherwise. aChannel must not be null. + */ + /* nsIPrincipal getChannelPrincipal (in nsIChannel aChannel); */ + NS_SCRIPTABLE NS_IMETHOD GetChannelPrincipal(nsIChannel* aChannel, + nsIPrincipal** _retval NS_OUTPARAM) = 0; + + /** + * Check whether a given principal is a system principal. This allows us + * to avoid handing back the system principal to script while allowing + * script to check whether a given principal is system. + */ + /* boolean isSystemPrincipal (in nsIPrincipal aPrincipal); */ + NS_SCRIPTABLE NS_IMETHOD IsSystemPrincipal(nsIPrincipal* aPrincipal, + PRBool* _retval NS_OUTPARAM) = 0; + + /** + * Same as getSubjectPrincipal(), only faster. cx must *never* be + * passed null, and it must be the context on the top of the + * context stack. Does *not* reference count the returned + * principal. + */ + /* [noscript, notxpcom] nsIPrincipal getCxSubjectPrincipal ( + in JSContextPtr cx); */ + NS_IMETHOD_(nsIPrincipal*) GetCxSubjectPrincipal(JSContext* cx) = 0; + + /* [noscript, notxpcom] nsIPrincipal getCxSubjectPrincipalAndFrame (in JSContextPtr cx, out JSStackFramePtr fp); */ + NS_IMETHOD_(nsIPrincipal*) GetCxSubjectPrincipalAndFrame(JSContext* cx, + JSStackFrame** fp NS_OUTPARAM) = 0; + +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsIScriptSecurityManager_FF35, + NS_ISCRIPTSECURITYMANAGER_IID_FF35); + +ASSOCIATE_IID(NS_ISCRIPTSECURITYMANAGER_IID_STR_FF35, + nsIScriptSecurityManager_FF35); + +#endif // CHROME_FRAME_SCRIPT_SECURITY_MANAGER_H_ diff --git a/chrome_frame/support.gyp b/chrome_frame/support.gyp new file mode 100644 index 0000000..30f5889 --- /dev/null +++ b/chrome_frame/support.gyp @@ -0,0 +1,17 @@ +# Copyright (c) 2009 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. + +{ + 'targets': [ + { + 'target_name': 'support', + 'type': 'none', + 'dependencies': [ + 'chrome_frame.gyp:*', + ], + }, + ], +} + +# vim: shiftwidth=2:et:ai:tabstop=2 diff --git a/chrome_frame/sync_msg_reply_dispatcher.cc b/chrome_frame/sync_msg_reply_dispatcher.cc new file mode 100644 index 0000000..b301698 --- /dev/null +++ b/chrome_frame/sync_msg_reply_dispatcher.cc @@ -0,0 +1,61 @@ +// Copyright (c) 2009 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. + +#include "chrome_frame/sync_msg_reply_dispatcher.h" + +#include "ipc/ipc_sync_message.h" + +void SyncMessageReplyDispatcher::Push(IPC::SyncMessage* msg, void* callback, + void* key) { + MessageSent pending(IPC::SyncMessage::GetMessageId(*msg), + msg->type(), callback, key); + AutoLock lock(message_queue_lock_); + message_queue_.push_back(pending); +} + +bool SyncMessageReplyDispatcher::HandleMessageType(const IPC::Message& msg, + const MessageSent& origin) { + return false; +} + +bool SyncMessageReplyDispatcher::OnMessageReceived(const IPC::Message& msg) { + MessageSent origin; + if (!Pop(msg, &origin)) { + return false; + } + + // No callback e.g. no return values and/or don't care + if (origin.callback == NULL) + return true; + + return HandleMessageType(msg, origin); +} + +void SyncMessageReplyDispatcher::Cancel(void* key) { + DCHECK(key != NULL); + AutoLock lock(message_queue_lock_); + PendingSyncMessageQueue::iterator it; + for (it = message_queue_.begin(); it != message_queue_.end(); ++it) { + if (it->key == key) { + it->key = NULL; + } + } +} + +bool SyncMessageReplyDispatcher::Pop(const IPC::Message& msg, MessageSent* t) { + if (!msg.is_reply()) + return false; + + int id = IPC::SyncMessage::GetMessageId(msg); + AutoLock lock(message_queue_lock_); + PendingSyncMessageQueue::iterator it; + for (it = message_queue_.begin(); it != message_queue_.end(); ++it) { + if (it->id == id) { + *t = *it; + message_queue_.erase(it); + return true; + } + } + return false; +} diff --git a/chrome_frame/sync_msg_reply_dispatcher.h b/chrome_frame/sync_msg_reply_dispatcher.h new file mode 100644 index 0000000..05e5162 --- /dev/null +++ b/chrome_frame/sync_msg_reply_dispatcher.h @@ -0,0 +1,96 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_FRAME_SYNC_MSG_REPLY_DISPATCHER_H_ +#define CHROME_FRAME_SYNC_MSG_REPLY_DISPATCHER_H_ + +#include <deque> + +#include "base/lock.h" +#include "ipc/ipc_channel_proxy.h" + +// Base class used to allow synchronous IPC messages to be sent and +// received in an asynchronous manner. To use this class add it as a filter to +// your IPC channel using ChannelProxy::AddFilter(). From then on, before +// sending a synchronous message, call SyncMessageReplyDispatcher::Push() with +// a callback and a key. This class will then handle the message response and +// will call the callback when it is received. +// +// This class is intended to be extended by classes implementing +// HandleMessageType with delegation for the messages they expect to receive in +// cases where you care about the return values of synchronous messages. +// +// Sample usage pattern: +// +// // Add handling for desired message types. +// class SyncMessageReplyDispatcherImpl : public SyncMessageReplyDispatcher { +// virtual bool HandleMessageType(const IPC::Message& msg, +// const MessageSent& origin) { +// switch (origin.type) { +// case AutomationMsg_CreateExternalTab::ID: +// InvokeCallback<Tuple3<HWND, HWND, int> >(msg, origin); +// break; +// [HANDLING FOR OTHER EXPECTED MESSAGE TYPES] +// } +// } +// +// // Add the filter +// IPC::SyncChannel channel_; +// channel_.AddFilter(new SyncMessageReplyDispatcherImpl()); +// +// sync_->Push(msg, NewCallback(this, CallbackMethod, this); +// channel_->ChannelProxy::Send(msg); +// +class SyncMessageReplyDispatcher : public IPC::ChannelProxy::MessageFilter { + public: + SyncMessageReplyDispatcher() {} + void Push(IPC::SyncMessage* msg, void* callback, void* key); + void Cancel(void* key); + + protected: + struct MessageSent { + MessageSent() {} + MessageSent(int id, uint16 type, void* callback, void* key) + : id(id), callback(callback), type(type), key(key) {} + int id; + void* callback; + void* key; + uint16 type; + }; + + typedef std::deque<MessageSent> PendingSyncMessageQueue; + + bool Pop(const IPC::Message& msg, MessageSent* t); + virtual bool OnMessageReceived(const IPC::Message& msg); + + // Child classes must implement a handler for the message types they are + // interested in handling responses for. If you don't care about the replies + // to any of the sync messages you are handling, then you don't have to + // implement this. + virtual bool HandleMessageType(const IPC::Message& msg, + const MessageSent& origin); + + template <typename T> + void InvokeCallback(const IPC::Message& msg, const MessageSent& origin) { + // Ensure we delete the callback + scoped_ptr<CallbackRunner<T> > c( + reinterpret_cast<CallbackRunner<T>*>(origin.callback)); + if (origin.key == NULL) { // Canceled + return; + } + + T tmp; // Acts as "initializer" for output parameters. + IPC::ParamDeserializer<T> deserializer(tmp); + if (deserializer.MessageReplyDeserializer::SerializeOutputParameters(msg)) { + c->RunWithParams(deserializer.out_); + } else { + // TODO(stoyan): How to handle errors? + } + } + + PendingSyncMessageQueue message_queue_; + Lock message_queue_lock_; +}; + +#endif // CHROME_FRAME_SYNC_MSG_REPLY_DISPATCHER_H_ diff --git a/chrome_frame/test/ChromeTab_UnitTests.vcproj b/chrome_frame/test/ChromeTab_UnitTests.vcproj new file mode 100644 index 0000000..9f4369b --- /dev/null +++ b/chrome_frame/test/ChromeTab_UnitTests.vcproj @@ -0,0 +1,273 @@ +<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="8.00"
+ Name="chrometab_unittests"
+ ProjectGUID="{BA08FE92-567D-4411-B344-17ADAECA2B5A}"
+ RootNamespace="ChromeTab_UnitTests"
+ Keyword="Win32Proj"
+ >
+ <Platforms>
+ <Platform
+ Name="Win32"
+ />
+ </Platforms>
+ <ToolFiles>
+ </ToolFiles>
+ <Configurations>
+ <Configuration
+ Name="Debug|Win32"
+ OutputDirectory="..\..\chrome\$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="1"
+ InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\build\debug.vsprops;$(SolutionDir)..\skia\using_skia.vsprops;$(SolutionDir)..\third_party\libxml\build\using_libxml.vsprops;$(SolutionDir)..\third_party\libxslt\build\using_libxslt.vsprops;..\..\testing\using_gtest.vsprops;$(SolutionDir)..\chrome\third_party\wtl\using_wtl.vsprops;.\chrometab_unittests.vsprops"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="0"
+ PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE"
+ MinimalRebuild="true"
+ BasicRuntimeChecks="3"
+ RuntimeLibrary="1"
+ UsePrecompiledHeader="0"
+ WarningLevel="3"
+ Detect64BitPortabilityProblems="false"
+ DebugInformationFormat="4"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ LinkIncremental="2"
+ GenerateDebugInformation="true"
+ SubSystem="1"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ <Configuration
+ Name="Release|Win32"
+ OutputDirectory="..\..\chrome\$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="1"
+ InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\skia\using_skia.vsprops;$(SolutionDir)..\third_party\libxml\build\using_libxml.vsprops;$(SolutionDir)..\third_party\libxslt\build\using_libxslt.vsprops;..\..\testing\using_gtest.vsprops;$(SolutionDir)..\chrome\third_party\wtl\using_wtl.vsprops;$(SolutionDir)..\build\release.vsprops;.\chrometab_unittests.vsprops"
+ UseOfATL="1"
+ ATLMinimizesCRunTimeLibraryUsage="false"
+ WholeProgramOptimization="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="2"
+ OmitFramePointers="false"
+ WholeProgramOptimization="false"
+ PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE;"
+ StringPooling="true"
+ BasicRuntimeChecks="0"
+ RuntimeLibrary="0"
+ UsePrecompiledHeader="0"
+ WarningLevel="3"
+ Detect64BitPortabilityProblems="false"
+ DebugInformationFormat="3"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ LinkIncremental="1"
+ AdditionalLibraryDirectories="$(ConfigurationName)\lib;"
+ IgnoreDefaultLibraryNames=""
+ GenerateDebugInformation="true"
+ SubSystem="1"
+ OptimizeReferences="2"
+ EnableCOMDATFolding="2"
+ OptimizeForWindows98="1"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <Filter
+ Name="Source Files"
+ Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx"
+ UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
+ >
+ <Filter
+ Name="Common"
+ >
+ <File
+ RelativePath=".\cf_test_utils.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\cf_test_utils.h"
+ >
+ </File>
+ <File
+ RelativePath=".\chrometab_unittests.h"
+ >
+ </File>
+ <File
+ RelativePath=".\run_all_unittests.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\..\base\test_suite.h"
+ >
+ </File>
+ <File
+ RelativePath="..\test_utils.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\test_utils.h"
+ >
+ </File>
+ </Filter>
+ <Filter
+ Name="Tests"
+ >
+ <File
+ RelativePath="..\chrome_frame_automation.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\chrometab_unittests.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\com_message_event.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\com_message_event_unittest.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\function_stub_unittest.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\html_util_unittests.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\html_utils.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\icu_stubs_unittests.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\util_unittests.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\utils.cc"
+ >
+ </File>
+ </Filter>
+ </Filter>
+ <Filter
+ Name="Header Files"
+ Filter="h;hpp;hxx;hm;inl;inc;xsd"
+ UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
+ >
+ </Filter>
+ <Filter
+ Name="Resource Files"
+ Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav"
+ UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}"
+ >
+ </Filter>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
diff --git a/chrome_frame/test/chrome_frame_automation_mock.cc b/chrome_frame/test/chrome_frame_automation_mock.cc new file mode 100644 index 0000000..d900176 --- /dev/null +++ b/chrome_frame/test/chrome_frame_automation_mock.cc @@ -0,0 +1,47 @@ +// Copyright (c) 2006-2009 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. + +#include "chrome_frame/test/chrome_frame_automation_mock.h" +#include "testing/gtest/include/gtest/gtest.h" + +const int kLongWaitTimeout = 25 * 1000; +const int kShortWaitTimeout = 5 * 1000; + +TEST(ChromeFrame, Launch) { + MessageLoopForUI loop; + AutomationMockLaunch mock_launch(&loop, kLongWaitTimeout); + + mock_launch.Navigate("about:blank"); + loop.Run(NULL); + EXPECT_EQ(true, mock_launch.launch_result()); +} + +TEST(ChromeFrame, Navigate) { + MessageLoopForUI loop; + AutomationMockNavigate mock_navigate(&loop, kLongWaitTimeout); + + mock_navigate.NavigateRelativeFile(L"postmessage_basic_frame.html"); + loop.Run(NULL); + EXPECT_EQ(true, mock_navigate.navigation_result()); +} + +TEST(ChromeFrame, PostMessage) { + MessageLoopForUI loop; + AutomationMockPostMessage mock_postmessage(&loop, kLongWaitTimeout); + + mock_postmessage.NavigateRelativeFile(L"postmessage_basic_frame.html"); + loop.Run(NULL); + EXPECT_EQ(true, mock_postmessage.postmessage_result()); +} + +TEST(ChromeFrame, RequestStart) { + MessageLoopForUI loop; + AutomationMockHostNetworkRequestStart mock_request_start(&loop, + kLongWaitTimeout); + + mock_request_start.NavigateRelative(L"postmessage_basic_frame.html"); + loop.Run(NULL); + EXPECT_EQ(true, mock_request_start.request_start_result()); +} + diff --git a/chrome_frame/test/chrome_frame_automation_mock.h b/chrome_frame/test/chrome_frame_automation_mock.h new file mode 100644 index 0000000..4c3fe7b --- /dev/null +++ b/chrome_frame/test/chrome_frame_automation_mock.h @@ -0,0 +1,208 @@ +// Copyright (c) 2006-2009 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. +#ifndef CHROME_FRAME_TEST_CHROME_FRAME_AUTOMATION_MOCK_H_ +#define CHROME_FRAME_TEST_CHROME_FRAME_AUTOMATION_MOCK_H_ + +#include <string> + +#include "base/file_path.h" +#include "base/path_service.h" +#include "chrome_frame/chrome_frame_automation.h" +#include "chrome_frame/chrome_frame_plugin.h" +#include "chrome_frame/test/http_server.h" + +template <typename T> +class AutomationMockDelegate + : public CWindowImpl<T>, + public ChromeFramePlugin<T> { + public: + AutomationMockDelegate(MessageLoop* caller_message_loop, + int launch_timeout, bool perform_version_check, + const std::wstring& profile_name, + const std::wstring& extra_chrome_arguments, bool incognito) + : caller_message_loop_(caller_message_loop), is_connected_(false) { + test_server_.SetUp(); + automation_client_.reset(new ChromeFrameAutomationClient); + automation_client_->Initialize(this, launch_timeout, perform_version_check, + profile_name, extra_chrome_arguments, incognito); + } + ~AutomationMockDelegate() { + if (automation_client_.get()) { + automation_client_->Uninitialize(); + automation_client_.reset(); + } + if (IsWindow()) + DestroyWindow(); + + test_server_.TearDown(); + } + + // Navigate external tab to the specified url through automation + bool Navigate(const std::string& url) { + url_ = GURL(url); + return automation_client_->InitiateNavigation(url); + } + + // Navigate the external to a 'file://' url for unit test files + bool NavigateRelativeFile(const std::wstring& file) { + FilePath cf_source_path; + PathService::Get(base::DIR_SOURCE_ROOT, &cf_source_path); + std::wstring file_url(L"file://"); + file_url.append(cf_source_path.Append( + FILE_PATH_LITERAL("chrome_frame")).Append( + FILE_PATH_LITERAL("test")).Append( + FILE_PATH_LITERAL("data")).Append(file).value()); + return Navigate(WideToUTF8(file_url)); + } + + bool NavigateRelative(const std::wstring& relative_url) { + return Navigate(test_server_.Resolve(relative_url.c_str()).spec()); + } + + virtual void OnAutomationServerReady() { + if (automation_client_.get()) { + Create(NULL, 0, NULL, WS_OVERLAPPEDWINDOW); + DCHECK(IsWindow()); + is_connected_ = true; + } else { + NOTREACHED(); + } + } + + virtual void OnAutomationServerLaunchFailed() { + QuitMessageLoop(); + } + + virtual void OnLoad(int tab_handle, const GURL& url) { + if (url_ == url) { + navigation_result_ = true; + } else { + QuitMessageLoop(); + } + } + + virtual void OnLoadFailed(int error_code, const std::string& url) { + QuitMessageLoop(); + } + + ChromeFrameAutomationClient* automation() { + return automation_client_.get(); + } + const GURL& url() const { + return url_; + } + bool is_connected() const { + return is_connected_; + } + bool navigation_result() const { + return navigation_result_; + } + + BEGIN_MSG_MAP(AutomationMockDelegate) + END_MSG_MAP() + + protected: + void QuitMessageLoop() { + // Quit on the caller message loop has to be called on the caller + // thread. + caller_message_loop_->PostTask(FROM_HERE, new MessageLoop::QuitTask); + } + + private: + ChromeFrameHTTPServer test_server_; + MessageLoop* caller_message_loop_; + GURL url_; + bool is_connected_; + bool navigation_result_; +}; + +class AutomationMockLaunch + : public AutomationMockDelegate<AutomationMockLaunch> { + public: + typedef AutomationMockDelegate<AutomationMockLaunch> Base; + AutomationMockLaunch(MessageLoop* caller_message_loop, + int launch_timeout) + : Base(caller_message_loop, launch_timeout, true, L"", L"", false) { + } + virtual void OnAutomationServerReady() { + Base::OnAutomationServerReady(); + QuitMessageLoop(); + } + bool launch_result() const { + return is_connected(); + } +}; + +class AutomationMockNavigate + : public AutomationMockDelegate<AutomationMockNavigate> { + public: + typedef AutomationMockDelegate<AutomationMockNavigate> Base; + AutomationMockNavigate(MessageLoop* caller_message_loop, + int launch_timeout) + : Base(caller_message_loop, launch_timeout, true, L"", L"", false) { + } + virtual void OnLoad(int tab_handle, const GURL& url) { + Base::OnLoad(tab_handle, url); + QuitMessageLoop(); + } +}; + +class AutomationMockPostMessage + : public AutomationMockDelegate<AutomationMockPostMessage> { + public: + typedef AutomationMockDelegate<AutomationMockPostMessage> Base; + AutomationMockPostMessage(MessageLoop* caller_message_loop, + int launch_timeout) + : Base(caller_message_loop, launch_timeout, true, L"", L"", false), + postmessage_result_(false) {} + bool postmessage_result() const { + return postmessage_result_; + } + virtual void OnLoad(int tab_handle, const GURL& url) { + Base::OnLoad(tab_handle, url); + if (navigation_result()) { + automation()->ForwardMessageFromExternalHost("Test", "null", "*"); + } + } + virtual void OnMessageFromChromeFrame(int tab_handle, + const std::string& message, + const std::string& origin, + const std::string& target) { + postmessage_result_ = true; + QuitMessageLoop(); + } + private: + bool postmessage_result_; +}; + +class AutomationMockHostNetworkRequestStart + : public AutomationMockDelegate<AutomationMockHostNetworkRequestStart> { + public: + typedef AutomationMockDelegate<AutomationMockHostNetworkRequestStart> Base; + AutomationMockHostNetworkRequestStart(MessageLoop* caller_message_loop, + int launch_timeout) + : Base(caller_message_loop, launch_timeout, true, L"", L"", false), + request_start_result_(false) { + if (automation()) { + automation()->set_use_chrome_network(false); + } + } + bool request_start_result() const { + return request_start_result_; + } + virtual void OnRequestStart(int tab_handle, int request_id, + const IPC::AutomationURLRequest& request) { + request_start_result_ = true; + QuitMessageLoop(); + } + virtual void OnLoad(int tab_handle, const GURL& url) { + Base::OnLoad(tab_handle, url); + } + private: + bool request_start_result_; +}; + + +#endif // CHROME_FRAME_TEST_CHROME_FRAME_AUTOMATION_MOCK_H_ + diff --git a/chrome_frame/test/chrome_frame_test_utils.cc b/chrome_frame/test/chrome_frame_test_utils.cc new file mode 100644 index 0000000..a75d791 --- /dev/null +++ b/chrome_frame/test/chrome_frame_test_utils.cc @@ -0,0 +1,416 @@ +// Copyright (c) 2006-2008 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. + +#include "chrome_frame/test/chrome_frame_test_utils.h" + +#include <atlbase.h> +#include <atlwin.h> +#include <iepmapi.h> + +#include "base/registry.h" // to find IE and firefox +#include "base/scoped_handle.h" +#include "base/scoped_comptr_win.h" +#include "base/string_util.h" +#include "base/win_util.h" +#include "chrome/common/chrome_switches.h" + +namespace chrome_frame_test { + +const wchar_t kIEImageName[] = L"iexplore.exe"; +const wchar_t kIEBrokerImageName[] = L"ieuser.exe"; +const wchar_t kFirefoxImageName[] = L"firefox.exe"; +const wchar_t kOperaImageName[] = L"opera.exe"; +const wchar_t kSafariImageName[] = L"safari.exe"; +const wchar_t kChromeImageName[] = L"chrome.exe"; + +bool IsTopLevelWindow(HWND window) { + long style = GetWindowLong(window, GWL_STYLE); // NOLINT + if (!(style & WS_CHILD)) + return true; + + HWND parent = GetParent(window); + if (!parent) + return true; + + if (parent == GetDesktopWindow()) + return true; + + return false; +} + +// Callback function for EnumThreadWindows. +BOOL CALLBACK CloseWindowsThreadCallback(HWND hwnd, LPARAM param) { + int& count = *reinterpret_cast<int*>(param); + if (IsWindowVisible(hwnd)) { + if (IsWindowEnabled(hwnd)) { + DWORD results = 0; + if (!::SendMessageTimeout(hwnd, WM_SYSCOMMAND, SC_CLOSE, 0, SMTO_BLOCK, + 10000, &results)) { + DLOG(WARNING) << "Window hung: " << StringPrintf(L"%08X", hwnd); + } + count++; + } else { + DLOG(WARNING) << "Skipping disabled window: " + << StringPrintf(L"%08X", hwnd); + } + } + return TRUE; // continue enumeration +} + +// Attempts to close all non-child, visible windows on the given thread. +// The return value is the number of visible windows a close request was +// sent to. +int CloseVisibleTopLevelWindowsOnThread(DWORD thread_id) { + int window_close_attempts = 0; + EnumThreadWindows(thread_id, CloseWindowsThreadCallback, + reinterpret_cast<LPARAM>(&window_close_attempts)); + return window_close_attempts; +} + +// Enumerates the threads of a process and attempts to close visible non-child +// windows on all threads of the process. +// The return value is the number of visible windows a close request was +// sent to. +int CloseVisibleWindowsOnAllThreads(HANDLE process) { + DWORD process_id = ::GetProcessId(process); + if (process_id == 0) { + NOTREACHED(); + return 0; + } + + ScopedHandle snapshot(CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0)); + if (!snapshot.IsValid()) { + NOTREACHED(); + return 0; + } + + int window_close_attempts = 0; + THREADENTRY32 te = { sizeof(THREADENTRY32) }; + if (Thread32First(snapshot, &te)) { + do { + if (RTL_CONTAINS_FIELD(&te, te.dwSize, th32OwnerProcessID) && + te.th32OwnerProcessID == process_id) { + window_close_attempts += + CloseVisibleTopLevelWindowsOnThread(te.th32ThreadID); + } + te.dwSize = sizeof(te); + } while (Thread32Next(snapshot, &te)); + } + + return window_close_attempts; +} + +class ForegroundHelperWindow : public CWindowImpl<ForegroundHelperWindow> { + public: +BEGIN_MSG_MAP(ForegroundHelperWindow) + MESSAGE_HANDLER(WM_HOTKEY, OnHotKey) +END_MSG_MAP() + + HRESULT SetForeground(HWND window) { + DCHECK(::IsWindow(window)); + if (NULL == Create(NULL, NULL, NULL, WS_POPUP)) + return AtlHresultFromLastError(); + + static const int hotkey_id = 0x0000baba; + + SetWindowLongPtr(GWLP_USERDATA, reinterpret_cast<ULONG_PTR>(window)); + RegisterHotKey(m_hWnd, hotkey_id, 0, VK_F22); + + MSG msg = {0}; + PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE); + + INPUT hotkey = {0}; + hotkey.type = INPUT_KEYBOARD; + hotkey.ki.wVk = VK_F22; + SendInput(1, &hotkey, sizeof(hotkey)); + + while (GetMessage(&msg, NULL, 0, 0)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + if (WM_HOTKEY == msg.message) + break; + } + + UnregisterHotKey(m_hWnd, hotkey_id); + DestroyWindow(); + + return S_OK; + } + + LRESULT OnHotKey(UINT msg, WPARAM wp, LPARAM lp, BOOL& handled) { // NOLINT + HWND window = reinterpret_cast<HWND>(GetWindowLongPtr(GWLP_USERDATA)); + SetForegroundWindow(window); + return 1; + } +}; + +bool ForceSetForegroundWindow(HWND window) { + if (GetForegroundWindow() == window) + return true; + ForegroundHelperWindow foreground_helper_window; + HRESULT hr = foreground_helper_window.SetForeground(window); + return SUCCEEDED(hr); +} + +struct PidAndWindow { + base::ProcessId pid; + HWND hwnd; +}; + +BOOL CALLBACK FindWindowInProcessCallback(HWND hwnd, LPARAM param) { + PidAndWindow* paw = reinterpret_cast<PidAndWindow*>(param); + base::ProcessId pid; + GetWindowThreadProcessId(hwnd, &pid); + if (pid == paw->pid && IsWindowVisible(hwnd)) { + paw->hwnd = hwnd; + return FALSE; + } + + return TRUE; +} + +bool EnsureProcessInForeground(base::ProcessId process_id) { + HWND hwnd = GetForegroundWindow(); + base::ProcessId current_foreground_pid = 0; + DWORD active_thread_id = GetWindowThreadProcessId(hwnd, + ¤t_foreground_pid); + if (current_foreground_pid == process_id) + return true; + + PidAndWindow paw = { process_id }; + EnumWindows(FindWindowInProcessCallback, reinterpret_cast<LPARAM>(&paw)); + if (!IsWindow(paw.hwnd)) { + DLOG(ERROR) << "failed to find process window"; + return false; + } + + bool ret = ForceSetForegroundWindow(paw.hwnd); + DLOG_IF(ERROR, !ret) << "ForceSetForegroundWindow: " << ret; + + return ret; +} + +// Iterates through all the characters in the string and simulates +// keyboard input. The input goes to the currently active application. +bool SendString(const wchar_t* string) { + DCHECK(string != NULL); + + INPUT input[2] = {0}; + input[0].type = INPUT_KEYBOARD; + input[0].ki.dwFlags = KEYEVENTF_UNICODE; // to avoid shift, etc. + input[1] = input[0]; + input[1].ki.dwFlags |= KEYEVENTF_KEYUP; + + for (const wchar_t* p = string; *p; p++) { + input[0].ki.wScan = input[1].ki.wScan = *p; + SendInput(2, input, sizeof(INPUT)); + } + + return true; +} + +void SendVirtualKey(int16 key) { + INPUT input = { INPUT_KEYBOARD }; + input.ki.wVk = key; + SendInput(1, &input, sizeof(input)); + input.ki.dwFlags = KEYEVENTF_KEYUP; + SendInput(1, &input, sizeof(input)); +} + +void SendChar(char c) { + SendVirtualKey(VkKeyScanA(c)); +} + +void SendString(const char* s) { + while (*s) { + SendChar(*s); + s++; + } +} + +// Sends a keystroke to the currently active application with optional +// modifiers set. +bool SendMnemonic(WORD mnemonic_char, bool shift_pressed, bool control_pressed, + bool alt_pressed) { + INPUT special_keys[3] = {0}; + for (int index = 0; index < arraysize(special_keys); ++index) { + special_keys[index].type = INPUT_KEYBOARD; + special_keys[index].ki.dwFlags = 0; + } + + int num_special_keys = 0; + if (shift_pressed) { + special_keys[num_special_keys].ki.wVk = VK_SHIFT; + num_special_keys++; + } + + if (control_pressed) { + special_keys[num_special_keys].ki.wVk = VK_CONTROL; + num_special_keys++; + } + + if (alt_pressed) { + special_keys[num_special_keys].ki.wVk = VK_MENU; + num_special_keys++; + } + + // Depress the modifiers. + SendInput(num_special_keys, special_keys, sizeof(INPUT)); + + Sleep(100); + + INPUT mnemonic = {0}; + mnemonic.type = INPUT_KEYBOARD; + mnemonic.ki.wVk = mnemonic_char; + + // Depress and release the mnemonic. + SendInput(1, &mnemonic, sizeof(INPUT)); + Sleep(100); + + mnemonic.ki.dwFlags |= KEYEVENTF_KEYUP; + SendInput(1, &mnemonic, sizeof(INPUT)); + Sleep(100); + + // Now release the modifiers. + for (int index = 0; index < num_special_keys; index++) { + special_keys[index].ki.dwFlags |= KEYEVENTF_KEYUP; + } + + SendInput(num_special_keys, special_keys, sizeof(INPUT)); + Sleep(100); + + return true; +} + +std::wstring GetExecutableAppPath(const std::wstring& file) { + std::wstring kAppPathsKey = + L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\"; + + std::wstring app_path; + RegKey key(HKEY_LOCAL_MACHINE, (kAppPathsKey + file).c_str()); + if (key.Handle()) { + key.ReadValue(NULL, &app_path); + } + + return app_path; +} + +std::wstring FormatCommandForApp(const std::wstring& exe_name, + const std::wstring& argument) { + std::wstring reg_path(StringPrintf(L"Applications\\%ls\\shell\\open\\command", + exe_name.c_str())); + RegKey key(HKEY_CLASSES_ROOT, reg_path.c_str()); + + std::wstring command; + if (key.Handle()) { + key.ReadValue(NULL, &command); + int found = command.find(L"%1"); + if (found >= 0) { + command.replace(found, 2, argument); + } + } + return command; +} + +base::ProcessHandle LaunchExecutable(const std::wstring& executable, + const std::wstring& argument) { + base::ProcessHandle process = NULL; + std::wstring path = GetExecutableAppPath(executable); + if (path.empty()) { + path = FormatCommandForApp(executable, argument); + if (path.empty()) { + DLOG(ERROR) << "Failed to find executable: " << executable; + } else { + CommandLine cmdline(L""); + cmdline.ParseFromString(path); + base::LaunchApp(cmdline, false, false, &process); + } + } else { + CommandLine cmdline(path); + cmdline.AppendLooseValue(argument); + base::LaunchApp(cmdline, false, false, &process); + } + return process; +} + +base::ProcessHandle LaunchFirefox(const std::wstring& url) { + return LaunchExecutable(kFirefoxImageName, url); +} + +base::ProcessHandle LaunchSafari(const std::wstring& url) { + return LaunchExecutable(kSafariImageName, url); +} + +base::ProcessHandle LaunchChrome(const std::wstring& url) { + return LaunchExecutable(kChromeImageName, + StringPrintf(L"--%ls ", switches::kNoFirstRun) + url); +} + +base::ProcessHandle LaunchOpera(const std::wstring& url) { + // NOTE: For Opera tests to work it must be configured to start up with + // a blank page. There is an command line switch, -nosession, that's supposed + // to avoid opening up the previous session, but that switch is not working. + // TODO(tommi): Include a special ini file (opera6.ini) for opera and launch + // with our required settings. This file is by default stored here: + // "%USERPROFILE%\Application Data\Opera\Opera\profile\opera6.ini" + return LaunchExecutable(kOperaImageName, url); +} + +base::ProcessHandle LaunchIEOnVista(const std::wstring& url) { + typedef HRESULT (WINAPI* IELaunchURLPtr)( + const wchar_t* url, + PROCESS_INFORMATION *pi, + VOID *info); + + IELaunchURLPtr launch; + PROCESS_INFORMATION pi = {0}; + IELAUNCHURLINFO info = {sizeof info, 0}; + HMODULE h = LoadLibrary(L"ieframe.dll"); + if (!h) + return NULL; + launch = reinterpret_cast<IELaunchURLPtr>(GetProcAddress(h, "IELaunchURL")); + HRESULT hr = launch(url.c_str(), &pi, &info); + FreeLibrary(h); + if (SUCCEEDED(hr)) + CloseHandle(pi.hThread); + return pi.hProcess; +} + +base::ProcessHandle LaunchIE(const std::wstring& url) { + if (win_util::GetWinVersion() >= win_util::WINVERSION_VISTA) { + return LaunchIEOnVista(url); + } else { + return LaunchExecutable(kIEImageName, url); + } +} + +int CloseAllIEWindows() { + int ret = 0; + + ScopedComPtr<IShellWindows> windows; + HRESULT hr = ::CoCreateInstance(__uuidof(ShellWindows), NULL, CLSCTX_ALL, + IID_IShellWindows, reinterpret_cast<void**>(windows.Receive())); + DCHECK(SUCCEEDED(hr)); + + if (SUCCEEDED(hr)) { + long count = 0; // NOLINT + windows->get_Count(&count); + VARIANT i = { VT_I4 }; + for (i.lVal = 0; i.lVal < count; ++i.lVal) { + ScopedComPtr<IDispatch> folder; + windows->Item(i, folder.Receive()); + if (folder != NULL) { + ScopedComPtr<IWebBrowser2> browser; + if (SUCCEEDED(browser.QueryFrom(folder))) { + browser->Quit(); + ++ret; + } + } + } + } + + return ret; +} + +} // namespace chrome_frame_test diff --git a/chrome_frame/test/chrome_frame_test_utils.h b/chrome_frame/test/chrome_frame_test_utils.h new file mode 100644 index 0000000..95e0c9b --- /dev/null +++ b/chrome_frame/test/chrome_frame_test_utils.h @@ -0,0 +1,61 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_FRAME_TEST_CHROME_FRAME_TEST_UTILS_H_ +#define CHROME_FRAME_TEST_CHROME_FRAME_TEST_UTILS_H_ + +#include <windows.h> + +#include "base/basictypes.h" +#include "base/process_util.h" + +namespace chrome_frame_test { + +bool IsTopLevelWindow(HWND window); +int CloseVisibleWindowsOnAllThreads(HANDLE process); +bool ForceSetForegroundWindow(HWND window); +bool EnsureProcessInForeground(base::ProcessId process_id); + +// Iterates through all the characters in the string and simulates +// keyboard input. The input goes to the currently active application. +bool SendString(const wchar_t* s); + +// Sends a virtual key such as VK_TAB, VK_RETURN or a character that has been +// translated to a virtual key. +void SendVirtualKey(int16 key); + +// Translates a single char to a virtual key and calls SendVirtualKey. +void SendChar(char c); + +// Sends an ascii string, char by char (calls SendChar for each). +void SendString(const char* s); + +// Sends a keystroke to the currently active application with optional +// modifiers set. +bool SendMnemonic(WORD mnemonic_char, bool shift_pressed, bool control_pressed, + bool alt_pressed); + +base::ProcessHandle LaunchFirefox(const std::wstring& url); +base::ProcessHandle LaunchOpera(const std::wstring& url); +base::ProcessHandle LaunchIE(const std::wstring& url); +base::ProcessHandle LaunchSafari(const std::wstring& url); +base::ProcessHandle LaunchChrome(const std::wstring& url); + +// Attempts to close all open IE windows. +// The return value is the number of windows closed. +// @note: this function requires COM to be initialized on the calling thread. +// Since the caller might be running in either MTA or STA, the function does +// not perform this initialization itself. +int CloseAllIEWindows(); + +extern const wchar_t kIEImageName[]; +extern const wchar_t kIEBrokerImageName[]; +extern const wchar_t kFirefoxImageName[]; +extern const wchar_t kOperaImageName[]; +extern const wchar_t kSafariImageName[]; +extern const wchar_t kChromeImageName[]; + +} // namespace chrome_frame_test + +#endif // CHROME_FRAME_CHROMETAB_UNITTESTS_CF_TEST_UTILS_H_ diff --git a/chrome_frame/test/chrome_frame_unittests.cc b/chrome_frame/test/chrome_frame_unittests.cc new file mode 100644 index 0000000..20826b1 --- /dev/null +++ b/chrome_frame/test/chrome_frame_unittests.cc @@ -0,0 +1,1510 @@ +// Copyright (c) 2006-2008 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. +#include <windows.h> +#include <stdarg.h> + +// IShellWindows includes. Unfortunately we can't keep these in +// alphabetic order since exdisp will bark if some interfaces aren't fully +// defined. +#include <mshtml.h> +#include <exdisp.h> + +#include "base/basictypes.h" +#include "base/file_version_info.h" +#include "base/file_util.h" +#include "base/scoped_bstr_win.h" +#include "base/scoped_comptr_win.h" +#include "base/scoped_variant_win.h" +#include "base/sys_info.h" +#include "gmock/gmock.h" +#include "net/url_request/url_request_unittest.h" +#include "chrome_frame/test/chrome_frame_unittests.h" +#include "chrome_frame/chrome_frame_automation.h" +#include "chrome_frame/chrome_frame_delegate.h" +#include "chrome_frame/test/chrome_frame_test_utils.h" +#include "chrome_frame/test/helper_gmock.h" +#include "chrome_frame/test_utils.h" +#include "chrome_frame/utils.h" +#include "chrome_frame/vectored_handler-impl.h" +#include "chrome/installer/util/install_util.h" +#include "chrome/installer/util/helper.h" + +const wchar_t kDocRoot[] = L"chrome_frame\\test\\data"; +const int kLongWaitTimeout = 60 * 1000; +const int kShortWaitTimeout = 25 * 1000; + +_ATL_FUNC_INFO WebBrowserEventSink::kNavigateErrorInfo = {
+ CC_STDCALL, VT_EMPTY, 5, { + VT_DISPATCH, + VT_VARIANT | VT_BYREF, + VT_VARIANT | VT_BYREF, + VT_VARIANT | VT_BYREF, + VT_BOOL | VT_BYREF, + } +}; + +_ATL_FUNC_INFO WebBrowserEventSink::kNavigateComplete2Info = { + CC_STDCALL, VT_EMPTY, 2, { + VT_DISPATCH, + VT_VARIANT | VT_BYREF + } +}; + +_ATL_FUNC_INFO WebBrowserEventSink::kBeforeNavigate2Info = {
+ CC_STDCALL, VT_EMPTY, 7, { + VT_DISPATCH, + VT_VARIANT | VT_BYREF, + VT_VARIANT | VT_BYREF, + VT_VARIANT | VT_BYREF, + VT_VARIANT | VT_BYREF, + VT_VARIANT | VT_BYREF, + VT_BOOL | VT_BYREF + } +}; + + + +void ChromeFrameTestWithWebServer::SetUp() { + server_.SetUp(); + results_dir_ = server_.GetDataDir(); + file_util::AppendToPath(&results_dir_, L"dump"); +} + +void ChromeFrameTestWithWebServer::TearDown() { + CloseBrowser(); + + // Web browsers tend to relaunch themselves in other processes, meaning the + // KillProcess stuff above might not have actually cleaned up all our browser + // instances, so make really sure browsers are dead. + base::KillProcesses(chrome_frame_test::kIEImageName, 0, NULL); + base::KillProcesses(chrome_frame_test::kIEBrokerImageName, 0, NULL); + base::KillProcesses(chrome_frame_test::kFirefoxImageName, 0, NULL); + base::KillProcesses(chrome_frame_test::kSafariImageName, 0, NULL); + base::KillProcesses(chrome_frame_test::kChromeImageName, 0, NULL); + + server_.TearDown(); +} + +bool ChromeFrameTestWithWebServer::LaunchBrowser(BrowserKind browser, + const wchar_t* page) { + std::wstring url = UTF8ToWide(server_.Resolve(page).spec()); + browser_ = browser; + if (browser == IE) { + browser_handle_.Set(chrome_frame_test::LaunchIE(url)); + } else if (browser == FIREFOX) { + browser_handle_.Set(chrome_frame_test::LaunchFirefox(url)); + } else if (browser == OPERA) { + browser_handle_.Set(chrome_frame_test::LaunchOpera(url)); + } else if (browser == SAFARI) { + browser_handle_.Set(chrome_frame_test::LaunchSafari(url)); + } else if (browser == CHROME) { + browser_handle_.Set(chrome_frame_test::LaunchChrome(url)); + } else { + NOTREACHED(); + } + + return browser_handle_.IsValid(); +} + +void ChromeFrameTestWithWebServer::CloseBrowser() { + if (!browser_handle_.IsValid()) + return; + + int attempts = 0; + if (browser_ == IE) { + attempts = chrome_frame_test::CloseAllIEWindows(); + } else { + attempts = chrome_frame_test::CloseVisibleWindowsOnAllThreads( + browser_handle_); + } + + if (attempts > 0) { + DWORD wait = ::WaitForSingleObject(browser_handle_, 20000); + if (wait == WAIT_OBJECT_0) { + browser_handle_.Close(); + } else { + DLOG(ERROR) << "WaitForSingleObject returned " << wait; + } + } else { + DLOG(ERROR) << "No attempts to close browser windows"; + } + + if (browser_handle_.IsValid()) { + DWORD exit_code = 0; + if (!::GetExitCodeProcess(browser_handle_, &exit_code) || + exit_code == STILL_ACTIVE) { + DLOG(ERROR) << L"Forcefully killing browser process. Exit:" << exit_code; + base::KillProcess(browser_handle_, 0, true); + } + browser_handle_.Close(); + } +} + +bool ChromeFrameTestWithWebServer::BringBrowserToTop() { + return chrome_frame_test::EnsureProcessInForeground(GetProcessId( + browser_handle_)); +} + +bool ChromeFrameTestWithWebServer::WaitForTestToComplete(int milliseconds) { + return server_.WaitToFinish(milliseconds); +} + +bool ChromeFrameTestWithWebServer::WaitForOnLoad(int milliseconds) { + DWORD start = ::GetTickCount(); + std::string data; + while (!ReadResultFile(L"OnLoadEvent", &data) || data.length() == 0) { + DWORD now = ::GetTickCount(); + if (start > now) { + // Very simple check for overflow. In that case we just restart the + // wait. + start = now; + } else if (static_cast<int>(now - start) > milliseconds) { + break; + } + Sleep(100); + } + + return data.compare("loaded") == 0; +} + +bool ChromeFrameTestWithWebServer::ReadResultFile(const std::wstring& file_name, + std::string* data) { + std::wstring full_path = results_dir_; + file_util::AppendToPath(&full_path, file_name); + return file_util::ReadFileToString(full_path, data); +} + +bool ChromeFrameTestWithWebServer::CheckResultFile( + const std::wstring& file_name, const std::string& expected_result) { + std::string data; + bool ret = ReadResultFile(file_name, &data); + if (ret) + ret = (data == expected_result); + + if (!ret) { + DLOG(ERROR) << "Error text: " << (data.empty() ? "<empty>" : data.c_str()); + } + + return ret; +} + +void ChromeFrameTestWithWebServer::SimpleBrowserTest(BrowserKind browser, + const wchar_t* page, const wchar_t* result_file_to_check) { + EXPECT_TRUE(LaunchBrowser(browser, page)); + ASSERT_TRUE(WaitForTestToComplete(kLongWaitTimeout)); + ASSERT_TRUE(CheckResultFile(result_file_to_check, "OK")); +} + +void ChromeFrameTestWithWebServer::OptionalBrowserTest(BrowserKind browser, + const wchar_t* page, const wchar_t* result_file_to_check) { + if (!LaunchBrowser(browser, page)) { + DLOG(ERROR) << "Failed to launch browser " << ToString(browser); + } else { + ASSERT_TRUE(WaitForTestToComplete(kLongWaitTimeout)); + ASSERT_TRUE(CheckResultFile(result_file_to_check, "OK")); + } +} + +void ChromeFrameTestWithWebServer::VersionTest(BrowserKind browser, + const wchar_t* page, const wchar_t* result_file_to_check) { + std::wstring plugin_path; + PathService::Get(base::DIR_MODULE, &plugin_path); + file_util::AppendToPath(&plugin_path, L"servers/npchrome_tab.dll"); + + static FileVersionInfo* version_info = + FileVersionInfo::CreateFileVersionInfo(plugin_path); + + std::wstring version; + if (version_info) + version = version_info->product_version(); + + // If we can't find the npchrome_tab.dll in the src tree, we turn to + // the directory where chrome is installed. + if (!version_info) { + installer::Version* ver_system = InstallUtil::GetChromeVersion(true); + installer::Version* ver_user = InstallUtil::GetChromeVersion(false); + ASSERT_TRUE(ver_system || ver_user); + + bool system_install = ver_system ? true : false; + std::wstring npchrome_path(installer::GetChromeInstallPath(system_install)); + file_util::AppendToPath(&npchrome_path, + ver_system ? ver_system->GetString() : ver_user->GetString()); + file_util::AppendToPath(&npchrome_path, L"npchrome_tab.dll"); + version_info = FileVersionInfo::CreateFileVersionInfo(npchrome_path); + if (version_info) + version = version_info->product_version(); + } + + EXPECT_TRUE(version_info); + EXPECT_FALSE(version.empty()); + EXPECT_TRUE(LaunchBrowser(browser, page)); + ASSERT_TRUE(WaitForTestToComplete(kLongWaitTimeout)); + ASSERT_TRUE(CheckResultFile(result_file_to_check, WideToUTF8(version))); +} + +const wchar_t kPostMessageBasicPage[] = L"files/postmessage_basic_host.html"; + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_PostMessageBasic) { + SimpleBrowserTest(IE, kPostMessageBasicPage, L"PostMessage"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_PostMessageBasic) { + SimpleBrowserTest(FIREFOX, kPostMessageBasicPage, L"PostMessage"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeOpera_PostMessageBasic) { + OptionalBrowserTest(OPERA, kPostMessageBasicPage, L"PostMessage"); +} + +TEST_F(ChromeFrameTestWithWebServer, FullTabIE_MIMEFilterBasic) { + const wchar_t kMIMEFilterBasicPage[] = + L"files/chrome_frame_mime_filter_test.html"; + + SimpleBrowserTest(IE, kMIMEFilterBasicPage, L"MIMEFilter"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_Resize) { + SimpleBrowserTest(IE, L"files/chrome_frame_resize.html", L"Resize"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_Resize) { + SimpleBrowserTest(FIREFOX, L"files/chrome_frame_resize.html", L"Resize"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeOpera_Resize) { + OptionalBrowserTest(OPERA, L"files/chrome_frame_resize.html", L"Resize"); +} + +const wchar_t kNavigateURLAbsolutePage[] = + L"files/navigateurl_absolute_host.html"; + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_NavigateURLAbsolute) { + SimpleBrowserTest(IE, kNavigateURLAbsolutePage, L"NavigateURL"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_NavigateURLAbsolute) { + SimpleBrowserTest(FIREFOX, kNavigateURLAbsolutePage, L"NavigateURL"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeOpera_NavigateURLAbsolute) { + OptionalBrowserTest(OPERA, kNavigateURLAbsolutePage, L"NavigateURL"); +} + +const wchar_t kNavigateURLRelativePage[] = + L"files/navigateurl_relative_host.html"; + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_NavigateURLRelative) { + SimpleBrowserTest(IE, kNavigateURLRelativePage, L"NavigateURL"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_NavigateURLRelative) { + SimpleBrowserTest(FIREFOX, kNavigateURLRelativePage, L"NavigateURL"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeOpera_NavigateURLRelative) { + OptionalBrowserTest(OPERA, kNavigateURLRelativePage, L"NavigateURL"); +} + +const wchar_t kNavigateSimpleObjectFocus[] = L"files/simple_object_focus.html"; + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_ObjectFocus) { + SimpleBrowserTest(FIREFOX, kNavigateSimpleObjectFocus, L"ObjectFocus"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_ObjectFocus) { + SimpleBrowserTest(IE, kNavigateSimpleObjectFocus, L"ObjectFocus"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeOpera_ObjectFocus) { + if (!LaunchBrowser(OPERA, kNavigateSimpleObjectFocus)) { + DLOG(ERROR) << "Failed to launch browser " << ToString(OPERA); + } else { + ASSERT_TRUE(WaitForOnLoad(kLongWaitTimeout)); + BringBrowserToTop(); + // Tab through a couple of times. Once should be enough in theory + // but in practice activating the browser can take a few milliseconds more. + bool ok; + for (int i = 0; + i < 5 && (ok = CheckResultFile(L"ObjectFocus", "OK")) == false; + ++i) { + Sleep(300); + chrome_frame_test::SendMnemonic(VK_TAB, false, false, false); + } + ASSERT_TRUE(ok); + } +} + +const wchar_t kiframeBasicPage[] = L"files/iframe_basic_host.html"; + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_iframeBasic) { + SimpleBrowserTest(IE, kiframeBasicPage, L"PostMessage"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_iframeBasic) { + SimpleBrowserTest(FIREFOX, kiframeBasicPage, L"PostMessage"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeOpera_iframeBasic) { + OptionalBrowserTest(OPERA, kiframeBasicPage, L"PostMessage"); +} + +const wchar_t kSrcPropertyTestPage[] = L"files/src_property_host.html"; + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_SrcProperty) { + SimpleBrowserTest(IE, kSrcPropertyTestPage, L"SrcProperty"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_SrcProperty) { + SimpleBrowserTest(FIREFOX, kSrcPropertyTestPage, L"SrcProperty"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeOpera_SrcProperty) { + OptionalBrowserTest(OPERA, kSrcPropertyTestPage, L"SrcProperty"); +} + +const wchar_t kCFInstanceBasicTestPage[] = L"files/CFInstance_basic_host.html"; + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_CFInstanceBasic) { + SimpleBrowserTest(IE, kCFInstanceBasicTestPage, L"CFInstanceBasic"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_CFInstanceBasic) { + SimpleBrowserTest(FIREFOX, kCFInstanceBasicTestPage, L"CFInstanceBasic"); +} + +const wchar_t kCFISingletonPage[] = L"files/CFInstance_singleton_host.html"; + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_CFInstanceSingleton) { + SimpleBrowserTest(IE, kCFISingletonPage, L"CFInstanceSingleton"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_CFInstanceSingleton) { + SimpleBrowserTest(FIREFOX, kCFISingletonPage, L"CFInstanceSingleton"); +} + +const wchar_t kCFIDelayPage[] = L"files/CFInstance_delay_host.html"; + +TEST_F(ChromeFrameTestWithWebServer, DISABLED_WidgetModeIE_CFInstanceDelay) { + SimpleBrowserTest(IE, kCFIDelayPage, L"CFInstanceDelay"); +} + +TEST_F(ChromeFrameTestWithWebServer, DISABLED_WidgetModeFF_CFInstanceDelay) { + SimpleBrowserTest(FIREFOX, kCFIDelayPage, L"CFInstanceDelay"); +} + +TEST_F(ChromeFrameTestWithWebServer, DISABLED_WidgetModeOpera_CFInstanceDelay) { + OptionalBrowserTest(OPERA, kCFIDelayPage, L"CFInstanceDelay"); +} + +const wchar_t kCFIFallbackPage[] = L"files/CFInstance_fallback_host.html"; + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_CFInstanceFallback) { + SimpleBrowserTest(IE, kCFIFallbackPage, L"CFInstanceFallback"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_CFInstanceFallback) { + SimpleBrowserTest(FIREFOX, kCFIFallbackPage, L"CFInstanceFallback"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeOpera_CFInstanceFallback) { + OptionalBrowserTest(OPERA, kCFIFallbackPage, L"CFInstanceFallback"); +} + +const wchar_t kCFINoSrcPage[] = L"files/CFInstance_no_src_host.html"; + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_CFInstanceNoSrc) { + SimpleBrowserTest(IE, kCFINoSrcPage, L"CFInstanceNoSrc"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_CFInstanceNoSrc) { + SimpleBrowserTest(FIREFOX, kCFINoSrcPage, L"CFInstanceNoSrc"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeOpera_CFInstanceNoSrc) { + OptionalBrowserTest(OPERA, kCFINoSrcPage, L"CFInstanceNoSrc"); +} + +const wchar_t kCFIIfrOnLoadPage[] = L"files/CFInstance_iframe_onload_host.html"; + +// disabled since it's unlikely that we care about this case +TEST_F(ChromeFrameTestWithWebServer, DISABLED_WidgetModeIE_CFInstanceIfrOnLoad) { + SimpleBrowserTest(IE, kCFIIfrOnLoadPage, L"CFInstanceIfrOnLoad"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_CFInstanceIfrOnLoad) { + SimpleBrowserTest(FIREFOX, kCFIIfrOnLoadPage, L"CFInstanceIfrOnLoad"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeOpera_CFInstanceIfrOnLoad) { + OptionalBrowserTest(OPERA, kCFIIfrOnLoadPage, L"CFInstanceIfrOnLoad"); +} + +const wchar_t kCFIZeroSizePage[] = L"files/CFInstance_zero_size_host.html"; + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_CFInstanceZeroSize) { + SimpleBrowserTest(IE, kCFIZeroSizePage, L"CFInstanceZeroSize"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_CFInstanceZeroSize) { + SimpleBrowserTest(FIREFOX, kCFIZeroSizePage, L"CFInstanceZeroSize"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeOpera_CFInstanceZeroSize) { + OptionalBrowserTest(OPERA, kCFIZeroSizePage, L"CFInstanceZeroSize"); +} + +const wchar_t kCFIIfrPostPage[] = L"files/CFInstance_iframe_post_host.html"; + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_CFInstanceIfrPost) { + SimpleBrowserTest(IE, kCFIIfrPostPage, L"CFInstanceIfrPost"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_CFInstanceIfrPost) { + SimpleBrowserTest(FIREFOX, kCFIIfrPostPage, L"CFInstanceIfrPost"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeChrome_CFInstanceIfrPost) { + OptionalBrowserTest(CHROME, kCFIIfrPostPage, L"CFInstanceIfrPost"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeSafari_CFInstanceIfrPost) { + OptionalBrowserTest(SAFARI, kCFIIfrPostPage, L"CFInstanceIfrPost"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeOpera_CFInstanceIfrPost) { + OptionalBrowserTest(OPERA, kCFIIfrPostPage, L"CFInstanceIfrPost"); +} + +const wchar_t kCFIPostPage[] = L"files/CFInstance_post_host.html"; + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_CFInstancePost) { + SimpleBrowserTest(IE, kCFIPostPage, L"CFInstancePost"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_CFInstancePost) { + SimpleBrowserTest(FIREFOX, kCFIPostPage, L"CFInstancePost"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeChrome_CFInstancePost) { + OptionalBrowserTest(CHROME, kCFIPostPage, L"CFInstancePost"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeSafari_CFInstancePost) { + OptionalBrowserTest(SAFARI, kCFIPostPage, L"CFInstancePost"); +} + +TEST_F(ChromeFrameTestWithWebServer, DISABLED_WidgetModeOpera_CFInstancePost) { + OptionalBrowserTest(OPERA, kCFIPostPage, L"CFInstancePost"); +} + +const wchar_t kCFIRPCPage[] = L"files/CFInstance_rpc_host.html"; + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_CFInstanceRPC) { + SimpleBrowserTest(IE, kCFIRPCPage, L"CFInstanceRPC"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_CFInstanceRPC) { + SimpleBrowserTest(FIREFOX, kCFIRPCPage, L"CFInstanceRPC"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeChrome_CFInstanceRPC) { + OptionalBrowserTest(CHROME, kCFIRPCPage, L"CFInstanceRPC"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeSafari_CFInstanceRPC) { + OptionalBrowserTest(SAFARI, kCFIRPCPage, L"CFInstanceRPC"); +} + +TEST_F(ChromeFrameTestWithWebServer, DISABLED_WidgetModeOpera_CFInstanceRPC) { + OptionalBrowserTest(OPERA, kCFIRPCPage, L"CFInstanceRPC"); +} + +const wchar_t kCFIRPCInternalPage[] = + L"files/CFInstance_rpc_internal_host.html"; + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_CFInstanceRPCInternal) { + SimpleBrowserTest(IE, kCFIRPCInternalPage, L"CFInstanceRPCInternal"); +} + +// Disabled: http://b/issue?id=2050201 +TEST_F(ChromeFrameTestWithWebServer, DISABLED_WidgetModeFF_CFInstanceRPCInternal) { + SimpleBrowserTest(FIREFOX, kCFIRPCInternalPage, L"CFInstanceRPCInternal"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeChrome_CFInstanceRPCInternal) { + OptionalBrowserTest(CHROME, kCFIRPCInternalPage, L"CFInstanceRPCInternal"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeSafari_CFInstanceRPCInternal) { + OptionalBrowserTest(SAFARI, kCFIRPCInternalPage, L"CFInstanceRPCInternal"); +} + +const wchar_t kCFIDefaultCtorPage[] = + L"files/CFInstance_default_ctor_host.html"; + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_CFInstanceDefaultCtor) { + SimpleBrowserTest(IE, kCFIDefaultCtorPage, L"CFInstanceDefaultCtor"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_CFInstanceDefaultCtor) { + SimpleBrowserTest(FIREFOX, kCFIDefaultCtorPage, L"CFInstanceDefaultCtor"); +} + +// Class that mocks external call from VectoredHandlerT for testing purposes. +class EMock : public VEHTraitsBase { + public: + static inline bool WriteDump(EXCEPTION_POINTERS* p) { + g_dump_made = true; + return true; + } + + static inline void* Register(PVECTORED_EXCEPTION_HANDLER func, + const void* module_start, + const void* module_end) { + VEHTraitsBase::SetModule(module_start, module_end); + // Return some arbitrary number, expecting to get the same on Unregister() + return reinterpret_cast<void*>(4); + } + + static inline ULONG Unregister(void* handle) { + EXPECT_EQ(handle, reinterpret_cast<void*>(4)); + return 1; + } + + static inline WORD RtlCaptureStackBackTrace(DWORD FramesToSkip, + DWORD FramesToCapture, void** BackTrace, DWORD* BackTraceHash) { + EXPECT_EQ(2, FramesToSkip); + EXPECT_LE(FramesToSkip + FramesToCapture, + VectoredHandlerBase::max_back_trace); + memcpy(BackTrace, g_stack, g_stack_entries * sizeof(BackTrace[0])); + return g_stack_entries; + } + + static inline EXCEPTION_REGISTRATION_RECORD* RtlpGetExceptionList() { + return g_seh_chain; + } + + // Test helpers + + // Create fake SEH chain of random filters - with and without our module. + static void SetHaveSEHFilter() { + SetSEHChain(reinterpret_cast<const char*>(g_module_start) - 0x1000, + reinterpret_cast<const char*>(g_module_start) + 0x1000, + reinterpret_cast<const char*>(g_module_end) + 0x7127, + NULL); + } + + static void SetNoSEHFilter() { + SetSEHChain(reinterpret_cast<const char*>(g_module_start) - 0x1000, + reinterpret_cast<const char*>(g_module_end) + 0x7127, + NULL); + } + + // Create fake stack - with and without our module. + static void SetOnStack() { + SetStack(reinterpret_cast<const char*>(g_module_start) - 0x11283, + reinterpret_cast<const char*>(g_module_start) - 0x278361, + reinterpret_cast<const char*>(g_module_start) + 0x9171, + reinterpret_cast<const char*>(g_module_end) + 1231, + NULL); + } + + static void SetNotOnStack() { + SetStack(reinterpret_cast<const char*>(g_module_start) - 0x11283, + reinterpret_cast<const char*>(g_module_start) - 0x278361, + reinterpret_cast<const char*>(g_module_end) + 1231, + NULL); + } + + // Populate stack array + static void SetStack(const void* p, ...) { + va_list vl; + va_start(vl, p); + g_stack_entries = 0; + for (; p; ++g_stack_entries) { + CHECK(g_stack_entries < arraysize(g_stack)); + g_stack[g_stack_entries] = p; + p = va_arg(vl, const void*); + } + } + + static void SetSEHChain(const void* p, ...) { + va_list vl; + va_start(vl, p); + int i = 0; + for (; p; ++i) { + CHECK(i + 1 < arraysize(g_seh_chain)); + g_seh_chain[i].Handler = const_cast<void*>(p); + g_seh_chain[i].Next = &g_seh_chain[i + 1]; + p = va_arg(vl, const void*); + } + + g_seh_chain[i].Next = EXCEPTION_CHAIN_END; + } + + static EXCEPTION_REGISTRATION_RECORD g_seh_chain[25]; + static const void* g_stack[VectoredHandlerBase::max_back_trace]; + static WORD g_stack_entries; + static bool g_dump_made; +}; + +EXCEPTION_REGISTRATION_RECORD EMock::g_seh_chain[25]; +const void* EMock::g_stack[VectoredHandlerBase::max_back_trace]; +WORD EMock::g_stack_entries; +bool EMock::g_dump_made; + +typedef VectoredHandlerT<EMock> VectoredHandlerMock; + +class ExPtrsHelper : public _EXCEPTION_POINTERS { + public: + ExPtrsHelper() { + ExceptionRecord = &er_; + ContextRecord = &ctx_; + ZeroMemory(&er_, sizeof(er_)); + ZeroMemory(&ctx_, sizeof(ctx_)); + } + + void Set(DWORD code, void* address, DWORD flags) { + er_.ExceptionCode = code; + er_.ExceptionAddress = address; + er_.ExceptionFlags = flags; + } + + EXCEPTION_RECORD er_; + CONTEXT ctx_; +}; + + +TEST(ChromeFrame, ExceptionReport) { + char* s = reinterpret_cast<char*>(0x30000000); + char* e = s + 0x10000; + void* handler = VectoredHandlerMock::Register(s, e); + char* our_code = s + 0x1111; + char* not_our_code = s - 0x5555; + char* not_our_code2 = e + 0x5555; + + ExPtrsHelper ex; + // Exception in our code, but we have SEH filter + ex.Set(STATUS_ACCESS_VIOLATION, our_code, 0); + EMock::SetHaveSEHFilter(); + EMock::SetOnStack(); + EXPECT_EQ(ExceptionContinueSearch, VectoredHandlerMock::VectoredHandler(&ex)); + EXPECT_EQ(1, VectoredHandlerMock::g_exceptions_seen); + EXPECT_FALSE(EMock::g_dump_made); + + // RPC_E_DISCONNECTED (0x80010108) is "The object invoked has disconnected + // from its clients", shall not be caught since it's a warning only. + ex.Set(RPC_E_DISCONNECTED, our_code, 0); + EMock::SetHaveSEHFilter(); + EMock::SetOnStack(); + EXPECT_EQ(ExceptionContinueSearch, VectoredHandlerMock::VectoredHandler(&ex)); + EXPECT_EQ(1, VectoredHandlerMock::g_exceptions_seen); + EXPECT_FALSE(EMock::g_dump_made); + + + // Exception, not in our code, we do not have SEH and we are not in stack. + ex.Set(STATUS_INTEGER_DIVIDE_BY_ZERO, not_our_code, 0); + EMock::SetNoSEHFilter(); + EMock::SetNotOnStack(); + EXPECT_EQ(ExceptionContinueSearch, VectoredHandlerMock::VectoredHandler(&ex)); + EXPECT_EQ(2, VectoredHandlerMock::g_exceptions_seen); + EXPECT_FALSE(EMock::g_dump_made); + + // Exception, not in our code, no SEH, but we are on the stack. + ex.Set(STATUS_INTEGER_DIVIDE_BY_ZERO, not_our_code2, 0); + EMock::SetNoSEHFilter(); + EMock::SetOnStack(); + EXPECT_EQ(ExceptionContinueSearch, VectoredHandlerMock::VectoredHandler(&ex)); + EXPECT_EQ(3, VectoredHandlerMock::g_exceptions_seen); + EXPECT_TRUE(EMock::g_dump_made); + EMock::g_dump_made = false; + + + // Exception, in our code, no SEH, not on stack (assume FPO screwed us) + ex.Set(STATUS_INTEGER_DIVIDE_BY_ZERO, our_code, 0); + EMock::SetNoSEHFilter(); + EMock::SetNotOnStack(); + EXPECT_EQ(ExceptionContinueSearch, VectoredHandlerMock::VectoredHandler(&ex)); + EXPECT_EQ(4, VectoredHandlerMock::g_exceptions_seen); + EXPECT_TRUE(EMock::g_dump_made); + EMock::g_dump_made = false; + + VectoredHandlerMock::Unregister(); +} + +const wchar_t kInitializeHiddenPage[] = L"files/initialize_hidden.html"; + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_InitializeHidden) { + SimpleBrowserTest(IE, kInitializeHiddenPage, L"InitializeHidden"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_InitializeHidden) { + SimpleBrowserTest(FIREFOX, kInitializeHiddenPage, L"InitializeHidden"); +} + +// Disabled due to a problem with Opera. +// http://b/issue?id=1708275 +TEST_F(ChromeFrameTestWithWebServer, DISABLED_WidgetModeOpera_InitializeHidden) { + OptionalBrowserTest(OPERA, kInitializeHiddenPage, L"InitializeHidden"); +} + +const wchar_t kInHeadPage[] = L"files/in_head.html"; + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_InHead) { + SimpleBrowserTest(FIREFOX, kInHeadPage, L"InHead"); +} + +const wchar_t kVersionPage[] = L"files/version.html"; + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_Version) { + VersionTest(IE, kVersionPage, L"version"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_Version) { + VersionTest(FIREFOX, kVersionPage, L"version"); +} + +const wchar_t kEventListenerPage[] = L"files/event_listener.html"; + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_EventListener) { + SimpleBrowserTest(IE, kEventListenerPage, L"EventListener"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_EventListener) { + SimpleBrowserTest(FIREFOX, kEventListenerPage, L"EventListener"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeOpera_EventListener) { + OptionalBrowserTest(OPERA, kEventListenerPage, L"EventListener"); +} + +const wchar_t kPrivilegedApisPage[] = L"files/privileged_apis_host.html"; + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeIE_PrivilegedApis) { + SimpleBrowserTest(IE, kPrivilegedApisPage, L"PrivilegedApis"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeFF_PrivilegedApis) { + SimpleBrowserTest(FIREFOX, kPrivilegedApisPage, L"PrivilegedApis"); +} + +TEST_F(ChromeFrameTestWithWebServer, WidgetModeOpera_PrivilegedApis) { + OptionalBrowserTest(OPERA, kPrivilegedApisPage, L"PrivilegedApis"); +} + +class ChromeFrameTestEnvironment: public testing::Environment { + public: + ~ChromeFrameTestEnvironment() { + } + + void SetUp() { + ScopedChromeFrameRegistrar::RegisterDefaults(); + } + + void TearDown() { + } +}; + +::testing::Environment* const chrome_frame_env =
+ ::testing::AddGlobalTestEnvironment(new ChromeFrameTestEnvironment); + +// TODO(stoyan): - Move everything below in separate file(s). +struct LaunchDelegateMock : public ProxyFactory::LaunchDelegate { + MOCK_METHOD2(LaunchComplete, void(ChromeFrameAutomationProxy*, + AutomationLaunchResult)); +}; + +TEST(ProxyFactoryTest, CreateDestroy) { + ProxyFactory f; + LaunchDelegateMock d; + EXPECT_CALL(d, LaunchComplete(testing::NotNull(), testing::_)).Times(1); + void* id = f.GetAutomationServer(0, L"Adam.N.Epilinter", L"", false, &d); + f.ReleaseAutomationServer(id); +} + +TEST(ProxyFactoryTest, CreateSameProfile) { + ProxyFactory f; + LaunchDelegateMock d; + EXPECT_CALL(d, LaunchComplete(testing::NotNull(), testing::_)).Times(2); + void* i1 = f.GetAutomationServer(0, L"Dr. Gratiano Forbeson", L"", false, &d); + void* i2 = f.GetAutomationServer(0, L"Dr. Gratiano Forbeson", L"", false, &d); + EXPECT_EQ(i1, i2); + f.ReleaseAutomationServer(i2); + f.ReleaseAutomationServer(i1); +} + +TEST(ProxyFactoryTest, CreateDifferentProfiles) { + ProxyFactory f; + LaunchDelegateMock d; + EXPECT_CALL(d, LaunchComplete(testing::NotNull(), testing::_)).Times(2); + void* i1 = f.GetAutomationServer(0, L"Adam.N.Epilinter", L"", false, &d); + void* i2 = f.GetAutomationServer(0, L"Dr. Gratiano Forbeson", L"", false, &d); + EXPECT_NE(i1, i2); + f.ReleaseAutomationServer(i2); + f.ReleaseAutomationServer(i1); +} + +// ChromeFrameAutomationClient [CFAC] tests. +struct MockCFDelegate : public ChromeFrameDelegateImpl { + MOCK_CONST_METHOD0(GetWindow, WindowType()); + MOCK_METHOD1(GetBounds, void(RECT* bounds)); + MOCK_METHOD0(GetDocumentUrl, std::string()); + MOCK_METHOD2(ExecuteScript, bool(const std::string& script, + std::string* result)); + MOCK_METHOD0(OnAutomationServerReady, void()); + MOCK_METHOD2(OnAutomationServerLaunchFailed, void( + AutomationLaunchResult reason, const std::string& server_version)); + // This remains in interface since we call it if Navigate() + // returns immediate error. + MOCK_METHOD2(OnLoadFailed, void(int error_code, const std::string& url)); + + // Do not mock this method. :) Use it as message demuxer and dispatcher + // to the following methods (which we mock) + // MOCK_METHOD1(OnMessageReceived, void(const IPC::Message&)); + + + MOCK_METHOD2(OnNavigationStateChanged, void(int tab_handle, int flags)); + MOCK_METHOD2(OnUpdateTargetUrl, void(int tab_handle, + const std::wstring& new_target_url)); + MOCK_METHOD2(OnAcceleratorPressed, void(int tab_handle, + const MSG& accel_message)); + MOCK_METHOD2(OnTabbedOut, void(int tab_handle, bool reverse)); + MOCK_METHOD3(OnOpenURL, void(int tab_handle, const GURL& url, + int open_disposition)); + MOCK_METHOD2(OnDidNavigate, void(int tab_handle, + const IPC::NavigationInfo& navigation_info)); + MOCK_METHOD3(OnNavigationFailed, void(int tab_handle, int error_code, + const GURL& gurl)); + MOCK_METHOD2(OnLoad, void(int tab_handle, const GURL& url)); + MOCK_METHOD4(OnMessageFromChromeFrame, void(int tab_handle, + const std::string& message, + const std::string& origin, + const std::string& target)); + MOCK_METHOD5(OnHandleContextMenu, void(int tab_handle, HANDLE menu_handle, + int x_pos, int y_pos, int align_flags)); + MOCK_METHOD3(OnRequestStart, void(int tab_handle, int request_id, + const IPC::AutomationURLRequest& request)); + MOCK_METHOD3(OnRequestRead, void(int tab_handle, int request_id, + int bytes_to_read)); + MOCK_METHOD3(OnRequestEnd, void(int tab_handle, int request_id, + const URLRequestStatus& status)); + MOCK_METHOD3(OnSetCookieAsync, void(int tab_handle, const GURL& url, + const std::string& cookie)); + + // Use for sending network responses + void SetAutomationSender(IPC::Message::Sender* automation) { + automation_ = automation; + } + + // Set-expectation helpers + void SetOnNavigationStateChanged(int tab_handle) { + EXPECT_CALL(*this, + OnNavigationStateChanged(testing::Eq(tab_handle), testing::_)) + .Times(testing::AnyNumber()); + } + + // Response sender helpers + void ReplyStarted(const IPC::AutomationURLResponse* response, + int tab_handle, int request_id, + const IPC::AutomationURLRequest& request) { + automation_->Send(new AutomationMsg_RequestStarted(0, tab_handle, + request_id, *response)); + } + + void ReplyData(const std::string* data, int tab_handle, int request_id, + int bytes_to_read) { + automation_->Send(new AutomationMsg_RequestData(0, tab_handle, + request_id, *data)); + } + + void ReplyEOF(int tab_handle, int request_id) { + automation_->Send(new AutomationMsg_RequestEnd(0, tab_handle, + request_id, URLRequestStatus())); + } + + void Reply404(int tab_handle, int request_id, + const IPC::AutomationURLRequest& request) { + const IPC::AutomationURLResponse notfound = {"", "HTTP/1.1 404\r\n\r\n"}; + automation_->Send(new AutomationMsg_RequestStarted(0, tab_handle, + request_id, notfound)); + automation_->Send(new AutomationMsg_RequestEnd(0, tab_handle, + request_id, URLRequestStatus())); + } + + IPC::Message::Sender* automation_; +}; + +class MockProxyFactory : public ProxyFactory { + public: + MOCK_METHOD5(GetAutomationServer, void*(int, const std::wstring&, + const std::wstring& extra_argument, bool, ProxyFactory::LaunchDelegate*)); + MOCK_METHOD1(ReleaseAutomationServer, bool(void* id)); + + MockProxyFactory() : thread_("mock factory worker") { + thread_.Start(); + loop_ = thread_.message_loop(); + } + + // Fake implementation + void GetServerImpl(ChromeFrameAutomationProxy* pxy, + AutomationLaunchResult result, + int timeout, + ProxyFactory::LaunchDelegate* d) { + Task* task = NewRunnableMethod(d, + &ProxyFactory::LaunchDelegate::LaunchComplete, pxy, result); + loop_->PostDelayedTask(FROM_HERE, task, timeout/2); + } + + base::Thread thread_; + MessageLoop* loop_; +}; + +class MockAutomationProxy : public ChromeFrameAutomationProxy { + public: + MOCK_METHOD1(Send, bool(IPC::Message*)); + MOCK_METHOD3(SendAsAsync, void(IPC::SyncMessage* msg, void* callback, + void* key)); + MOCK_METHOD1(CancelAsync, void(void* key)); + MOCK_METHOD1(CreateTabProxy, scoped_refptr<TabProxy>(int handle)); + MOCK_METHOD0(server_version, std::string(void)); + MOCK_METHOD1(SendProxyConfig, void(const std::string&)); + MOCK_METHOD1(SetEnableExtensionAutomation, void(bool enable)); + + ~MockAutomationProxy() {} +}; + +struct MockAutomationMessageSender : public AutomationMessageSender { + MOCK_METHOD1(Send, bool(IPC::Message*)); + MOCK_METHOD3(SendWithTimeout, bool(IPC::Message* , int , bool*)); + + void ForwardTo(MockAutomationProxy *p) { + proxy_ = p; + ON_CALL(*this, Send(testing::_)) + .WillByDefault(testing::Invoke(proxy_, &MockAutomationProxy::Send)); + } + + MockAutomationProxy* proxy_; +}; + +template <> struct RunnableMethodTraits<ProxyFactory::LaunchDelegate> { + static void RetainCallee(ProxyFactory::LaunchDelegate* obj) {} + static void ReleaseCallee(ProxyFactory::LaunchDelegate* obj) {} +}; + +template <> struct RunnableMethodTraits<MockProxyFactory> { + static void RetainCallee(MockProxyFactory* obj) {} + static void ReleaseCallee(MockProxyFactory* obj) {} +}; + +template <> struct RunnableMethodTraits<ChromeFrameAutomationClient> { + static void RetainCallee(ChromeFrameAutomationClient* obj) {} + static void ReleaseCallee(ChromeFrameAutomationClient* obj) {} +}; + +// MessageLoopForUI wrapper that runs only for a limited time. +// We need a UI message loop in the main thread. +struct TimedMsgLoop { + public: + void RunFor(int seconds) { + loop_.PostDelayedTask(FROM_HERE, new MessageLoop::QuitTask, 1000 * seconds); + loop_.MessageLoop::Run(); + } + + void Quit() { + loop_.PostTask(FROM_HERE, new MessageLoop::QuitTask); + } + + MessageLoopForUI loop_; +}; + +template <> struct RunnableMethodTraits<TimedMsgLoop> { + static void RetainCallee(TimedMsgLoop* obj) {} + static void ReleaseCallee(TimedMsgLoop* obj) {} +}; + +// Saves typing. It's somewhat hard to create a wrapper around +// testing::InvokeWithoutArgs since it returns a +// non-public (testing::internal) type. +#define QUIT_LOOP(loop) testing::InvokeWithoutArgs(TaskHolder(\ + NewRunnableMethod(&loop, &TimedMsgLoop::Quit))) + +// We mock ChromeFrameDelegate only. The rest is with real AutomationProxy +TEST(CFACWithChrome, CreateTooFast) { + MockCFDelegate cfd; + TimedMsgLoop loop; + int timeout = 0; // Chrome cannot send Hello message so fast. + const std::wstring profile = L"Adam.N.Epilinter"; + + scoped_ptr<ChromeFrameAutomationClient> client; + client.reset(new ChromeFrameAutomationClient()); + + EXPECT_CALL(cfd, OnAutomationServerLaunchFailed(AUTOMATION_TIMEOUT, + testing::_)) + .Times(1) + .WillOnce(QUIT_LOOP(loop)); + + EXPECT_TRUE(client->Initialize(&cfd, timeout, false, profile, L"", false)); + loop.RunFor(10); + client->Uninitialize(); +} + +// This test may fail if Chrome take more that 10 seconds (timeout var) to +// launch. In this case GMock shall print something like "unexpected call to +// OnAutomationServerLaunchFailed". I'm yet to find out how to specify +// that this is an unexpected call, and still to execute and action. +TEST(CFACWithChrome, CreateNotSoFast) { + MockCFDelegate cfd; + TimedMsgLoop loop; + const std::wstring profile = L"Adam.N.Epilinter"; + int timeout = 10000; + + scoped_ptr<ChromeFrameAutomationClient> client; + client.reset(new ChromeFrameAutomationClient); + + EXPECT_CALL(cfd, OnAutomationServerReady()) + .Times(1) + .WillOnce(QUIT_LOOP(loop)); + + EXPECT_CALL(cfd, OnAutomationServerLaunchFailed(testing::_, testing::_)) + .Times(0); + + EXPECT_TRUE(client->Initialize(&cfd, timeout, false, profile, L"", false)); + + loop.RunFor(11); + client->Uninitialize(); + client.reset(NULL); +} + +MATCHER_P(MsgType, msg_type, "IPC::Message::type()") { + const IPC::Message& m = arg; + return (m.type() == msg_type); +} + +MATCHER_P(EqNavigationInfoUrl, url, "IPC::NavigationInfo matcher") { + if (url.is_valid() && url != arg.url) + return false; + // TODO: other members + return true; +} + +TEST(CFACWithChrome, NavigateOk) { + MockCFDelegate cfd; + TimedMsgLoop loop; + const std::wstring profile = L"Adam.N.Epilinter"; + const std::string url = "about:version"; + int timeout = 10000; + + scoped_ptr<ChromeFrameAutomationClient> client; + client.reset(new ChromeFrameAutomationClient); + + EXPECT_CALL(cfd, OnAutomationServerReady()) + .WillOnce(testing::InvokeWithoutArgs(TaskHolder(NewRunnableMethod( + client.get(), &ChromeFrameAutomationClient::InitiateNavigation, + url)))); + +// cfd.SetOnNavigationStateChanged(); + EXPECT_CALL(cfd, + OnNavigationStateChanged(testing::_, testing::_)) + .Times(testing::AnyNumber()); + + { + testing::InSequence s; + + EXPECT_CALL(cfd, OnDidNavigate(testing::_, EqNavigationInfoUrl(GURL()))) + .Times(1); + + EXPECT_CALL(cfd, OnUpdateTargetUrl(testing::_, testing::_)).Times(1); + + EXPECT_CALL(cfd, OnLoad(testing::_, testing::_)) + .Times(1) + .WillOnce(QUIT_LOOP(loop)); + } + + EXPECT_TRUE(client->Initialize(&cfd, timeout, false, profile, L"", false)); + loop.RunFor(10); + client->Uninitialize(); + client.reset(NULL); +} + +// Bug: http://b/issue?id=2033644 +TEST(CFACWithChrome, DISABLED_NavigateFailed) { + MockCFDelegate cfd; + TimedMsgLoop loop; + const std::wstring profile = L"Adam.N.Epilinter"; + const std::string url = "http://127.0.0.3:65412/"; + int timeout = 10000; + + scoped_ptr<ChromeFrameAutomationClient> client; + client.reset(new ChromeFrameAutomationClient); + + EXPECT_CALL(cfd, OnAutomationServerReady()) + .WillOnce(testing::InvokeWithoutArgs(TaskHolder(NewRunnableMethod( + client.get(), &ChromeFrameAutomationClient::InitiateNavigation, + url)))); + + EXPECT_CALL(cfd, + OnNavigationStateChanged(testing::_, testing::_)) + .Times(testing::AnyNumber()); + + EXPECT_CALL(cfd, OnNavigationFailed(testing::_, testing::_, testing::_)) + .Times(1); + + EXPECT_CALL(cfd, OnUpdateTargetUrl(testing::_, testing::_)) + .Times(testing::AnyNumber()); + + EXPECT_CALL(cfd, OnLoad(testing::_, testing::_)) + .Times(0); + + EXPECT_TRUE(client->Initialize(&cfd, timeout, false, profile, L"", false)); + + loop.RunFor(10); + client->Uninitialize(); + client.reset(NULL); +} + +MATCHER_P(EqURLRequest, x, "IPC::AutomationURLRequest matcher") { + if (arg.url != x.url) + return false; + if (arg.method != x.method) + return false; + if (arg.referrer != x.referrer) + return false; + if (arg.extra_request_headers != x.extra_request_headers) + return false; + // TODO: uploaddata member + return true; +} + +MATCHER_P(EqUrlGet, url, "Quick URL matcher for 'HTTP GET' request") { + if (arg.url != url) + return false; + if (arg.method != "GET") + return false; + return true; +} + +TEST(CFACWithChrome, UseHostNetworkStack) { + MockCFDelegate cfd; + TimedMsgLoop loop; + const std::wstring profile = L"Adam.N.Epilinter"; + const std::string url = "http://bongo.com"; + int timeout = 10000; + + scoped_ptr<ChromeFrameAutomationClient> client; + client.reset(new ChromeFrameAutomationClient); + client->set_use_chrome_network(false); + cfd.SetAutomationSender(client.get()); + + EXPECT_CALL(cfd, OnAutomationServerReady()) + .WillOnce(testing::InvokeWithoutArgs(TaskHolder(NewRunnableMethod( + client.get(), &ChromeFrameAutomationClient::InitiateNavigation, + url)))); + + EXPECT_CALL(cfd, OnNavigationStateChanged(testing::_, testing::_)) + .Times(testing::AnyNumber()); + + EXPECT_CALL(cfd, GetBounds(testing::_)) + .Times(testing::AtMost(1)); + + EXPECT_CALL(cfd, OnUpdateTargetUrl(testing::_, testing::_)) + .Times(testing::AnyNumber()); + + // Note slash appending to the url string, because of GURL inside chrome + const IPC::AutomationURLResponse found = {"", "HTTP/0.9 200\r\n\r\n\r\n", }; + + // Hard coded tab and request ids + static const int tab_id = 1; + int request_id = 2; + + EXPECT_CALL(cfd, OnRequestStart(tab_id, request_id, EqUrlGet(url + '/'))) + .Times(1) + .WillOnce(testing::Invoke(CBF(&cfd, &MockCFDelegate::ReplyStarted, + &found))); + + // Return some trivial page, that have a link to a "logo.gif" image + const std::string data = "<!DOCTYPE html><title>Hello</title>" + "<img src=\"logo.gif\">"; + EXPECT_CALL(cfd, OnRequestRead(tab_id, request_id, testing::Ge(0))) + .Times(2) + .WillOnce(testing::Invoke(CBF(&cfd, &MockCFDelegate::ReplyData, &data))) + .WillOnce(testing::WithArgs<0, 1>(testing::Invoke(CBF(&cfd, + &MockCFDelegate::ReplyEOF)))); + + EXPECT_CALL(cfd, OnDidNavigate(tab_id, EqNavigationInfoUrl(GURL(url)))) + .Times(1); + EXPECT_CALL(cfd, OnLoad(tab_id, GURL(url))) + .Times(1); + + // Expect request for logo.gif + request_id++; + EXPECT_CALL(cfd, + OnRequestStart(tab_id, request_id, EqUrlGet(url + "/logo.gif"))) + .Times(1) + .WillOnce(testing::Invoke(CBF(&cfd, &MockCFDelegate::Reply404))); + + EXPECT_CALL(cfd, OnRequestRead(tab_id, request_id, testing::_)) + .Times(testing::AtMost(1)); + + // Chrome makes a brave request for favicon.ico + request_id++; + EXPECT_CALL(cfd, + OnRequestStart(tab_id, request_id, EqUrlGet(url + "/favicon.ico"))) + .Times(1) + .WillOnce(testing::Invoke(CBF(&cfd, &MockCFDelegate::Reply404))); + + EXPECT_CALL(cfd, OnRequestRead(tab_id, request_id, testing::_)) + .Times(testing::AtMost(1)); + + bool incognito = true; + EXPECT_TRUE(client->Initialize(&cfd, timeout, false, profile, L"", + incognito)); + + loop.RunFor(10); + client->Uninitialize(); + client.reset(NULL); +} + + +// [CFAC] -- uses a ProxyFactory for creation of ChromeFrameAutomationProxy +// -- uses ChromeFrameAutomationProxy +// -- uses TabProxy obtained from ChromeFrameAutomationProxy +// -- uses ChromeFrameDelegate as outgoing interface +// +// We mock ProxyFactory to return mock object (MockAutomationProxy) implementing +// ChromeFrameAutomationProxy interface. +// Since CFAC uses TabProxy for few calls and TabProxy is not easy mockable, +// we create 'real' TabProxy but with fake AutomationSender (the one responsible +// for sending messages over channel). +// Additionally we have mock implementation ChromeFrameDelagate interface - +// MockCFDelegate. + +// Test fixture, saves typing all of it's members. +class CFACMockTest : public testing::Test { + public: + MockProxyFactory factory_; + MockCFDelegate cfd_; + TimedMsgLoop loop_; + MockAutomationProxy proxy_; + scoped_ptr<AutomationHandleTracker> tracker_; + MockAutomationMessageSender dummy_sender_; + scoped_refptr<TabProxy> tab_; + scoped_ptr<ChromeFrameAutomationClient> client_; // the victim of all tests + + std::wstring profile_; + int timeout_; + void* id_; // Automation server id we are going to return + int tab_handle_; // Tab handle. Any non-zero value is Ok. + + inline ChromeFrameAutomationProxy* get_proxy() { + return static_cast<ChromeFrameAutomationProxy*>(&proxy_); + } + + inline void CreateTab() { + ASSERT_EQ(NULL, tab_.get()); + tab_ = new TabProxy(&dummy_sender_, tracker_.get(), tab_handle_); + } + + // Easy methods to set expectations. + void SetAutomationServerOk() { + EXPECT_CALL(factory_, GetAutomationServer(testing::Eq(timeout_), + testing::StrEq(profile_), + testing::_, + testing::_, + testing::NotNull())) + .Times(1) + .WillOnce(testing::DoAll( + testing::WithArgs<0, 4>( + testing::Invoke(CBF(&factory_, &MockProxyFactory::GetServerImpl, + get_proxy(), AUTOMATION_SUCCESS))), + testing::Return(id_))); + + EXPECT_CALL(factory_, ReleaseAutomationServer(testing::Eq(id_))).Times(1); + } + + void Set_CFD_LaunchFailed(AutomationLaunchResult result) { + EXPECT_CALL(cfd_, OnAutomationServerLaunchFailed( + testing::Eq(result), testing::_)) + .Times(1) + .WillOnce(QUIT_LOOP(loop_)); + } + + protected: + CFACMockTest() : tracker_(NULL), timeout_(500), + profile_(L"Adam.N.Epilinter") { + id_ = reinterpret_cast<void*>(5); + tab_handle_ = 3; + } + + virtual void SetUp() { + dummy_sender_.ForwardTo(&proxy_); + tracker_.reset(new AutomationHandleTracker(&dummy_sender_)); + + client_.reset(new ChromeFrameAutomationClient); + client_->set_proxy_factory(&factory_); + } +}; + +// Could be implemented as MockAutomationProxy member (we have WithArgs<>!) +ACTION_P3(HandleCreateTab, tab_handle, external_tab_container, tab_wnd) { + // arg0 - message + // arg1 - callback + // arg2 - key + CallbackRunner<Tuple3<HWND, HWND, int> >* c = + reinterpret_cast<CallbackRunner<Tuple3<HWND, HWND, int> >*>(arg1); + c->Run(external_tab_container, tab_wnd, tab_handle); + delete c; + delete arg0; +} + +TEST_F(CFACMockTest, MockedCreateTabOk) { + int timeout = 500; + CreateTab(); + SetAutomationServerOk(); + + EXPECT_CALL(proxy_, server_version()).Times(testing::AnyNumber()) + .WillRepeatedly(testing::Return("")); + + // We need some valid HWNDs, when responding to CreateExternalTab + HWND h1 = ::GetDesktopWindow(); + HWND h2 = ::GetDesktopWindow(); + EXPECT_CALL(proxy_, SendAsAsync(testing::Property(&IPC::SyncMessage::type, + AutomationMsg_CreateExternalTab__ID), + testing::NotNull(), testing::_)) + .Times(1) + .WillOnce(HandleCreateTab(tab_handle_, h1, h2)); + + EXPECT_CALL(proxy_, CreateTabProxy(testing::Eq(tab_handle_))) + .WillOnce(testing::Return(tab_)); + + EXPECT_CALL(cfd_, OnAutomationServerReady()) + .WillOnce(QUIT_LOOP(loop_)); + + // Here we go! + EXPECT_TRUE(client_->Initialize(&cfd_, timeout, false, profile_, L"", false)); + loop_.RunFor(10); + client_->Uninitialize(); +} + +TEST_F(CFACMockTest, MockedCreateTabFailed) { + HWND null_wnd = NULL; + SetAutomationServerOk(); + + EXPECT_CALL(proxy_, server_version()).Times(testing::AnyNumber()) + .WillRepeatedly(testing::Return("")); + + EXPECT_CALL(proxy_, SendAsAsync(testing::Property(&IPC::SyncMessage::type, + AutomationMsg_CreateExternalTab__ID), + testing::NotNull(), testing::_)) + .Times(1) + .WillOnce(HandleCreateTab(tab_handle_, null_wnd, null_wnd)); + + EXPECT_CALL(proxy_, CreateTabProxy(testing::_)).Times(0); + + Set_CFD_LaunchFailed(AUTOMATION_CREATE_TAB_FAILED); + + // Here we go! + EXPECT_TRUE(client_->Initialize(&cfd_, timeout_, false, profile_, L"", + false)); + loop_.RunFor(4); + client_->Uninitialize(); +} + +const wchar_t kMetaTagPage[] = L"files/meta_tag.html"; +TEST_F(ChromeFrameTestWithWebServer, FullTabModeIE_MetaTag) { + SimpleBrowserTest(IE, kMetaTagPage, L"meta_tag"); +} + +const wchar_t kCFProtocolPage[] = L"files/chrome_frame_protocol.html"; +TEST_F(ChromeFrameTestWithWebServer, FullTabModeIE_CFProtocol) { + SimpleBrowserTest(IE, kCFProtocolPage, L"chrome_frame_protocol"); +} + +const wchar_t kPersistentCookieTest[] = + L"files/persistent_cookie_test_page.html"; +TEST_F(ChromeFrameTestWithWebServer, FullTabModeIE_PersistentCookieTest) { + SimpleBrowserTest(IE, kPersistentCookieTest, L"PersistentCookieTest"); +} + +const wchar_t kNavigateOutPage[] = L"files/navigate_out.html"; +TEST_F(ChromeFrameTestWithWebServer, FullTabModeIE_NavigateOut) { + SimpleBrowserTest(IE, kNavigateOutPage, L"navigate_out"); +} + +HRESULT LaunchIEAsComServer(IWebBrowser2** web_browser) { + if (!web_browser) + return E_INVALIDARG; + + ScopedComPtr<IWebBrowser2> web_browser2; + HRESULT hr = CoCreateInstance( + CLSID_InternetExplorer, NULL, CLSCTX_LOCAL_SERVER, IID_IWebBrowser2, + reinterpret_cast<void**>(web_browser2.Receive())); + + if (SUCCEEDED(hr)) { + *web_browser = web_browser2.Detach(); + } + + return hr; +} + +TEST(ChromeFrameTest, FullTabModeIE_DisallowedUrls) { + int major_version = 0; + int minor_version = 0; + int bugfix_version = 0; + + base::SysInfo::OperatingSystemVersionNumbers(&major_version, &minor_version, + &bugfix_version); + if (major_version > 5) { + DLOG(INFO) << __FUNCTION__ << " Not running test on Windows version: " + << major_version; + return; + } + + IEVersion ie_version = GetIEVersion(); + if (ie_version == IE_8) { + DLOG(INFO) << __FUNCTION__ << " Not running test on IE8"; + return; + } + + HRESULT hr = CoInitialize(NULL); + bool should_uninit = SUCCEEDED(hr); + + ScopedComPtr<IWebBrowser2> web_browser2; + EXPECT_TRUE(S_OK == LaunchIEAsComServer(web_browser2.Receive())); + web_browser2->put_Visible(VARIANT_TRUE); + + CComObject<WebBrowserEventSink>* web_browser_sink = NULL; + CComObject<WebBrowserEventSink>::CreateInstance(&web_browser_sink); + + // Pass the main thread id to the browser sink so that it can notify + // us about test completion. + web_browser_sink->set_main_thread_id(GetCurrentThreadId()); + + hr = web_browser_sink->DispEventAdvise(web_browser2, + &DIID_DWebBrowserEvents2); + EXPECT_TRUE(hr == S_OK); + + VARIANT empty = ScopedVariant::kEmptyVariant; + ScopedVariant url; + url.Set(L"cf:file:///C:/"); + + TimedMsgLoop loop; + + hr = web_browser2->Navigate2(url.AsInput(), &empty, &empty, &empty, &empty); + EXPECT_TRUE(hr == S_OK); + + loop.RunFor(10); + + EXPECT_TRUE(web_browser_sink->navigation_failed()); + + hr = web_browser_sink->DispEventUnadvise(web_browser2); + EXPECT_TRUE(hr == S_OK); + + web_browser2.Release(); + chrome_frame_test::CloseAllIEWindows(); + + if (should_uninit) { + CoUninitialize(); + } +} + diff --git a/chrome_frame/test/chrome_frame_unittests.h b/chrome_frame/test/chrome_frame_unittests.h new file mode 100644 index 0000000..98b5985 --- /dev/null +++ b/chrome_frame/test/chrome_frame_unittests.h @@ -0,0 +1,164 @@ +// Copyright (c) 2006-2008 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. +#ifndef CHROME_FRAME_TEST_CHROME_FRAME_UNITTESTS_H_ +#define CHROME_FRAME_TEST_CHROME_FRAME_UNITTESTS_H_ + +#include <atlbase.h> +#include <atlcom.h> +#include <string> +#include <exdisp.h> +#include <exdispid.h> + +#include "base/ref_counted.h" +#include "base/scoped_handle_win.h" +#include "googleurl/src/gurl.h" +#include "chrome_frame/test/http_server.h" +#include "testing/gtest/include/gtest/gtest.h" + +// Class that: +// 1) Starts the local webserver, +// 2) Supports launching browsers - Internet Explorer and Firefox with local url +// 3) Wait the webserver to finish. It is supposed the test webpage to shutdown +// the server by navigating to "kill" page +// 4) Supports read the posted results from the test webpage to the "dump" +// webserver directory +class ChromeFrameTestWithWebServer: public testing::Test { + protected: + enum BrowserKind { INVALID, IE, FIREFOX, OPERA, SAFARI, CHROME }; + + bool LaunchBrowser(BrowserKind browser, const wchar_t* url); + bool WaitForTestToComplete(int milliseconds); + // Waits for the page to notify us of the window.onload event firing. + // Note that the milliseconds value is only approximate. + bool WaitForOnLoad(int milliseconds); + bool ReadResultFile(const std::wstring& file_name, std::string* data); + + // Launches the specified browser and waits for the test to complete + // (see WaitForTestToComplete). Then checks that the outcome is OK. + // This function uses EXPECT_TRUE and ASSERT_TRUE for all steps performed + // hence no return value. + void SimpleBrowserTest(BrowserKind browser, const wchar_t* page, + const wchar_t* result_file_to_check); + + // Same as SimpleBrowserTest but if the browser isn't installed (LaunchBrowser + // fails), the function will print out a warning but not treat the test + // as failed. + // Currently this is how we run Opera tests. + void OptionalBrowserTest(BrowserKind browser, const wchar_t* page, + const wchar_t* result_file_to_check); + + // Test if chrome frame correctly reports its version. + void VersionTest(BrowserKind browser, const wchar_t* page, + const wchar_t* result_file_to_check); + + void CloseBrowser(); + + // Ensures (well, at least tries to ensure) that the browser window has focus. + bool BringBrowserToTop(); + + // Returns true iff the specified result file contains 'expected result'. + bool CheckResultFile(const std::wstring& file_name, + const std::string& expected_result); + + virtual void SetUp(); + virtual void TearDown(); + + // Important: kind means "sheep" in Icelandic. ?:-o + const char* ToString(BrowserKind kind) { + switch (kind) { + case IE: + return "IE"; + case FIREFOX: + return "Firefox"; + case OPERA: + return "Opera"; + case CHROME: + return "Chrome"; + case SAFARI: + return "Safari"; + default: + NOTREACHED(); + break; + } + return ""; + } + + BrowserKind browser_; + std::wstring results_dir_; + ScopedHandle browser_handle_; + ChromeFrameHTTPServer server_; +}; + +// This class sets up event sinks to the IWebBrowser interface. Currently it +// subscribes to the following events:- +// 1. DISPID_BEFORENAVIGATE2 +// 2. DISPID_NAVIGATEERROR +// 3. DISPID_NAVIGATECOMPLETE2 +// Other events can be subscribed to on an if needed basis. +class WebBrowserEventSink + : public CComObjectRootEx<CComSingleThreadModel>, + public IDispEventSimpleImpl<0, WebBrowserEventSink, + &DIID_DWebBrowserEvents2> { + public: + WebBrowserEventSink() + : navigation_failed_(false), + main_thread_id_(0) { + } + +BEGIN_COM_MAP(WebBrowserEventSink) +END_COM_MAP() + +BEGIN_SINK_MAP(WebBrowserEventSink) + SINK_ENTRY_INFO(0, DIID_DWebBrowserEvents2, DISPID_BEFORENAVIGATE2, + OnBeforeNavigate2, &kBeforeNavigate2Info) + SINK_ENTRY_INFO(0, DIID_DWebBrowserEvents2, DISPID_NAVIGATECOMPLETE2, + OnNavigateComplete2, &kNavigateComplete2Info) + SINK_ENTRY_INFO(0, DIID_DWebBrowserEvents2, DISPID_NAVIGATEERROR, + OnNavigateError, &kNavigateErrorInfo) +END_SINK_MAP() + + STDMETHOD_(void, OnNavigateError)(IDispatch* dispatch, VARIANT* url, + VARIANT* frame_name, VARIANT* status_code, + VARIANT* cancel) { + navigation_failed_ = true; + } + + STDMETHOD(OnBeforeNavigate2)(IDispatch* dispatch, VARIANT* url, VARIANT* + flags, VARIANT* target_frame_name, + VARIANT* post_data, VARIANT* headers, + VARIANT_BOOL* cancel) { + DLOG(INFO) << __FUNCTION__; + // If a navigation fails then IE issues a navigation to an interstitial + // page. Catch this to track navigation errors as the NavigateError + // notification does not seem to fire reliably. + GURL crack_url(url->bstrVal); + if (crack_url.scheme() == "res") { + navigation_failed_ = true; + } + return S_OK; + } + + STDMETHOD_(void, OnNavigateComplete2)(IDispatch* dispatch, VARIANT* url) { + DLOG(INFO) << __FUNCTION__; + } + + bool navigation_failed() const { + return navigation_failed_; + } + + void set_main_thread_id(DWORD thread_id) { + main_thread_id_ = thread_id; + } + + protected: + bool navigation_failed_; + + static _ATL_FUNC_INFO kBeforeNavigate2Info; + static _ATL_FUNC_INFO kNavigateComplete2Info; + static _ATL_FUNC_INFO kNavigateErrorInfo; + DWORD main_thread_id_; +}; + +#endif // CHROME_FRAME_TEST_CHROME_FRAME_UNITTESTS_H_ + diff --git a/chrome_frame/test/chrometab_unittests.vsprops b/chrome_frame/test/chrometab_unittests.vsprops new file mode 100644 index 0000000..d5375c9 --- /dev/null +++ b/chrome_frame/test/chrometab_unittests.vsprops @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioPropertySheet
+ ProjectType="Visual C++"
+ Version="8.00"
+ Name="chrometab_unittests"
+ InheritedPropertySheets="$(SolutionDir)..\tools\grit\build\using_generated_resources.vsprops"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ PreprocessorDefinitions="_ATL_APARTMENT_THREADED;_ATL_CSTRING_EXPLICIT_CONSTRUCTORS"
+ AdditionalIncludeDirectories=""
+ />
+
+</VisualStudioPropertySheet>
diff --git a/chrome_frame/test/com_message_event_unittest.cc b/chrome_frame/test/com_message_event_unittest.cc new file mode 100644 index 0000000..f850c20 --- /dev/null +++ b/chrome_frame/test/com_message_event_unittest.cc @@ -0,0 +1,325 @@ +// Copyright (c) 2009 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.
+
+#include "chrome_frame/com_message_event.h"
+
+#include <atlbase.h>
+#include <atlcom.h>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+// To allow the unit test read-only access to check protected member variables.
+class FriendlyComMessageEvent : public ComMessageEvent {
+ public:
+ inline IHTMLEventObj* basic_event() { return basic_event_; }
+};
+
+class ATL_NO_VTABLE MockDumbContainer :
+ public CComObjectRoot,
+ public IOleContainer {
+ public:
+ DECLARE_NOT_AGGREGATABLE(MockDumbContainer)
+ BEGIN_COM_MAP(MockDumbContainer)
+ COM_INTERFACE_ENTRY(IParseDisplayName)
+ COM_INTERFACE_ENTRY(IOleContainer)
+ END_COM_MAP()
+
+ STDMETHOD(ParseDisplayName)(IBindCtx*, LPOLESTR, ULONG*, IMoniker**) {
+ NOTREACHED();
+ return E_NOTIMPL;
+ }
+ STDMETHOD(EnumObjects)(DWORD, IEnumUnknown**) {
+ NOTREACHED();
+ return E_NOTIMPL;
+ }
+ STDMETHOD(LockContainer)(BOOL) {
+ NOTREACHED();
+ return E_NOTIMPL;
+ }
+};
+
+TEST(ComMessageEvent, WithDumbContainer) {
+ CComObject<MockDumbContainer>* container_obj = NULL;
+ CComObject<MockDumbContainer>::CreateInstance(&container_obj);
+ ScopedComPtr<IOleContainer> container(container_obj);
+ EXPECT_FALSE(!container);
+
+ CComObject<FriendlyComMessageEvent>* event_obj = NULL;
+ CComObject<FriendlyComMessageEvent>::CreateInstance(&event_obj);
+ ScopedComPtr<IUnknown> event_ref(event_obj);
+
+ bool result = event_obj->Initialize(container, "hi", "http://www.foo.com/",
+ "message");
+ EXPECT_TRUE(result);
+ EXPECT_TRUE(!event_obj->basic_event());
+}
+
+// Mock object to mimic a "smart" container, e.g. IE, that will
+// be able to return an IHTMLDocument2 and 4, and from which you
+// can get an IHTMLEventObj implementation. Doubles as a mock
+// IHTMLEventObj implementation.
+class ATL_NO_VTABLE MockSmartContainer :
+ public CComObjectRoot,
+ public IOleContainer,
+ public IHTMLDocument2,
+ public IHTMLDocument4,
+ public IHTMLEventObj {
+ public:
+ DECLARE_NOT_AGGREGATABLE(MockSmartContainer)
+ BEGIN_COM_MAP(MockSmartContainer)
+ COM_INTERFACE_ENTRY_IID(IID_IDispatch, IHTMLEventObj)
+ COM_INTERFACE_ENTRY(IParseDisplayName)
+ COM_INTERFACE_ENTRY(IOleContainer)
+ COM_INTERFACE_ENTRY(IHTMLDocument)
+ COM_INTERFACE_ENTRY(IHTMLDocument2)
+ COM_INTERFACE_ENTRY(IHTMLDocument4)
+ COM_INTERFACE_ENTRY(IHTMLEventObj)
+ END_COM_MAP()
+
+ static const DISPID kDispId = 424242;
+ static const long kResultValue = 42;
+
+ // Only method we actually implement from IHTMLDocument4, to give
+ // out the mock IHTMLEventObj.
+ STDMETHOD(createEventObject)(VARIANT*, IHTMLEventObj** event_obj) {
+ return GetUnknown()->QueryInterface(event_obj);
+ }
+
+ // Dummy IDispatch implementation for unit testing, to validate
+ // passthrough semantics.
+ STDMETHOD(GetIDsOfNames)(REFIID iid, LPOLESTR* names, UINT num_names,
+ LCID lcid, DISPID* disp_ids) {
+ DCHECK(num_names == 1);
+ disp_ids[0] = kDispId;
+ return S_OK;
+ }
+
+ STDMETHOD(Invoke)(DISPID id, REFIID iid, LCID lcid, WORD flags,
+ DISPPARAMS* disp_params, VARIANT* var_result,
+ EXCEPINFO* excep_info, UINT* arg_error) {
+ var_result->vt = VT_I4;
+ var_result->lVal = kResultValue;
+ return S_OK;
+ }
+
+
+ // Do-nothing implementation of the rest of the interface methods.
+ // To make this less verbose, define a macro here and undefine it
+ // at the end of the list.
+#define STDMETHODNOTIMP(method, parameters) \
+ STDMETHOD(method) parameters { \
+ NOTREACHED(); \
+ return E_NOTIMPL; \
+ }
+
+ // IDispatch
+ STDMETHODNOTIMP(GetTypeInfoCount, (UINT*));
+ STDMETHODNOTIMP(GetTypeInfo, (UINT, LCID, ITypeInfo**));
+
+ // IParseDisplayName
+ STDMETHODNOTIMP(ParseDisplayName, (IBindCtx*, LPOLESTR, ULONG*, IMoniker**));
+ // IOleContainer
+ STDMETHODNOTIMP(EnumObjects, (DWORD, IEnumUnknown**));
+ STDMETHODNOTIMP(LockContainer, (BOOL));
+ // IHTMLDocument
+ STDMETHODNOTIMP(get_Script, (IDispatch**));
+ // IHTMLDocument2
+ STDMETHODNOTIMP(get_all, (IHTMLElementCollection**));
+ STDMETHODNOTIMP(get_body, (IHTMLElement**));
+ STDMETHODNOTIMP(get_activeElement, (IHTMLElement**));
+ STDMETHODNOTIMP(get_images, (IHTMLElementCollection**));
+ STDMETHODNOTIMP(get_applets, (IHTMLElementCollection**));
+ STDMETHODNOTIMP(get_links, (IHTMLElementCollection**));
+ STDMETHODNOTIMP(get_forms, (IHTMLElementCollection**));
+ STDMETHODNOTIMP(get_anchors, (IHTMLElementCollection**));
+ STDMETHODNOTIMP(put_title, (BSTR));
+ STDMETHODNOTIMP(get_title, (BSTR*));
+ STDMETHODNOTIMP(get_scripts, (IHTMLElementCollection**));
+ STDMETHODNOTIMP(put_designMode, (BSTR));
+ STDMETHODNOTIMP(get_designMode, (BSTR*));
+ STDMETHODNOTIMP(get_selection, (IHTMLSelectionObject**));
+ STDMETHODNOTIMP(get_readyState, (BSTR*));
+ STDMETHODNOTIMP(get_frames, (IHTMLFramesCollection2**));
+ STDMETHODNOTIMP(get_embeds, (IHTMLElementCollection**));
+ STDMETHODNOTIMP(get_plugins, (IHTMLElementCollection**));
+ STDMETHODNOTIMP(put_alinkColor, (VARIANT));
+ STDMETHODNOTIMP(get_alinkColor, (VARIANT*));
+ STDMETHODNOTIMP(put_bgColor, (VARIANT));
+ STDMETHODNOTIMP(get_bgColor, (VARIANT*));
+ STDMETHODNOTIMP(put_fgColor, (VARIANT));
+ STDMETHODNOTIMP(get_fgColor, (VARIANT*));
+ STDMETHODNOTIMP(put_linkColor, (VARIANT));
+ STDMETHODNOTIMP(get_linkColor, (VARIANT*));
+ STDMETHODNOTIMP(put_vlinkColor, (VARIANT));
+ STDMETHODNOTIMP(get_vlinkColor, (VARIANT*));
+ STDMETHODNOTIMP(get_referrer, (BSTR*));
+ STDMETHODNOTIMP(get_location, (IHTMLLocation**));
+ STDMETHODNOTIMP(get_lastModified, (BSTR*));
+ STDMETHODNOTIMP(put_URL, (BSTR));
+ STDMETHODNOTIMP(get_URL, (BSTR*));
+ STDMETHODNOTIMP(put_domain, (BSTR));
+ STDMETHODNOTIMP(get_domain, (BSTR*));
+ STDMETHODNOTIMP(put_cookie, (BSTR));
+ STDMETHODNOTIMP(get_cookie, (BSTR*));
+ STDMETHODNOTIMP(put_expando, (VARIANT_BOOL));
+ STDMETHODNOTIMP(get_expando, (VARIANT_BOOL*));
+ STDMETHODNOTIMP(put_charset, (BSTR));
+ STDMETHODNOTIMP(get_charset, (BSTR*));
+ STDMETHODNOTIMP(put_defaultCharset, (BSTR));
+ STDMETHODNOTIMP(get_defaultCharset, (BSTR*));
+ STDMETHODNOTIMP(get_mimeType, (BSTR*));
+ STDMETHODNOTIMP(get_fileSize, (BSTR*));
+ STDMETHODNOTIMP(get_fileCreatedDate, (BSTR*));
+ STDMETHODNOTIMP(get_fileModifiedDate, (BSTR*));
+ STDMETHODNOTIMP(get_fileUpdatedDate, (BSTR*));
+ STDMETHODNOTIMP(get_security, (BSTR*));
+ STDMETHODNOTIMP(get_protocol, (BSTR*));
+ STDMETHODNOTIMP(get_nameProp, (BSTR*));
+ STDMETHODNOTIMP(write, (SAFEARRAY*));
+ STDMETHODNOTIMP(writeln, (SAFEARRAY*));
+ STDMETHODNOTIMP(open, (BSTR, VARIANT, VARIANT, VARIANT, IDispatch**));
+ STDMETHODNOTIMP(close, ());
+ STDMETHODNOTIMP(clear, ());
+ STDMETHODNOTIMP(queryCommandSupported, (BSTR, VARIANT_BOOL*));
+ STDMETHODNOTIMP(queryCommandEnabled, (BSTR, VARIANT_BOOL*));
+ STDMETHODNOTIMP(queryCommandState, (BSTR, VARIANT_BOOL*));
+ STDMETHODNOTIMP(queryCommandIndeterm, (BSTR, VARIANT_BOOL*));
+ STDMETHODNOTIMP(queryCommandText, (BSTR, BSTR*));
+ STDMETHODNOTIMP(queryCommandValue, (BSTR, VARIANT*));
+ STDMETHODNOTIMP(execCommand, (BSTR, VARIANT_BOOL, VARIANT, VARIANT_BOOL*));
+ STDMETHODNOTIMP(execCommandShowHelp, (BSTR, VARIANT_BOOL*));
+ STDMETHODNOTIMP(createElement, (BSTR, IHTMLElement**));
+ STDMETHODNOTIMP(put_onhelp, (VARIANT));
+ STDMETHODNOTIMP(get_onhelp, (VARIANT*));
+ STDMETHODNOTIMP(put_onclick, (VARIANT));
+ STDMETHODNOTIMP(get_onclick, (VARIANT*));
+ STDMETHODNOTIMP(put_ondblclick, (VARIANT));
+ STDMETHODNOTIMP(get_ondblclick, (VARIANT*));
+ STDMETHODNOTIMP(put_onkeyup, (VARIANT));
+ STDMETHODNOTIMP(get_onkeyup, (VARIANT*));
+ STDMETHODNOTIMP(put_onkeydown, (VARIANT));
+ STDMETHODNOTIMP(get_onkeydown, (VARIANT*));
+ STDMETHODNOTIMP(put_onkeypress, (VARIANT));
+ STDMETHODNOTIMP(get_onkeypress, (VARIANT*));
+ STDMETHODNOTIMP(put_onmouseup, (VARIANT));
+ STDMETHODNOTIMP(get_onmouseup, (VARIANT*));
+ STDMETHODNOTIMP(put_onmousedown, (VARIANT));
+ STDMETHODNOTIMP(get_onmousedown, (VARIANT*));
+ STDMETHODNOTIMP(put_onmousemove, (VARIANT));
+ STDMETHODNOTIMP(get_onmousemove, (VARIANT*));
+ STDMETHODNOTIMP(put_onmouseout, (VARIANT));
+ STDMETHODNOTIMP(get_onmouseout, (VARIANT*));
+ STDMETHODNOTIMP(put_onmouseover, (VARIANT));
+ STDMETHODNOTIMP(get_onmouseover, (VARIANT*));
+ STDMETHODNOTIMP(put_onreadystatechange, (VARIANT));
+ STDMETHODNOTIMP(get_onreadystatechange, (VARIANT*));
+ STDMETHODNOTIMP(put_onafterupdate, (VARIANT));
+ STDMETHODNOTIMP(get_onafterupdate, (VARIANT*));
+ STDMETHODNOTIMP(put_onrowexit, (VARIANT));
+ STDMETHODNOTIMP(get_onrowexit, (VARIANT*));
+ STDMETHODNOTIMP(put_onrowenter, (VARIANT));
+ STDMETHODNOTIMP(get_onrowenter, (VARIANT*));
+ STDMETHODNOTIMP(put_ondragstart, (VARIANT));
+ STDMETHODNOTIMP(get_ondragstart, (VARIANT*));
+ STDMETHODNOTIMP(put_onselectstart, (VARIANT));
+ STDMETHODNOTIMP(get_onselectstart, (VARIANT*));
+ STDMETHODNOTIMP(elementFromPoint, (long, long, IHTMLElement**));
+ STDMETHODNOTIMP(get_parentWindow, (IHTMLWindow2**));
+ STDMETHODNOTIMP(get_styleSheets, (IHTMLStyleSheetsCollection**));
+ STDMETHODNOTIMP(put_onbeforeupdate, (VARIANT));
+ STDMETHODNOTIMP(get_onbeforeupdate, (VARIANT*));
+ STDMETHODNOTIMP(put_onerrorupdate, (VARIANT));
+ STDMETHODNOTIMP(get_onerrorupdate, (VARIANT*));
+ STDMETHODNOTIMP(toString, (BSTR*));
+ STDMETHODNOTIMP(createStyleSheet, (BSTR, long, IHTMLStyleSheet**));
+ // IHTMLDocument4
+ STDMETHODNOTIMP(focus, ());
+ STDMETHODNOTIMP(hasFocus, (VARIANT_BOOL*));
+ STDMETHODNOTIMP(put_onselectionchange, (VARIANT));
+ STDMETHODNOTIMP(get_onselectionchange, (VARIANT*));
+ STDMETHODNOTIMP(get_namespaces, (IDispatch**));
+ STDMETHODNOTIMP(createDocumentFromUrl, (BSTR, BSTR, IHTMLDocument2**));
+ STDMETHODNOTIMP(put_media, (BSTR));
+ STDMETHODNOTIMP(get_media, (BSTR*));
+ STDMETHODNOTIMP(fireEvent, (BSTR, VARIANT*, VARIANT_BOOL*));
+ STDMETHODNOTIMP(createRenderStyle, (BSTR, IHTMLRenderStyle**));
+ STDMETHODNOTIMP(put_oncontrolselect, (VARIANT));
+ STDMETHODNOTIMP(get_oncontrolselect, (VARIANT*));
+ STDMETHODNOTIMP(get_URLUnencoded, (BSTR*));
+ // IHTMLEventObj
+ STDMETHODNOTIMP(get_srcElement, (IHTMLElement**))
+ STDMETHODNOTIMP(get_altKey, (VARIANT_BOOL*));
+ STDMETHODNOTIMP(get_ctrlKey, (VARIANT_BOOL*));
+ STDMETHODNOTIMP(get_shiftKey, (VARIANT_BOOL*));
+ STDMETHODNOTIMP(put_returnValue, (VARIANT));
+ STDMETHODNOTIMP(get_returnValue, (VARIANT*));
+ STDMETHODNOTIMP(put_cancelBubble, (VARIANT_BOOL));
+ STDMETHODNOTIMP(get_cancelBubble, (VARIANT_BOOL*));
+ STDMETHODNOTIMP(get_fromElement, (IHTMLElement**));
+ STDMETHODNOTIMP(get_toElement, (IHTMLElement**));
+ STDMETHODNOTIMP(put_keyCode, (long));
+ STDMETHODNOTIMP(get_keyCode, (long*));
+ STDMETHODNOTIMP(get_button, (long*));
+ STDMETHODNOTIMP(get_type, (BSTR*));
+ STDMETHODNOTIMP(get_qualifier, (BSTR*));
+ STDMETHODNOTIMP(get_reason, (long*));
+ STDMETHODNOTIMP(get_x, (long*));
+ STDMETHODNOTIMP(get_y, (long*));
+ STDMETHODNOTIMP(get_clientX, (long*));
+ STDMETHODNOTIMP(get_clientY, (long*));
+ STDMETHODNOTIMP(get_offsetX, (long*));
+ STDMETHODNOTIMP(get_offsetY, (long*));
+ STDMETHODNOTIMP(get_screenX, (long*));
+ STDMETHODNOTIMP(get_screenY, (long*));
+ STDMETHODNOTIMP(get_srcFilter, (IDispatch**));
+#undef STDMETHODNOTIMP
+};
+
+TEST(ComMessageEvent, WithSmartContainer) {
+ CComObject<MockSmartContainer>* container_obj = NULL;
+ CComObject<MockSmartContainer>::CreateInstance(&container_obj);
+ ScopedComPtr<IOleContainer> container(container_obj);
+ EXPECT_FALSE(!container);
+
+ CComObject<FriendlyComMessageEvent>* event_obj = NULL;
+ CComObject<FriendlyComMessageEvent>::CreateInstance(&event_obj);
+ ScopedComPtr<IUnknown> event_ref(event_obj);
+
+ bool succeeded = event_obj->Initialize(container, "hi",
+ "http://www.foo.com/", "message");
+ EXPECT_TRUE(succeeded);
+ EXPECT_FALSE(!event_obj->basic_event());
+
+ // Name handled natively by CF's ComMessageEvent.
+ DISPID dispid = -1;
+ LPOLESTR name = L"data";
+ HRESULT hr = event_obj->GetIDsOfNames(IID_IDispatch, &name, 1,
+ LOCALE_USER_DEFAULT, &dispid);
+ EXPECT_HRESULT_SUCCEEDED(hr);
+ EXPECT_EQ(dispid, ComMessageEvent::DISPID_MESSAGE_EVENT_DATA);
+
+ // Name not handled by CF's ComMessageEvent.
+ dispid = -1;
+ name = L"nothandledatallbyanyone";
+ hr = event_obj->GetIDsOfNames(IID_IDispatch, &name, 1,
+ LOCALE_USER_DEFAULT, &dispid);
+ EXPECT_HRESULT_SUCCEEDED(hr);
+ EXPECT_EQ(dispid, MockSmartContainer::kDispId);
+
+ // Invoke function handled by ComMessageEvent.
+ CComDispatchDriver dispatcher(event_obj);
+ CComVariant result;
+ hr = dispatcher.GetProperty(ComMessageEvent::DISPID_MESSAGE_EVENT_DATA,
+ &result);
+ EXPECT_HRESULT_SUCCEEDED(hr);
+ EXPECT_EQ(result.vt, VT_BSTR);
+ EXPECT_EQ(wcscmp(result.bstrVal, L"hi"), 0);
+
+ // And now check passthrough.
+ result.Clear();
+ hr = dispatcher.GetProperty(MockSmartContainer::kDispId, &result);
+ EXPECT_HRESULT_SUCCEEDED(hr);
+ EXPECT_EQ(result.vt, VT_I4);
+ EXPECT_EQ(result.lVal, MockSmartContainer::kResultValue);
+}
diff --git a/chrome_frame/test/data/CFInstance_basic_frame.html b/chrome_frame/test/data/CFInstance_basic_frame.html new file mode 100644 index 0000000..11938ee --- /dev/null +++ b/chrome_frame/test/data/CFInstance_basic_frame.html @@ -0,0 +1,8 @@ +<html> + <head> + <title></title> + </head> + <body> + <h1>do nothing</h1> + </body> +</html> diff --git a/chrome_frame/test/data/CFInstance_basic_host.html b/chrome_frame/test/data/CFInstance_basic_host.html new file mode 100644 index 0000000..6bad61c --- /dev/null +++ b/chrome_frame/test/data/CFInstance_basic_host.html @@ -0,0 +1,55 @@ +<html> + <!-- This page is meant to loaded inside the host browser (IE, FF, etc.) --> + <head> + <script type="text/javascript" + src="chrome_frame_tester_helpers.js"></script> + <script type="text/javascript" + src="CFInstance.js"></script> + </head> + + <body> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + + <div id="parent"> + <div id="prev">before</div><div id="toBeReplaced"> + fallback content goes here + </div><div id="after">after</div> + </div> + <script type="text/javascript"> + var testName = "CFInstanceBasic"; + (function(){ + try{ + var cf = new CFInstance({ + src: "CFInstance_basic_frame.html", + node: "toBeReplaced" + }); + + if (document.getElementById("parent") != cf.parentNode ) { + onFailure(testName, 1, "parent node mismatch"); + return; + } + + if (document.getElementById("prev").nextSibling != cf) { + onFailure(testName, 1, "sibling node mismatch"); + return; + } + + if (document.getElementById("after").previousSibling != cf) { + onFailure(testName, 1, "sibling node mismatch"); + return; + } + + onSuccess(testName, 1); + + } catch (e) { + onFailure(testName, 1, + "CFInstance constructor failed with error: "+e); + } + })(); + </script> + <p>Tests ChromeFrame Navigation</p> + </body> +</html> + diff --git a/chrome_frame/test/data/CFInstance_default_ctor_host.html b/chrome_frame/test/data/CFInstance_default_ctor_host.html new file mode 100644 index 0000000..744c6de --- /dev/null +++ b/chrome_frame/test/data/CFInstance_default_ctor_host.html @@ -0,0 +1,45 @@ +<html> + <!-- This page is meant to loaded inside the host browser (IE, FF, etc.) --> + <head> + <script type="text/javascript" + src="chrome_frame_tester_helpers.js"></script> + <script type="text/javascript" + src="CFInstance.js"></script> + </head> + + <body> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + + <div id="parent"> + <div id="prev">before</div><div id="toBeReplaced"> + fallback content goes here + </div><div id="after">after</div> + </div> + <script type="text/javascript"> + var testName = "CFInstanceDefaultCtor"; + (function(){ + try{ + var cf = new CFInstance(); + cf.src = "CFInstance_basic_frame.html"; + var node = document.getElementById("toBeReplaced"); + node.parentNode.replaceChild(cf, node); + var timer = setTimeout(function() { + onFailure(testName, 1, "CFInstance navigation timeout"); + }, 15000); + cf.listen("load", function() { + clearTimeout(timer); + onSuccess(testName, 1); + }); + cf.src = "CFInstance_basic_frame.html"; + } catch (e) { + onFailure(testName, 1, + "CFInstance constructor failed with error: "+e); + } + })(); + </script> + <p>Tests Chrome Frame constructor without arguments</p> + </body> +</html> + diff --git a/chrome_frame/test/data/CFInstance_delay_host.html b/chrome_frame/test/data/CFInstance_delay_host.html new file mode 100644 index 0000000..f6d9e02 --- /dev/null +++ b/chrome_frame/test/data/CFInstance_delay_host.html @@ -0,0 +1,47 @@ +<html> + <!-- This page is meant to loaded inside the host browser (IE, FF, etc.) --> + <head> + <script type="text/javascript" + src="chrome_frame_tester_helpers.js"></script> + <script type="text/javascript" + src="CFInstance.js"></script> + </head> + + <body> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + + <div id="parent"> + <div id="prev">before</div><div id="toBeReplaced"> + fallback content goes here + </div><div id="after">after</div> + </div> + <script type="text/javascript"> + var testName = "CFInstanceDelay"; + (function(){ + try{ + var cf = new CFInstance({ + onload: function() { + onSuccess(testName, 1); + }, + src: "CFInstance_basic_frame.html" + }); + + setTimeout(function() { + var replNode = document.getElementById("toBeReplaced"); + // impedence matching between new and old CFInstance.js + var node = cf["plugin"] ? cf.plugin : cf; + replNode.parentNode.replaceChild(node, replNode); + }, 100); + + } catch (e) { + onFailure(testName, 1, + "CFInstance constructor failed with error: "+e); + } + })(); + </script> + <p>Tests ChromeFrame Navigation when placed in the document on a delay</p> + </body> +</html> + diff --git a/chrome_frame/test/data/CFInstance_fallback_host.html b/chrome_frame/test/data/CFInstance_fallback_host.html new file mode 100644 index 0000000..5939180 --- /dev/null +++ b/chrome_frame/test/data/CFInstance_fallback_host.html @@ -0,0 +1,44 @@ +<html> + <!-- This page is meant to loaded inside the host browser (IE, FF, etc.) --> + <head> + <script type="text/javascript" + src="chrome_frame_tester_helpers.js"></script> + <script type="text/javascript" + src="CFInstance.js"></script> + </head> + + <body> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + + <div id="parent"> + <div id="prev">before</div><div id="toBeReplaced"> + fallback content goes here + </div><div id="after">after</div> + </div> + <script type="text/javascript"> + var testName = "CFInstanceFallback"; + (function() { + try { + var cf = new CFInstance({ + node: "toBeReplaced", + src: "CFInstance_basic_frame.html", + requirements: [] + }); + + if (cf.tagName.toLowerCase() == "iframe") { + onSuccess(testName, 1); + } else { + onFailure(testName, 1, "expected tagName mismatch"); + } + } catch (e) { + onFailure(testName, 1, + "CFInstance constructor failed with error: "+e); + } + })(); + </script> + <p>Tests ChromeFrame fallback</p> + </body> +</html> + diff --git a/chrome_frame/test/data/CFInstance_iframe_onload_host.html b/chrome_frame/test/data/CFInstance_iframe_onload_host.html new file mode 100644 index 0000000..913c462 --- /dev/null +++ b/chrome_frame/test/data/CFInstance_iframe_onload_host.html @@ -0,0 +1,41 @@ +<html> + <!-- This page is meant to loaded inside the host browser (IE, FF, etc.) --> + <head> + <script type="text/javascript" + src="chrome_frame_tester_helpers.js"></script> + <script type="text/javascript" + src="CFInstance.js"></script> + </head> + + <body> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + + <div id="parent"> + <div id="prev">before</div><div id="toBeReplaced"> + fallback content goes here + </div><div id="after">after</div> + </div> + <script type="text/javascript"> + var testName = "CFInstanceIfrOnload"; + (function() { + try { + var cf = new CFInstance({ + node: "toBeReplaced", + src: "CFInstance_basic_frame.html", + onload: function() { + onSuccess(testName, 1); + }, + requirements: [] // always use an iframe + }); + } catch (e) { + onFailure(testName, 1, + "CFInstance constructor failed with error: "+e); + } + })(); + </script> + <p>Tests CFInstance event handling on iframes</p> + </body> +</html> + diff --git a/chrome_frame/test/data/CFInstance_iframe_post_host.html b/chrome_frame/test/data/CFInstance_iframe_post_host.html new file mode 100644 index 0000000..f503522 --- /dev/null +++ b/chrome_frame/test/data/CFInstance_iframe_post_host.html @@ -0,0 +1,50 @@ +<html> + <!-- This page is meant to loaded inside the host browser (IE, FF, etc.) --> + <head> + <script type="text/javascript" + src="chrome_frame_tester_helpers.js"></script> + <script type="text/javascript" + src="CFInstance.js"></script> + </head> + + <body> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + + <div id="parent"> + <div id="prev">before</div><div id="toBeReplaced"> + fallback content goes here + </div><div id="after">after</div> + </div> + <script type="text/javascript"> + var testName = "CFInstanceIfrPost"; + (function() { + if (!CFInstance.contentTests.postMessage()) { + onSuccess(testName, 1); + } else { + try { + var cf = new CFInstance({ + node: "toBeReplaced", + src: "CFInstance_post_frame.html", + onload: function() { + cf.postMessage("howdy!"); + }, + onmessage: function(evt) { + if (evt.data == 'hola!') { + onSuccess(testName, 1); + } + }, + requirements: [] // always use an iframe + }); + } catch (e) { + onFailure(testName, 1, + "CFInstance constructor failed with error: "+e); + } + } + })(); + </script> + <p>Tests CFInstance event handling on iframes</p> + </body> +</html> + diff --git a/chrome_frame/test/data/CFInstance_no_src_host.html b/chrome_frame/test/data/CFInstance_no_src_host.html new file mode 100644 index 0000000..379e26d --- /dev/null +++ b/chrome_frame/test/data/CFInstance_no_src_host.html @@ -0,0 +1,43 @@ +<html> + <!-- This page is meant to loaded inside the host browser (IE, FF, etc.) --> + <head> + <script type="text/javascript" + src="chrome_frame_tester_helpers.js"></script> + <script type="text/javascript" + src="CFInstance.js"></script> + </head> + + <body> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + + <div id="parent"> + <div id="prev">before</div><div id="toBeReplaced"> + fallback content goes here + </div><div id="after">after</div> + </div> + <script type="text/javascript"> + var testName = "CFInstanceNoSrc"; + (function() { + try { + var cf = new CFInstance({ + src: "", + node: "toBeReplaced" + }); + + // check that we got "src" set to "about:blank" by CFInstance + if (cf.src == "about:blank") { + onSuccess(testName, 1); + } else { + onFailure(testName, 1, "blank URL mismatch"); + } + } catch (e) { + onFailure(testName, 1, + "CFInstance constructor failed with error: "+e); + } + })(); + </script> + <p>Tests ChromeFrame with blank src</p> + </body> +</html> diff --git a/chrome_frame/test/data/CFInstance_post_frame.html b/chrome_frame/test/data/CFInstance_post_frame.html new file mode 100644 index 0000000..8055cb2 --- /dev/null +++ b/chrome_frame/test/data/CFInstance_post_frame.html @@ -0,0 +1,26 @@ +<!-- saved from url=(0014)about:internet --> +<html> +<!-- This page is meant to load inside ChromeFrame --> + <head> + <script type="text/javascript" + src="chrome_frame_tester_helpers.js"></script> + <script type="text/javascript" + src="CFInstance.js"></script> + <script> + var cf = CFInstance; + + cf.listen("load", function() { + cf.postMessage("hola!"); + }) + </script> + </head> + + <body> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + + <p>ChromeFrame + CFInstance PostMessage Test + <br>Test for PostMessage from the host browser to iframe and back</p> + </body> +</html> diff --git a/chrome_frame/test/data/CFInstance_post_host.html b/chrome_frame/test/data/CFInstance_post_host.html new file mode 100644 index 0000000..09402ac --- /dev/null +++ b/chrome_frame/test/data/CFInstance_post_host.html @@ -0,0 +1,47 @@ +<!DOCTYPE html> + +<html> + <!-- This page is meant to loaded inside the host browser (IE, FF, etc.) --> + <head> + <script type="text/javascript" + src="chrome_frame_tester_helpers.js"></script> + <script type="text/javascript" + src="CFInstance.js"></script> + </head> + + <body> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + + <div id="parent"> + <div id="prev">before</div><div id="toBeReplaced"> + fallback content goes here + </div><div id="after">after</div> + </div> + <script type="text/javascript"> + var testName = "CFInstancePost"; + (function() { + try { + var cf = new CFInstance({ + node: "toBeReplaced", + src: "CFInstance_post_frame.html", + onload: function() { + cf.postMessage("howdy!"); + }, + onmessage: function(evt) { + if (evt.data == "hola!") { + onSuccess(testName, 1); + } + } + }); + } catch (e) { + onFailure(testName, 1, + "CFInstance constructor failed with error: " + e); + } + })(); + </script> + <p>Tests CFInstance event handling on iframes</p> + </body> +</html> + diff --git a/chrome_frame/test/data/CFInstance_rpc_frame.html b/chrome_frame/test/data/CFInstance_rpc_frame.html new file mode 100644 index 0000000..a7dbfd7 --- /dev/null +++ b/chrome_frame/test/data/CFInstance_rpc_frame.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> + +<html> +<!-- This page is meant to load inside ChromeFrame --> + <head> + <script type="text/javascript" + src="chrome_frame_tester_helpers.js"></script> + <script type="text/javascript" + src="CFInstance.js"></script> + <script> + var cf = CFInstance; + cf.rpc.expose("rpcHandler", function(arg) { + cf.rpc.callRemote("handleCallback", ["hola!"]); + }); + cf.rpc.init(); + </script> + </head> + + <body> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + + <p>ChromeFrame + CFInstance PostMessage Test + <br>Test for PostMessage from the host browser to iframe and back</p> + </body> +</html> diff --git a/chrome_frame/test/data/CFInstance_rpc_host.html b/chrome_frame/test/data/CFInstance_rpc_host.html new file mode 100644 index 0000000..60680cf --- /dev/null +++ b/chrome_frame/test/data/CFInstance_rpc_host.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> + +<html> + <!-- This page is meant to loaded inside the host browser (IE, FF, etc.) --> + <head> + <script type="text/javascript" + src="chrome_frame_tester_helpers.js"></script> + <script type="text/javascript" + src="CFInstance.js"></script> + </head> + + <body> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + + <div id="parent"> + <div id="prev">before</div><div id="toBeReplaced"> + fallback content goes here + </div><div id="after">after</div> + </div> + <script type="text/javascript"> + var testName = "CFInstanceRPC"; + (function() { + try { + var cf = new CFInstance({ + node: "toBeReplaced", + src: "CFInstance_rpc_frame.html" + }); + + var handleCallback = function(arg) { + // alert(arg); + if (arg == "hola!") { + onSuccess(testName, 1); + } + }; + + cf.rpc.expose("handleCallback", handleCallback); + cf.rpc.init(); + + cf.rpc.callRemote("rpcHandler", ["whatcho talkin 'bout, willis!?"]); + } catch (e) { + onFailure(testName, 1, + "CFInstance constructor failed with error: " + e); + } + })(); + </script> + <p>Tests CFInstance event handling on iframes</p> + </body> +</html> + diff --git a/chrome_frame/test/data/CFInstance_rpc_internal_frame.html b/chrome_frame/test/data/CFInstance_rpc_internal_frame.html new file mode 100644 index 0000000..8208269 --- /dev/null +++ b/chrome_frame/test/data/CFInstance_rpc_internal_frame.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> + +<html> +<!-- This page is meant to load inside ChromeFrame --> + <head> + <script type="text/javascript" + src="chrome_frame_tester_helpers.js"></script> + <script type="text/javascript" + src="CFInstance.js"></script> + <script> + setTimeout(rpcCall, 10000); + function rpcCall() { + var cf = CFInstance; + cf.rpc.callRemote("callback"); + cf.rpc.init(); + } + </script> + </head> + + <body> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + + <p>ChromeFrame + CFInstance PostMessage Test + <br>Test for PostMessage from the host browser to iframe and back</p> + </body> +</html> diff --git a/chrome_frame/test/data/CFInstance_rpc_internal_host.html b/chrome_frame/test/data/CFInstance_rpc_internal_host.html new file mode 100644 index 0000000..f350871 --- /dev/null +++ b/chrome_frame/test/data/CFInstance_rpc_internal_host.html @@ -0,0 +1,45 @@ +<!DOCTYPE html> + +<html> + <!-- This page is meant to loaded inside the host browser (IE, FF, etc.) --> + <head> + <script type="text/javascript" + src="chrome_frame_tester_helpers.js"></script> + <script type="text/javascript" + src="CFInstance.js"></script> + </head> + + <body> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + + <div id="parent"> + <div id="prev">before</div><div id="toBeReplaced"> + fallback content goes here + </div><div id="after">after</div> + </div> + <script type="text/javascript"> + var testName = "CFInstanceRPCInternal"; + (function() { + try { + var cf = new CFInstance({ + node: "toBeReplaced", + src: "CFInstance_rpc_internal_frame.html" + }); + + + cf.rpc.expose("callback", function(arg) { + onSuccess(testName, 1); + }); + cf.rpc.init(); + } catch (e) { + onFailure(testName, 1, + "CFInstance constructor failed with error: " + e); + } + })(); + </script> + <p>Tests CFInstance event handling on iframes</p> + </body> +</html> + diff --git a/chrome_frame/test/data/CFInstance_singleton_frame.html b/chrome_frame/test/data/CFInstance_singleton_frame.html new file mode 100644 index 0000000..1ab0302 --- /dev/null +++ b/chrome_frame/test/data/CFInstance_singleton_frame.html @@ -0,0 +1,20 @@ +<html> + <head> + <title>talk to me...</title> + <script type="text/javascript" src="CFInstance.js"></script> + <script type="text/javascript"> + CFInstance.listen("load", function() { + document.body.innerHTML = "sending 'foo'"; + CFInstance.postMessage("foo"); + document.body.innerHTML = "...waiting..."; + }); + CFInstance.listen("message", function(evt) { + document.body.innerHTML = "sending 'baz'"; + CFInstance.postMessage("baz"); + }); + </script> + </head> + <body> + <h1>sends a message to the parent</h1> + </body> +</html> diff --git a/chrome_frame/test/data/CFInstance_singleton_host.html b/chrome_frame/test/data/CFInstance_singleton_host.html new file mode 100644 index 0000000..3eda108 --- /dev/null +++ b/chrome_frame/test/data/CFInstance_singleton_host.html @@ -0,0 +1,44 @@ +<html> + <!-- This page is meant to loaded inside the host browser (IE, FF, etc.) --> + <head> + <script type="text/javascript" src="chrome_frame_tester_helpers.js"> + </script> + <script type="text/javascript" src="CFInstance.js"></script> + </head> + + <body> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + + <div id="toBeReplaced"> + fallback content goes here + </div> + <script type="text/javascript"> + var testName = "CFInstanceSingleton"; + (function() { + try{ + var cf = new CFInstance({ + src: "CFInstance_singleton_frame.html", + node: "toBeReplaced" + }); + + // test a call/response set of actions driven by the CF content + cf.listen("message", function(evt) { + if (evt.data == "foo") { + cf.postMessage("bar"); + } else if(evt.data == "baz") { + onSuccess(testName, 1); + } + }); + + } catch (e) { + onFailure(testName, 1, + "CFInstance constructor failed with error: " + e); + } + })(); + </script> + <p>Tests ChromeFrame Navigation</p> + </body> +</html> + diff --git a/chrome_frame/test/data/CFInstance_zero_size_host.html b/chrome_frame/test/data/CFInstance_zero_size_host.html new file mode 100644 index 0000000..94b35c0 --- /dev/null +++ b/chrome_frame/test/data/CFInstance_zero_size_host.html @@ -0,0 +1,41 @@ +<html> + <!-- This page is meant to loaded inside the host browser (IE, FF, etc.) --> + <head> + <script type="text/javascript" + src="chrome_frame_tester_helpers.js"></script> + <script type="text/javascript" + src="CFInstance.js"></script> + </head> + + <body> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + + <div id="parent"> + <div id="prev">before</div><div id="toBeReplaced"> + fallback content goes here + </div><div id="after">after</div> + </div> + <script type="text/javascript"> + var testName = "CFInstanceZeroSize"; + (function() { + try { + var cf = new CFInstance({ + node: "toBeReplaced", + src: "CFInstance_basic_frame.html", + cssText: "width: 0px; height: 0px;", + onload: function() { + onSuccess(testName, 1); + } + }); + } catch (e) { + onFailure(testName, 1, + "CFInstance constructor failed with error: "+e); + } + })(); + </script> + <p>Tests CFInstance event handling on iframes</p> + </body> +</html> + diff --git a/chrome_frame/test/data/back_to_ie.html b/chrome_frame/test/data/back_to_ie.html new file mode 100644 index 0000000..ad4b61d --- /dev/null +++ b/chrome_frame/test/data/back_to_ie.html @@ -0,0 +1,21 @@ +<html>
+ <head><title>Back to IE</title>
+ <script type="text/javascript"
+ src="chrome_frame_tester_helpers.js"></script>
+
+ <script type="text/javascript">
+ function test() {
+ var test_name = 'navigate_out';
+ if (isRunningInMSIE()) {
+ onSuccess(test_name, 1);
+ } else {
+ onFailure(test_name, 1, 'Failed');
+ }
+ }
+ </script>
+ </head>
+ <body onLoad="test();">
+ <h2>Redirected!</h2>
+ <p>This page should have loaded in IE</p>
+ </body>
+</html>
diff --git a/chrome_frame/test/data/cf_protocol.html b/chrome_frame/test/data/cf_protocol.html new file mode 100644 index 0000000..0036555 --- /dev/null +++ b/chrome_frame/test/data/cf_protocol.html @@ -0,0 +1,20 @@ +<html>
+ <head><title>cf: protocol test</title>
+ <script type="text/javascript"
+ src="chrome_frame_tester_helpers.js"></script>
+
+ <script type="text/javascript">
+ function test() {
+ if (isRunningInMSIE()) {
+ reloadUsingCFProtocol();
+ } else {
+ onSuccess("chrome_frame_protocol", 1);
+ }
+ }
+ </script>
+ </head>
+ <body onLoad="setTimeout(test, 100);">
+ <h2>Prepare to be redirected!</h2>
+ <p>Redirects the same page to its 'cf:' version</p>
+ </body>
+</html>
diff --git a/chrome_frame/test/data/chrome_frame_mime_filter_test.html b/chrome_frame/test/data/chrome_frame_mime_filter_test.html new file mode 100644 index 0000000..7520758 --- /dev/null +++ b/chrome_frame/test/data/chrome_frame_mime_filter_test.html @@ -0,0 +1,32 @@ +<!-- saved from url=(0014)about:internet -->
+<!-- Note that for the above Mark of the Web to work, the comment must
+ be followed by a CR/LF ending, so please do not change the line endings. -->
+<html>
+<!-- This page is meant to load inside a host browser like IE/FF -->
+<head>
+<meta http-equiv="X-UA-Compatible" content="chrome=1"/>
+<script type="text/javascript" src="chrome_frame_tester_helpers.js"></script>
+<script type="text/javascript">
+
+function TestIfRunningInChrome() {
+ var is_chrome = /chrome/.test(navigator.userAgent.toLowerCase());
+ if (is_chrome) {
+ onSuccess("MIMEFilter", "MIME filter worked!");
+ } else {
+ onFailure("MIMEFilter", "MIME filter failed :-(",
+ "User agent = " + navigator.userAgent.toLowerCase());
+ }
+}
+
+</script>
+</head>
+
+<body onload="TestIfRunningInChrome();">
+<div id="statusPanel" style="border: 1px solid red; width: 100%">
+Test running....
+</div>
+
+<p>
+
+</body>
+</html>
\ No newline at end of file diff --git a/chrome_frame/test/data/chrome_frame_resize.html b/chrome_frame/test/data/chrome_frame_resize.html new file mode 100644 index 0000000..afba53b --- /dev/null +++ b/chrome_frame/test/data/chrome_frame_resize.html @@ -0,0 +1,138 @@ +<!-- saved from url=(0014)about:internet -->
+<!-- Please preserve the CR/LF at the end of the previous line. -->
+<html>
+<!-- This page is meant to load inside the host browser like IE/FF -->
+<head>
+<script type="text/javascript" src="chrome_frame_tester_helpers.js"></script>
+<script type="text/javascript">
+function onLoad() {
+ var chromeFrame = GetChromeFrame();
+ chromeFrame.onmessage = OnChromeFrameResize;
+ setTimeout(NavigateToURL, 100);
+}
+
+function NavigateToURL() {
+ var chromeFrame = GetChromeFrame();
+ chromeFrame.src = "chrome_frame_resize_hosted.html";
+ setTimeout(CheckIfNavigationFailed, 15000);
+}
+
+var navigation_success = false;
+
+function CheckIfNavigationFailed() {
+ if (!navigation_success) {
+ onFailure("Resize", 1, "ChromeFrame Navigation failed");
+ }
+}
+
+function OnNavigationSucceeded() {
+ navigation_success = true;
+ appendStatus("ChromeFrame hosted page loaded, beginning test...");
+ setTimeout(ResizeChromeFrame, 100);
+}
+
+var resize_step = 0;
+
+function ResizeChromeFrame() {
+ var chromeFrame = GetChromeFrame();
+
+ if (resize_step == 0) {
+ appendStatus("Setting chromeFrame to 100x100");
+ resize_step = 1;
+ chromeFrame.width = 100;
+ setTimeout("OnResizeFailure(0)", 2000);
+ } else if (resize_step == 1) {
+ resize_step = 2;
+ chromeFrame.height = 100;
+ setTimeout("OnResizeFailure(1)", 2000);
+ } else if (resize_step == 2) {
+ appendStatus("Setting chromeFrame to 10x10");
+ resize_step = 3;
+ chromeFrame.width = 10;
+ setTimeout("OnResizeFailure(0)", 2000);
+ } else if (resize_step == 3) {
+ resize_step = 4;
+ chromeFrame.height = 10;
+ setTimeout("OnResizeFailure(1)", 2000);
+ }
+
+ // Note that setting the ChromeFrame window to 0x0 (or < 2x2 if we have the
+ // WS_BORDER style defined on our window) currently results
+ // in a check failure from the child chrome.exe process.
+ // TODO(robertshield): Figure out why and fix it.
+}
+
+var resize_step_received = 0;
+
+function OnChromeFrameResize(evt) {
+ resize_step_received++;
+ appendStatus("ChromeFrame resized: " + evt.data + "step=" +
+ resize_step_received);
+
+ if (resize_step == 4) {
+ onSuccess("Resize", 1);
+ } else {
+ setTimeout(ResizeChromeFrame, 100);
+ }
+}
+
+function OnResizeFailure(step) {
+ // It turns out that the hosted page gets two calls to onresize()
+ // every time a single size parameter (i.e. width OR height) is changed.
+ // As such this check doesn't quite guarantee success, but if it fails,
+ // then we should fail the unit test.
+ if (step >= resize_step_received) {
+ onFailure("Resize", 1, "Did not receive resize reply back from frame.");
+ }
+}
+
+function GetChromeFrame() {
+ return window.document.ChromeFrame;
+}
+
+var debug_counter = 0;
+
+function DebugResizeChromeFrame(delta) {
+ var chromeFrame = GetChromeFrame();
+ var newWidth = chromeFrame.clientWidth + delta;
+ var newHeight = chromeFrame.clientHeight + delta;
+
+ appendStatus(debug_counter + ". DEBUG resizing CF to (" + newWidth + "," +
+ newHeight + ")");
+
+ debug_counter++;
+
+ chromeFrame.width = newWidth;
+ chromeFrame.height = newHeight;
+}
+
+</script>
+</head>
+
+<body onload="onLoad();">
+<div id="description" style="border: 2px solid black; width: 100%">
+ Test for resizing the chrome frame control.
+</div>
+<div id="statusPanel" style="border: 1px solid red; width: 100%">
+Test running....
+</div>
+
+<object id="ChromeFrame" codebase="http://www.google.com"
+ classid="CLSID:E0A900DF-9611-4446-86BD-4B1D47E7DB2A"
+ style="border: 1px solid blue">
+ <param name="onload" value="return OnNavigationSucceeded();" />
+ <embed id="ChromeFramePlugin" name="ChromeFrame"
+ onload="return OnNavigationSucceeded();"
+ type="application/chromeframe"
+ style="border: 1px solid green">
+ </embed>
+</object>
+<br />
+<br />
+
+<button onclick="javascript:DebugResizeChromeFrame(20);">Bigger</button>
+<button onclick="javascript:DebugResizeChromeFrame(-20);">Smaller</button>
+
+</body>
+</html>
+
diff --git a/chrome_frame/test/data/chrome_frame_resize_hosted.html b/chrome_frame/test/data/chrome_frame_resize_hosted.html new file mode 100644 index 0000000..95528ec --- /dev/null +++ b/chrome_frame/test/data/chrome_frame_resize_hosted.html @@ -0,0 +1,48 @@ +<html>
+<!-- This page is meant to load inside ChromeFrame -->
+<head>
+
+<script type="text/javascript" src="chrome_frame_tester_helpers.js"></script>
+<script type="text/javascript">
+
+function onLoad() {
+ var host = window.externalHost;
+ if (host) {
+ host.postMessage(
+ "ChromeFrame navigated to: " + window.location);
+ } else {
+ appendStatus("Running in non-hosted mode");
+ }
+}
+
+var resize_event_counter = 0;
+
+function OnResizeEvent() {
+ width = window.innerWidth;
+ height = window.innerHeight;
+
+ appendStatus(resize_event_counter + ". Resized to (" + width +
+ "," + height + ")");
+
+ var host = window.externalHost;
+ if (host) {
+ host.postMessage(
+ resize_event_counter + ":(" + width + "," + height + ")");
+ } else {
+ appendStatus("window.externalHost is null!");
+ }
+}
+</script>
+</head>
+
+<body onload="onLoad();" bgcolor="#999999" onresize="OnResizeEvent();">
+<div id="description" style="border: 2px solid black; width: 100%">
+ Hosted resize test component.
+</div>
+
+<div id="statusPanel" style="border: 1px solid red; width: 100%">
+Test running....
+</div>
+
+</body>
+</html>
diff --git a/chrome_frame/test/data/chrome_frame_tester_helpers.js b/chrome_frame/test/data/chrome_frame_tester_helpers.js new file mode 100644 index 0000000..1c914ee --- /dev/null +++ b/chrome_frame/test/data/chrome_frame_tester_helpers.js @@ -0,0 +1,142 @@ +// +// This script provides some mechanics for testing ChromeFrame +// +function onSuccess(name, id) { + onFinished(name, id, "OK"); +} + +function onFailure(name, id, status) { + onFinished(name, id, status); +} + +function getXHRObject(){ + var XMLHTTP_PROGIDS = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', + 'Msxml2.XMLHTTP.4.0']; + var http = null; + try { + http = new XMLHttpRequest(); + } catch(e) { + } + + if (http) + return http; + + for (var i = 0; i < 3; ++i) { + var progid = XMLHTTP_PROGIDS[i]; + try { + http = new ActiveXObject(progid); + } catch(e) { + } + + if (http) + break; + } + return http; +} + +var reportURL = "/writefile/"; + +function shutdownServer() { + var xhr = getXHRObject(); + if(!xhr) + return; + + xhr.open("POST", "/kill", false); + try { + xhr.send(null); + } catch(e) { + appendStatus("XHR send failed. Error: " + e.description); + } +} + +// Optionally send the server a notification that onload was fired. +// To be called from within window.onload. +function sendOnLoadEvent() { + writeToServer("OnLoadEvent", "loaded"); +} + +function writeToServer(name, result) { + var xhr = getXHRObject(); + if(!xhr) + return; + + // synchronously POST the results + xhr.open("POST", reportURL + name, false); + xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); + try { + xhr.send(result); + } catch(e) { + appendStatus("XHR send failed. Error: " + e.description); + } +} + +function postResult(name, result) { + writeToServer(name, result); + // NOTE: + // not watching for failure or return status issues. What should we do here? + shutdownServer(); +} + +// Finish running a test by setting the status +// and the cookie. +function onFinished(name, id, result) { + appendStatus(result); + + // set a cookie to report the results... + var cookie = name + "." + id + ".status=" + result + "; path=/"; + document.cookie = cookie; + + // ...and POST the status back to the server + postResult(name, result); +} + +function appendStatus(message) { + var statusPanel = document.getElementById("statusPanel"); + if (statusPanel) { + statusPanel.innerHTML += '<BR>' + message; + } +} + +function readCookie(name) { + var cookie_name = name + "="; + var ca = document.cookie.split(';'); + + for(var i = 0 ; i < ca.length ; i++) { + var c = ca[i]; + while (c.charAt(0) == ' ') { + c = c.substring(1,c.length); + } + if (c.indexOf(cookie_name) == 0) { + return c.substring(cookie_name.length, c.length); + } + } + return null; +} + +function createCookie(name,value,days) { + var expires = ""; + if (days) { + var date = new Date(); + date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); + expires = "; expires=" + date.toGMTString(); + } + document.cookie = name+"="+value+expires+"; path=/"; +} + +function eraseCookie(name) { + createCookie(name, "", -1); +} + +function isRunningInMSIE() { + if (/MSIE (\d+\.\d+);/.test(navigator.userAgent)) + return true; + + return false; +} + +function reloadUsingCFProtocol() { + var redirect_location = "cf:"; + redirect_location += window.location; + window.location = redirect_location; +} + diff --git a/chrome_frame/test/data/event_listener.html b/chrome_frame/test/data/event_listener.html new file mode 100644 index 0000000..6fbd158 --- /dev/null +++ b/chrome_frame/test/data/event_listener.html @@ -0,0 +1,42 @@ +<html> +<!-- This page is meant to load inside the host browser like IE/FF --> +<head> + <script type="text/javascript" src="chrome_frame_tester_helpers.js"></script> + <script language="javascript"> + var g_test_name = 'EventListener'; + + function onChromeFrameLoaded() { + appendStatus('Chrome frame loaded.'); + onSuccess(g_test_name, 1); + } + + function onEventNotFired() { + onFailure(g_test_name, 1, 'Did not receive onload event'); + } + + function onDocumentLoad() { + appendStatus('document loaded'); + var cf = getCf(); + cf.addEventListener("load", onChromeFrameLoaded, false); + setTimeout(onEventNotFired, 10000) + cf.src = "CFInstance_basic_frame.html"; + } + + function getCf() { + return window.document.ChromeFrame; + } + </script> +</head> +<body onload="onDocumentLoad();"> + <object id="ChromeFrame" width="500" height ="300" + codebase="http://www.google.com" + classid="CLSID:E0A900DF-9611-4446-86BD-4B1D47E7DB2A"> + <embed id="ChromeFramePlugin" name="ChromeFrame" width="500" + height="500" type="application/chromeframe"> + </embed> + </object> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> +</body> +</html> diff --git a/chrome_frame/test/data/iframe_basic_host.html b/chrome_frame/test/data/iframe_basic_host.html new file mode 100644 index 0000000..f9a4c0c --- /dev/null +++ b/chrome_frame/test/data/iframe_basic_host.html @@ -0,0 +1,13 @@ +<!-- saved from url=(0014)about:internet --> +<html> +<!-- This page is meant to load inside the host browser like IE/FF --> +<head></head> +<body> + <iframe src ="postmessage_basic_host.html" width="50%" height="600">
+ </iframe> + + <br> + <br> + Test for ChromeFrame inside iframe +</body> +</html> diff --git a/chrome_frame/test/data/in_head.html b/chrome_frame/test/data/in_head.html new file mode 100644 index 0000000..a093c71 --- /dev/null +++ b/chrome_frame/test/data/in_head.html @@ -0,0 +1,62 @@ +<html> + <!-- This page is meant to load inside the host browser like IE/FF --> + <head><title>Initialize hidden chrome frame</title> + <script type="text/javascript" src="chrome_frame_tester_helpers.js"> + </script> + <script type="text/javascript"> + var g_failure_timeout = null; + var g_test_id = 1; + var g_test_name = 'InHead'; + var g_cf3_loaded = false; + + function OnNavigationFailed() { + onFailure(g_test_name, g_test_id, 'ChromeFrame Navigation failed'); + } + + function OnObjectFocusFailed() { + appendStatus('chrome frame focus failed'); + onFailure(g_test_name, g_test_id, 'Embed in head test failed'); + } + + function OnFrameMessage(evt) { + appendStatus('Chrome frame visible and focused'); + if (evt.data == 'btnOnFocus') { + window.clearTimeout(g_failure_timeout); + g_failure_timeout = null; + appendStatus('Chrome frame visible and focused'); + + onSuccess(g_test_name, g_test_id); + } + } + + function OnFrameLoad() { + document.ChromeFrame.focus(); + } + + function OnLoad() { + g_failure_timeout = window.setTimeout(OnObjectFocusFailed, 10000); + } + </script> + <object id="ChromeFrame" width="300" height="80" tabindex="0" + codebase="http://www.google.com" + classid="CLSID:E0A900DF-9611-4446-86BD-4B1D47E7DB2A"> + <param name="src" value="simple_object_focus_cf.html"> + <param name="onload" value="OnFrameLoad();"> + <param name="onloaderror" value="OnNavigationFailed();"> + <param name="onmessage" value="OnFrameMessage(arguments[0]);"> + <embed width="300" height="80" name="ChromeFrame" + type="application/chromeframe" + src="simple_object_focus_cf.html" + onload="OnFrameLoad();" + onloaderror="OnNavigationFailed();" + onmessage="OnFrameMessage(arguments[0]);"> + </embed> + </object> + </head> + <body onload = "OnLoad();"> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + <div id = "dummy"> </div> + </body> +</html> diff --git a/chrome_frame/test/data/initialize_hidden.html b/chrome_frame/test/data/initialize_hidden.html new file mode 100644 index 0000000..2da0917 --- /dev/null +++ b/chrome_frame/test/data/initialize_hidden.html @@ -0,0 +1,120 @@ +<html> + <!-- This page is meant to load inside the host browser like IE/FF --> + <head><title>Initialize hidden chrome frame</title> + <script type="text/javascript" src="chrome_frame_tester_helpers.js"> + </script> + <script type="text/javascript"> + var g_failure_timeout = null; + var g_test_id = 1; + var g_test_name = 'InitializeHidden'; + var g_cf3_loaded = false; + + function OnNavigationFailed() { + onFailure(g_test_name, g_test_id, 'ChromeFrame Navigation failed'); + } + + function OnObjectFocusFailed() { + appendStatus('chrome frame focus failed'); + onFailure(g_test_name, g_test_id, 'Visibility test failed'); + } + + function OnCF1Loaded() { + appendStatus('Chrome frame 1 loaded, not visible yet.'); + try { + // Make chrome frame visible + var cf1 = document.getElementById('CFDiv1'); + cf1.style.visibility = 'visible'; + appendStatus('Chrome frame 1 visibility - ' + cf1.style.visibility); + // Set focus to chrome frame. This should set focus to the + // first element inside the frame, which a script inside the + // page will detect and notify us back by sending us a message. + document.ChromeFrame1.focus(); + g_failure_timeout = window.setTimeout(OnObjectFocusFailed, 10000); + } catch(e) { + appendStatus('Error setting focus to CF1. Error: ' + e.description); + onFailure(g_test_name, g_test_id, 'CF1 focus() error'); + } + } + + function OnCF1Message(evt) { + if (evt.data == 'btnOnFocus') { + window.clearTimeout(g_failure_timeout); + g_failure_timeout = null; + appendStatus('CF1 visible and focused'); + + // Now make second chrome frame instance visible + document.getElementById('CFDiv2').style.display = 'block'; + appendStatus('Chrome frame 2 visible, should start loading now'); + g_failure_timeout = window.setTimeout(OnObjectFocusFailed, 10000); + } + } + + function OnCF2Loaded() { + appendStatus('Chrome frame 2 loaded'); + try { + // Set focus to chrome frame. This should set focus to the + // first element inside the frame, which a script inside the + // page will detect and notify us back by sending us a message. + // We set focus on a timeout as on IE it takes a while for the window + // to become visible. + setTimeout(SetFocusToCF2, 100); + } catch(e) { + appendStatus('Error setting focus to CF2. Error: ' + e.description); + onFailure(g_test_name, g_test_id, 'CF2 focus() error'); + } + } + + function SetFocusToCF2() { + document.ChromeFrame2.focus(); + } + + function OnCF2Message(evt) { + if (evt.data == 'btnOnFocus') { + window.clearTimeout(g_failure_timeout); + g_failure_timeout = null; + appendStatus('CF2 visible and focused'); + onSuccess(g_test_name, g_test_id); + } + } + </script> + </head> + <body> + <div id="CFDiv1" style="visibility: hidden;"> + <object id="ChromeFrame1" width="300" height="80" tabindex="0" + codebase="http://www.google.com" + classid="CLSID:E0A900DF-9611-4446-86BD-4B1D47E7DB2A"> + <param name="src" value="simple_object_focus_cf.html"> + <param name="onload" value="OnCF1Loaded();"> + <param name="onloaderror" value="OnNavigationFailed();"> + <param name="onmessage" value="OnCF1Message(arguments[0]);"> + <embed width="300" height="80" name="ChromeFrame1" + type="application/chromeframe" + src="simple_object_focus_cf.html" + onload="OnCF1Loaded();" + onloaderror="OnNavigationFailed();" + onmessage="OnCF1Message(arguments[0]);"> + </embed> + </object> + </div> + <div id="CFDiv2" style="display: none;"> + <object id="ChromeFrame2" width="300" height="80" tabindex="1" + codebase="http://www.google.com" + classid="CLSID:E0A900DF-9611-4446-86BD-4B1D47E7DB2A"> + <param name="src" value="simple_object_focus_cf.html"> + <param name="onload" value="OnCF2Loaded();"> + <param name="onloaderror" value="OnNavigationFailed();"> + <param name="onmessage" value="OnCF2Message(arguments[0]);"> + <embed width="300" height="80" name="ChromeFrame2" + type="application/chromeframe" + src="simple_object_focus_cf.html" + onload="OnCF2Loaded();" + onloaderror="OnNavigationFailed();" + onmessage="OnCF2Message(arguments[0]);"> + </embed> + </object> + </div> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + </body> +</html> diff --git a/chrome_frame/test/data/meta_tag.html b/chrome_frame/test/data/meta_tag.html new file mode 100644 index 0000000..9b17406 --- /dev/null +++ b/chrome_frame/test/data/meta_tag.html @@ -0,0 +1,21 @@ +<html>
+ <head>
+ <meta http-equiv="x-ua-compatible" content="chrome=1" /> + <title>Load chrome frame using meta tag</title>
+ <script type="text/javascript"
+ src="chrome_frame_tester_helpers.js"></script>
+
+ <script type="text/javascript">
+ function test() {
+ if (isRunningInMSIE()) {
+ onFailure("meta_tag", 1, "Failed");
+ } else {
+ onSuccess("meta_tag", 1);
+ }
+ }
+ </script>
+ </head>
+ <body onLoad="setTimeout(test, 100);"> + chrome trame in tab mode + </body> +</html> diff --git a/chrome_frame/test/data/navigate_out.html b/chrome_frame/test/data/navigate_out.html new file mode 100644 index 0000000..7b910b4 --- /dev/null +++ b/chrome_frame/test/data/navigate_out.html @@ -0,0 +1,20 @@ +<html>
+ <head><title>Test to make sure that navigations sent back to IE</title>
+ <script type="text/javascript"
+ src="chrome_frame_tester_helpers.js"></script>
+
+ <script type="text/javascript">
+ function test() {
+ if (isRunningInMSIE()) {
+ reloadUsingCFProtocol();
+ } else {
+ window.location = "back_to_ie.html";
+ }
+ }
+ </script>
+ </head>
+ <body onLoad="setTimeout(test, 100);">
+ <h2>Prepare to be redirected!</h2>
+ <p>Redirects the same page to its 'cf:' version and </p>
+ </body>
+</html>
diff --git a/chrome_frame/test/data/navigateurl_absolute_host.html b/chrome_frame/test/data/navigateurl_absolute_host.html new file mode 100644 index 0000000..03e1de2 --- /dev/null +++ b/chrome_frame/test/data/navigateurl_absolute_host.html @@ -0,0 +1,64 @@ +<html> +<!-- This page is meant to load inside the host browser like IE/FF --> + <head> + <script type="text/javascript" src="chrome_frame_tester_helpers.js"> + </script> + <script type="text/javascript"> + function onLoad() { + var chromeFrame = GetChromeFrame(); + chromeFrame.onloaderror = OnNavigationFailed; + setTimeout(NavigateToURL, 100); + } + + function NavigateToURL() { + var frame_location = new String(window.location); + frame_location = frame_location.replace( + /navigateurl_absolute_host.html/, "navigateurl_basic_frame.html"); + var chromeFrame = GetChromeFrame(); + chromeFrame.src = frame_location; + setTimeout(OnNavigationTimeout, 10000); + } + + var navigation_success = 0; + + function OnNavigationFailed(msg) { + if (!navigation_success) { + onFailure("NavigateURL", 1, 'ChromeFrame Navigation failed: ' + msg); + } + } + + function OnNavigationTimeout() { + OnNavigationFailed('TIMEOUT'); + } + + function OnChromeFrameLoaded() { + navigation_success = 1; + onSuccess("NavigateURL", 1); + } + + function GetChromeFrame() { + return window.document.ChromeFrame; + } + </script> + </head> + + <body onload="onLoad();"> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + <object id="ChromeFrame" width="500" height="500" + codebase="http://www.google.com" + classid="CLSID:E0A900DF-9611-4446-86BD-4B1D47E7DB2A"> + <param name="onload" value="return OnChromeFrameLoaded();"> + <embed id="ChromeFramePlugin" width="500" height="500" + name="ChromeFrame" onload="return OnChromeFrameLoaded();" + type="application/chromeframe"> + </embed> + </OBJECT> + <br /> + <br /> + + <p>Tests ChromeFrame Navigation</p> + + </body> +</html> diff --git a/chrome_frame/test/data/navigateurl_basic_frame.html b/chrome_frame/test/data/navigateurl_basic_frame.html new file mode 100644 index 0000000..4d99b43 --- /dev/null +++ b/chrome_frame/test/data/navigateurl_basic_frame.html @@ -0,0 +1,12 @@ +<html> +<!-- This page is meant to load inside ChromeFrame --> + +<body> +<div id="statusPanel" style="border: 1px solid red; width: 100%"> +Test running.... +</div> + +<p>ChromeFrame NavigateURL Test<br> +Tests ChromeFrame Navigation</p> +</body> +</html> diff --git a/chrome_frame/test/data/navigateurl_relative_host.html b/chrome_frame/test/data/navigateurl_relative_host.html new file mode 100644 index 0000000..06ec63e --- /dev/null +++ b/chrome_frame/test/data/navigateurl_relative_host.html @@ -0,0 +1,60 @@ +<html> + <!-- This page is meant to load inside the host browser like IE/FF --> + <head> + <script type="text/javascript" src="chrome_frame_tester_helpers.js"> + </script> + <script type="text/javascript"> + function onLoad() { + var chromeFrame = GetChromeFrame(); + chromeFrame.onloaderror = OnNavigationFailed; + setTimeout(NavigateToURL, 100); + } + + function NavigateToURL() { + var chromeFrame = GetChromeFrame(); + chromeFrame.src = "navigateurl_basic_frame.html"; + setTimeout(OnNavigationTimeout, 10000); + } + + var navigation_complete = 0; + + function OnNavigationFailed(msg) { + if (!navigation_complete) { + onFailure("NavigateURL", 1, 'ChromeFrame Navigation failed: ' + msg); + } + } + + function OnNavigationTimeout() { + OnNavigationFailed('TIMEOUT'); + } + + function OnChromeFrameLoaded() { + navigation_success = 1; + onSuccess("NavigateURL", 1); + } + + function GetChromeFrame() { + return window.document.ChromeFrame; + } + </script> + </head> + + <body onload="onLoad();"> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + <object id="ChromeFrame" width="500" height="500" + codebase="http://www.google.com" + classid="CLSID:E0A900DF-9611-4446-86BD-4B1D47E7DB2A"> + <param name="onload" value="return OnChromeFrameLoaded();"> + <embed id="ChromeFramePlugin" width="500" height="500" + name="ChromeFrame" onload="return OnChromeFrameLoaded();" + type="application/chromeframe"> + </embed> + </OBJECT> + <br /> + <br /> + + <p>Tests ChromeFrame Navigation</p> + </body> +</html> diff --git a/chrome_frame/test/data/persistent_cookie_test_page.html b/chrome_frame/test/data/persistent_cookie_test_page.html new file mode 100644 index 0000000..ea56262 --- /dev/null +++ b/chrome_frame/test/data/persistent_cookie_test_page.html @@ -0,0 +1,36 @@ +<html>
+ <head><title>Persistent host browser chrome frame cookie test</title>
+ <script type="text/javascript"
+ src="chrome_frame_tester_helpers.js"></script>
+
+ <script type="text/javascript">
+ function validatePersistentCookie() {
+ if (readCookie("PersistentCookie1") != "Cookie1" ||
+ readCookie("PersistentCookie2") != "Cookie2") {
+ onFailure("PersistentCookieTest", 1, "Failed");
+ } else {
+ onSuccess("PersistentCookieTest", 1);
+ }
+ eraseCookie("PersistentCookie1");
+ eraseCookie("PersistentCookie2");
+ }
+
+ function setPersistentCookieAndRedirect() {
+ if (isRunningInMSIE()) {
+ eraseCookie("PersistentCookie1");
+ eraseCookie("PersistentCookie2");
+ createCookie("PersistentCookie1", "Cookie1", 365);
+ createCookie("PersistentCookie2", "Cookie2", 365);
+ reloadUsingCFProtocol();
+ } else {
+ validatePersistentCookie();
+ }
+ }
+ </script>
+ </head>
+ <body onLoad="setTimeout(setPersistentCookieAndRedirect, 100);">
+ <h2>Prepare to be redirected!</h2>
+ <p>Sets two persistent cookies in the host and redirects ChromeFrame <br />
+ to the same page </p>
+ </body>
+</html>
diff --git a/chrome_frame/test/data/postmessage_basic_frame.html b/chrome_frame/test/data/postmessage_basic_frame.html new file mode 100644 index 0000000..76f8cb3 --- /dev/null +++ b/chrome_frame/test/data/postmessage_basic_frame.html @@ -0,0 +1,27 @@ +<!-- saved from url=(0014)about:internet -->
+<html>
+<!-- This page is meant to load inside ChromeFrame -->
+ <head>
+ <script type="text/javascript" src="chrome_frame_tester_helpers.js"> + </script> + <script type="text/javascript">
+ function OnLoad() { + externalHost.onmessage = OnHostMessage; + }
+
+ function OnHostMessage(evt) {
+ appendStatus('Host message: ' + evt.data); + externalHost.postMessage("Hello from ChromeFrame");
+ }
+ </script>
+ </head>
+
+ <body onload="OnLoad();">
+ <div id="statusPanel" style="border: 1px solid red; width: 100%">
+ Test running....
+ </div>
+
+ <p>ChromeFrame PostMessage Test
+ <br>Test for PostMessage from the host browser to ChromeFrame and back</p>
+ </body>
+</html>
diff --git a/chrome_frame/test/data/postmessage_basic_host.html b/chrome_frame/test/data/postmessage_basic_host.html new file mode 100644 index 0000000..e5ecef9 --- /dev/null +++ b/chrome_frame/test/data/postmessage_basic_host.html @@ -0,0 +1,69 @@ +<!-- saved from url=(0014)about:internet --> +<html> +<!-- This page is meant to load inside the host browser like IE/FF --> + <head> + <script type="text/javascript" src="chrome_frame_tester_helpers.js"> + </script> + <script type="text/javascript"> + var post_message_reply_received = 0; + + function onChromeFrameLoaded() { + appendStatus('Chrome frame loaded...'); + document.ChromeFrame.postMessage('Hello from host'); + setTimeout(onPostMessageFailure, 10000); + } + + function onNavigationFailed(msg) { + onFailure('PostMessage', 1, 'ChromeFrame Navigation failed: ' + msg); + } + + function onChromeFrameMessage(evt) { + try { + var d = new String(evt.data); + appendStatus('Message: ' + d); + if (d == 'Hello from ChromeFrame') { + post_message_reply_received = 1; + onSuccess('PostMessage', 1); + } else { + onFailure('PostMessage', 1, 'unexpected data'); + } + } catch (e) { + onFailure('PostMessage', 1, 'exception in onChromeFrameMessage'); + } + } + + function onPostMessageFailure() { + if (!post_message_reply_received) { + onFailure('PostMessage', 1, 'Did not receive reply back from frame'); + } + } + </script> + </head> + + <body> + <object id="ChromeFrame" width="500" height ="300" + codebase="http://www.google.com" + classid="CLSID:E0A900DF-9611-4446-86BD-4B1D47E7DB2A"> + <param name="src" value="postmessage_basic_frame.html"> + <param name="onload" value="onChromeFrameLoaded();"> + <param name="onloaderror" value="onNavigationFailed();"> + <param name="onmessage" value="onChromeFrameMessage(arguments[0]);"> + <embed id="ChromeFramePlugin" name="ChromeFrame" + width="500" height="500" + src="postmessage_basic_frame.html" + type="application/chromeframe" + onload="onChromeFrameLoaded();" + onloaderror="onNavigationFailed();" + onmessage="onChromeFrameMessage(arguments[0]);"> + </embed> + </object> + <br> + <br> + <p>Test for PostMessage from the host browser to ChromeFrame and back</p> + <button onclick="document.ChromeFrame.postMessage('Message from button');"> + Send message to frame</button> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + </body> +</html> diff --git a/chrome_frame/test/data/privileged_apis_frame.html b/chrome_frame/test/data/privileged_apis_frame.html new file mode 100644 index 0000000..9e51152 --- /dev/null +++ b/chrome_frame/test/data/privileged_apis_frame.html @@ -0,0 +1,33 @@ +<!-- saved from url=(0014)about:internet --> +<html> +<!-- This page is meant to load inside ChromeFrame --> + <head> + <script type="text/javascript" src="chrome_frame_tester_helpers.js"> + </script> + <script type="text/javascript"> + function OnLoad() { + externalHost.onmessage = OnHostMessage; + } + + function OnHostMessage(evt) { + // Any time we receive a message, we reflect it back both + // with a nonsensical target, and with "*". + appendStatus('Host message: ' + evt.data); + externalHost.postMessage(evt.data, + "privileged_target"); + appendStatus('After postMessage(' + evt.data + ', "privileged_target)"'); + externalHost.postMessage(evt.data); + appendStatus('After postMessage(' + evt.data + '")'); + } + </script> + </head> + + <body onload="OnLoad();"> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + + <p>ChromeFrame PrivilegeApis Test + <br>Tests that private messaging is not available to regular web pages</p> + </body> +</html> diff --git a/chrome_frame/test/data/privileged_apis_host.html b/chrome_frame/test/data/privileged_apis_host.html new file mode 100644 index 0000000..7261704 --- /dev/null +++ b/chrome_frame/test/data/privileged_apis_host.html @@ -0,0 +1,85 @@ +<html> + <head><title>Privileged Apis test</title> + <script type='text/javascript' src='chrome_frame_tester_helpers.js'> + </script> + <script type='text/javascript'> + var testName = 'PrivilegedApis'; + function OnNavigationFailed(msg) { + onFailure(testName, 1, 'ChromeFrame Navigation failed: ' + msg); + } + + function OnPrivateMessage() { + onFailure(testName, 1, 'OnPrivateMessage should not execute'); + } + + function OnChromeFrameMessage(evt) { + try { + var d = new String(evt.data); + appendStatus('Message: ' + d); + if (d == 'succeed') { + onSuccess(testName, 1); + } else { + onFailure(testName, 1, 'unexpected data'); + } + } catch (e) { + onFailure(testName, 1, 'exception in OnChromeFrameMessage'); + } + } + + function OnChromeFrameLoaded(url) { + var cf = GetChromeFrame(); + + try { + // Any message received by this listener is a failure. + // This succeeds in FF, but throws an exception in IE. + cf.addEventListener('onprivatemessage', OnPrivateMessage, false); + } catch(e) { + cf.onprivatemessage = + appendStatus('addEventListener onprivatemessage threw exception') + } + + // If this invocation succeeds, then 'fail' is reflected by the frame + // and we fail in the OnChromeFrameMessage handler above. + try { + cf.postPrivateMessage('fail', String(document.location), '*'); + onFailure(testName, 1, 'postPrivateMessage should throw'); + } catch(e) { + } + appendStatus('After postPrivateMessage') + // The frame reflects this twice, first to a bogus target + // and again to the default target '*'. We succeed if we + // get the reflected message to OnChromeFrameMessage and not to + // OnPrivateMessage. + cf.postMessage('succeed'); + } + + function GetChromeFrame() { + return window.document.ChromeFrame; + } + </script> + </head> + <body> + <div id='statusPanel' style='border: 1px solid red; width: 100%'> + Test running.... + </div> + + <!-- TODO(siggi): Test setting onprivatemessage in these params --> + <object id='ChromeFrame' width='500' height='500' + codebase='http://www.google.com' + classid='CLSID:E0A900DF-9611-4446-86BD-4B1D47E7DB2A'> + <param name='src' value='privileged_apis_frame.html'> + <param name='onload' value='OnChromeFrameLoaded(arguments[0]);'> + <param name='onloaderror' value='OnNavigationFailed();'> + <param name='onmessage' value='OnChromeFrameMessage(arguments[0]);'> + <embed id='ChromeFramePlugin' width='500' height='500' name='ChromeFrame' + src='privileged_apis_frame.html' + type='application/chromeframe' + onload='OnChromeFrameLoaded(arguments[0]);' + onloaderror='OnNavigationFailed();' + onmessage='return OnChromeFrameMessage(arguments[0]);' + privileged_mode='1' + </embed> + </object> + <p>Tests that privileged apis are unavailable from regular pages</p> + </body> +</html> diff --git a/chrome_frame/test/data/simple_object_focus.html b/chrome_frame/test/data/simple_object_focus.html new file mode 100644 index 0000000..138ffa5 --- /dev/null +++ b/chrome_frame/test/data/simple_object_focus.html @@ -0,0 +1,95 @@ +<!-- saved from url=(0014)about:internet --> +<html> +<!-- This page is meant to load inside the host browser like IE/FF --> +<head> +<script type="text/javascript" src="chrome_frame_tester_helpers.js"></script> +<script type="text/javascript"> +var g_failure_timeout = null; +var g_test_id = 1; +var g_test_name = "ObjectFocus"; + +function onLoad() { + status("onload"); + + try { + var cf = getCf(); + cf.onmessage = OnChromeFrameMessage; + window.setTimeout(NavigateToURL, 100); + } catch(e) { + status("error: onload"); + onFailure(g_test_name, g_test_id, "error in onload"); + } + + sendOnLoadEvent(); +} + +function NavigateToURL() { + try { + status("Navigate to URL"); + var cf = getCf(); + cf.src = "simple_object_focus_cf.html"; + g_failure_timeout = window.setTimeout(OnObjectFocusFailed, 10000); + } catch(e) { + status("error: NavigateToURL"); + onFailure(g_test_name, g_test_id, "NavigateToURL error"); + } +} + +function OnObjectFocusFailed() { + status("OnNavigationFailed"); + onFailure(g_test_name, g_test_id, "focus test failed"); +} + +function OnChromeFrameLoaded() { + status("OnChromeFrameLoaded"); + try { + // Set focus to chrome frame. This should set focus to the first element + // inside the frame, which a script inside the page will detect and notify + // us back by sending us a message. + getCf().focus(); + } catch(e) { + status("error: can't set focus"); + onFailure(g_test_name, g_test_id, "focus() error"); + } +} + +function OnChromeFrameMessage(evt) { + if (evt.data != "btnOnFocus") { + status("unexpected message: " + evt.data + " from " + evt.origin); + } else { + window.clearTimeout(g_failure_timeout); + g_failure_timeout = null; + status("success"); + } + onSuccess(g_test_name, g_test_id); +} + +function getCf() { + // Fetching chrome frame with getElementById doesn't work in Firefox. + // Most likely due to object vs embed. + return document.ChromeFrame; +} + +// Useful while writing and debugging the unit test. +function status(s) { + var panel = document.getElementById("status_panel"); + panel.innerHTML = s; +} + +</script> +</head> +<body onload="onLoad();"> +<div id="status_panel" style="border: 1px solid red; width: 100%"> +Test running.... +</div> +<object id="ChromeFrame" width="300" height="60" tabindex="0" + codebase="http://www.google.com" + classid="CLSID:E0A900DF-9611-4446-86BD-4B1D47E7DB2A"> + <param name="onload" value="return OnChromeFrameLoaded();">
+ <embed width="300" height="60" name="ChromeFrame" + onload="return OnChromeFrameLoaded();" + type="application/chromeframe"> + </embed> +</object> +</body> +</html> diff --git a/chrome_frame/test/data/simple_object_focus_cf.html b/chrome_frame/test/data/simple_object_focus_cf.html new file mode 100644 index 0000000..9b06711 --- /dev/null +++ b/chrome_frame/test/data/simple_object_focus_cf.html @@ -0,0 +1,10 @@ +<html> +<!-- This page is meant to load inside Chrome Frame --> + <body> + <button onfocus="externalHost.postMessage('btnOnFocus');"> + hello world</button> + <div id="statusPanel" style="border: 1px solid green; width: 100%"> + Inside Chrome Frame.... + </div> + </body> +</html> diff --git a/chrome_frame/test/data/src_property_frame1.html b/chrome_frame/test/data/src_property_frame1.html new file mode 100644 index 0000000..1eaa3cf --- /dev/null +++ b/chrome_frame/test/data/src_property_frame1.html @@ -0,0 +1,13 @@ +<html>
+ <head><title>src property test - page 1</title>
+ <script type="text/javascript">
+ function redirect(){
+ window.location = "src_property_frame2.html";
+ }
+ </script>
+ </head>
+ <body onLoad="setTimeout(redirect, 100);">
+ <h2>Prepare to be redirected!</h2>
+ <p>Redirecting to a new page within frame...</p>
+ </body>
+</html>
\ No newline at end of file diff --git a/chrome_frame/test/data/src_property_frame2.html b/chrome_frame/test/data/src_property_frame2.html new file mode 100644 index 0000000..c5c0364 --- /dev/null +++ b/chrome_frame/test/data/src_property_frame2.html @@ -0,0 +1,8 @@ +<html>
+ <head><title>src property test - page 2</title>
+ </head>
+ <body>
+ <h2>Redirected!</h2>
+ <p>All finished.</p>
+ </body>
+</html>
\ No newline at end of file diff --git a/chrome_frame/test/data/src_property_host.html b/chrome_frame/test/data/src_property_host.html new file mode 100644 index 0000000..7b7b358 --- /dev/null +++ b/chrome_frame/test/data/src_property_host.html @@ -0,0 +1,65 @@ +<html>
+ <head><title>src property test</title> + <script type="text/javascript" src="chrome_frame_tester_helpers.js"> + </script> + <script type="text/javascript"> + function OnNavigationFailed() { + onFailure("ChromeFrame_SrcTest", 1, "ChromeFrame Navigation failed"); + } + + var load_count = 2; + + function OnChromeFrameLoaded(url) { + url = url.data; + + var chromeFrame = GetChromeFrame(); + var frame_url = chromeFrame.src; + + appendStatus("Loaded URL: " + url + " Frame url: " + frame_url); + load_count--; + + if (load_count) { + // For the first load, the URLs should match. + if (frame_url != url) { + onFailure("SrcProperty", 1, "Url: " + url); + } + } else { + // Previous versions changed the frame URL when internal navigation + // was performed. This does not match how iframes behave, and so we + // report success only in the case that they continue to match, even + // though the "internal" URL is different (and not visible) to the + // external host. + if (frame_url == url) { + onFailure("SrcProperty", 1, "Url: " + url); + } else { + onSuccess("SrcProperty", 1); + } + } + } + + function GetChromeFrame() { + return window.document.ChromeFrame; + } + </script> + </head> + <body> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + + <object id="ChromeFrame" width="500" height="500" + codebase="http://www.google.com" + classid="CLSID:E0A900DF-9611-4446-86BD-4B1D47E7DB2A"> + <param name="src" value="src_property_frame1.html"> + <param name="onload" value="return OnChromeFrameLoaded(arguments[0]);"> + <param name="onloaderror" value="return OnNavigationFailed(arguments[0]);"> + <embed id="ChromeFramePlugin" width="500" height="500" name="ChromeFrame" + src="src_property_frame1.html" + type="application/chromeframe" + onload="return OnChromeFrameLoaded(arguments[0]);" + onloaderror="return OnNavigationFailed(arguments[0]);"> + </embed> + </object> + <p>Tests ChromeFrame Navigation</p> + </body> +</html> diff --git a/chrome_frame/test/data/version.html b/chrome_frame/test/data/version.html new file mode 100644 index 0000000..b585a6d --- /dev/null +++ b/chrome_frame/test/data/version.html @@ -0,0 +1,29 @@ +<html> +<!-- This page is meant to load inside the host browser like IE/FF --> + <head> + <script type="text/javascript" src="chrome_frame_tester_helpers.js"> + </script> + <script type="text/javascript"> + function onLoad() { + appendStatus('Chrome frame version: ' + document.ChromeFrame.version); + onFinished('version', 1, document.ChromeFrame.version); + } + </script> + </head> + + <body onload="onLoad();"> + <object id="ChromeFrame" + codebase="http://www.google.com" + classid="CLSID:E0A900DF-9611-4446-86BD-4B1D47E7DB2A"> + <embed id="ChromeFramePlugin" name="ChromeFrame" + type="application/chromeframe" + </embed> + </object> + <br> + <br> + <p>Test for Chrome frame version</p> + <div id="statusPanel" style="border: 1px solid red; width: 100%"> + Test running.... + </div> + </body> +</html> diff --git a/chrome_frame/test/function_stub_unittest.cc b/chrome_frame/test/function_stub_unittest.cc new file mode 100644 index 0000000..6ef6f36 --- /dev/null +++ b/chrome_frame/test/function_stub_unittest.cc @@ -0,0 +1,66 @@ +// Copyright (c) 2009 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. + +#include "chrome_frame/function_stub.h" + +#include "chrome_frame/test/chrome_frame_unittests.h" + +#define NO_INLINE __declspec(noinline) + +namespace { + +typedef int (__stdcall* FooPrototype)(); + +NO_INLINE int __stdcall Foo() { + return 1; +} + +NO_INLINE int __stdcall PatchedFoo(FooPrototype original) { + return original() + 1; +} + +} // end namespace + +TEST(PatchTests, FunctionStub) { + EXPECT_EQ(Foo(), 1); + // Create a function stub that calls PatchedFoo and supplies it with + // a pointer to Foo. + FunctionStub* stub = FunctionStub::Create(reinterpret_cast<uintptr_t>(&Foo), + &PatchedFoo); + EXPECT_TRUE(stub != NULL); + // Call the stub as it were Foo(). The call should get forwarded to Foo(). + FooPrototype patch = reinterpret_cast<FooPrototype>(stub->code()); + EXPECT_EQ(patch(), 2); + // Now neutralize the stub so that it calls Foo() directly without touching + // PatchedFoo(). + // stub->BypassStub(&Foo); + stub->BypassStub(reinterpret_cast<void*>(stub->argument())); + EXPECT_EQ(patch(), 1); + // We're done with the stub. + FunctionStub::Destroy(stub); +} + +// Basic tests to check the validity of a stub. +TEST(PatchTests, FunctionStubCompare) { + EXPECT_EQ(Foo(), 1); + + // Detect the absence of a stub + FunctionStub* stub = reinterpret_cast<FunctionStub*>(&Foo); + EXPECT_FALSE(stub->is_valid()); + + stub = FunctionStub::Create(reinterpret_cast<uintptr_t>(&Foo), &PatchedFoo); + EXPECT_TRUE(stub != NULL); + EXPECT_TRUE(stub->is_valid()); + + FooPrototype patch = reinterpret_cast<FooPrototype>(stub->code()); + EXPECT_EQ(patch(), 2); + + // See if we can get the correct absolute pointer to the hook function + // back from the stub. + EXPECT_EQ(stub->absolute_target(), reinterpret_cast<uintptr_t>(&PatchedFoo)); + + // Also verify that the argument being passed to the hook function is indeed + // the pointer to the original function (again, absolute not relative). + EXPECT_EQ(stub->argument(), reinterpret_cast<uintptr_t>(&Foo)); +} diff --git a/chrome_frame/test/helper_gmock.h b/chrome_frame/test/helper_gmock.h new file mode 100644 index 0000000..7f6d0a7 --- /dev/null +++ b/chrome_frame/test/helper_gmock.h @@ -0,0 +1,597 @@ +// Copyright (c) 2006-2009 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. +#ifndef CHROME_FRAME_TEST_HELPER_GMOCK_H_ +#define CHROME_FRAME_TEST_HELPER_GMOCK_H_ +// This intention of this file is to make possible gmock WithArgs<> in +// Chromium code base. +// MutantImpl is like CallbackImpl, but also has prebound arguments (like Task) +// There is also functor wrapper around it that should be used with +// testing::Invoke, for example: +// testing::WithArgs<0, 2>( +// testing::Invoke(CBF(&mock_object, &MockObject::Something, &tmp_obj, 12))); +// This will invoke MockObject::Something(tmp_obj, 12, arg_0, arg_2) + +// DispatchToMethod supporting two sets of arguments - +// prebound (P) and calltime (C) +// 1 - 1 +template <class ObjT, class Method, class P1, class C1> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple1<P1>& p, + const Tuple1<C1>& c) { + (obj->*method)(p.a, c.a); +} +// 2 - 1 +template <class ObjT, class Method, class P1, class P2, class C1> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple2<P1, P2>& p, + const Tuple1<C1>& c) { + (obj->*method)(p.a, p.b, c.a); +} +// 3 - 1 +template <class ObjT, class Method, class P1, class P2, class P3, class C1> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple3<P1, P2, P3>& p, + const Tuple1<C1>& c) { + (obj->*method)(p.a, p.b, p.c, c.a); +} +// 4 - 1 +template <class ObjT, class Method, class P1, class P2, class P3, + class P4, class C1> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple4<P1, P2, P3, P4>& p, + const Tuple1<C1>& c) { + (obj->*method)(p.a, p.b, p.c, p.d, c.a); +} + +// 1 - 2 +template <class ObjT, class Method, class P1, class C1, class C2> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple1<P1>& p, + const Tuple2<C1, C2>& c) { + (obj->*method)(p.a, c.a, c.b); +} + +// 2 - 2 +template <class ObjT, class Method, class P1, class P2, class C1, class C2> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple2<P1, P2>& p, + const Tuple2<C1, C2>& c) { + (obj->*method)(p.a, p.b, c.a, c.b); +} + +// 3 - 2 +template <class ObjT, class Method, class P1, class P2, class P3, class C1, + class C2> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple3<P1, P2, P3>& p, + const Tuple2<C1, C2>& c) { + (obj->*method)(p.a, p.b, p.c, c.a, c.b); +} + +// 4 - 2 +template <class ObjT, class Method, class P1, class P2, class P3, class P4, + class C1, class C2> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple4<P1, P2, P3, P4>& p, + const Tuple2<C1, C2>& c) { + (obj->*method)(p.a, p.b, p.c, p.d, c.a, c.b); +} + +// 1 - 3 +template <class ObjT, class Method, class P1, class C1, class C2, class C3> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple1<P1>& p, + const Tuple3<C1, C2, C3>& c) { + (obj->*method)(p.a, c.a, c.b, c.c); +} + +// 2 - 3 +template <class ObjT, class Method, class P1, class P2, class C1, class C2, + class C3> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple2<P1, P2>& p, + const Tuple3<C1, C2, C3>& c) { + (obj->*method)(p.a, p.b, c.a, c.b, c.c); +} + +// 3 - 3 +template <class ObjT, class Method, class P1, class P2, class P3, class C1, + class C2, class C3> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple3<P1, P2, P3>& p, + const Tuple3<C1, C2, C3>& c) { + (obj->*method)(p.a, p.b, p.c, c.a, c.b, c.c); +} + +// 4 - 3 +template <class ObjT, class Method, class P1, class P2, class P3, class P4, + class C1, class C2, class C3> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple4<P1, P2, P3, P4>& p, + const Tuple3<C1, C2, C3>& c) { + (obj->*method)(p.a, p.b, p.c, p.d, c.a, c.b, c.c); +} + +// 1 - 4 +template <class ObjT, class Method, class P1, class C1, class C2, class C3, + class C4> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple1<P1>& p, + const Tuple4<C1, C2, C3, C4>& c) { + (obj->*method)(p.a, c.a, c.b, c.c, c.d); +} + +// 2 - 4 +template <class ObjT, class Method, class P1, class P2, class C1, class C2, + class C3, class C4> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple2<P1, P2>& p, + const Tuple4<C1, C2, C3, C4>& c) { + (obj->*method)(p.a, p.b, c.a, c.b, c.c, c.d); +} + +// 3 - 4 +template <class ObjT, class Method, class P1, class P2, class P3, + class C1, class C2, class C3, class C4> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple3<P1, P2, P3>& p, + const Tuple4<C1, C2, C3, C4>& c) { + (obj->*method)(p.a, p.b, p.c, c.a, c.b, c.c, c.d); +} + +// 4 - 4 +template <class ObjT, class Method, class P1, class P2, class P3, class P4, + class C1, class C2, class C3, class C4> +inline void DispatchToMethod(ObjT* obj, Method method, + const Tuple4<P1, P2, P3, P4>& p, + const Tuple4<C1, C2, C3, C4>& c) { + (obj->*method)(p.a, p.b, p.c, p.d, c.a, c.b, c.c, c.d); +} + + +//////////////////////////////////////////////////////////////////////////////// + +// Like CallbackImpl but has prebound arguments (like Task) +template <class T, typename Method, typename PreBound, typename Params> +class MutantImpl : public CallbackStorage<T, Method>, + public CallbackRunner<Params> { + public: + MutantImpl(T* obj, Method meth, const PreBound& pb) + : CallbackStorage<T, Method>(obj, meth), + pb_(pb) { + } + + virtual void RunWithParams(const Params& params) { + // use "this->" to force C++ to look inside our templatized base class; see + // Effective C++, 3rd Ed, item 43, p210 for details. + DispatchToMethod(this->obj_, this->meth_, pb_, params); + } + + PreBound pb_; +}; + +//////////////////////////////////////////////////////////////////////////////// +// Mutant creation simplification +// 1 - 1 +template <class T, typename P1, typename A1> +inline typename Callback1<A1>::Type* NewMutant(T* obj, + void (T::*method)(P1, A1), + P1 p1) { + return new MutantImpl<T, void (T::*)(P1, A1), P1, A1>(obj, method, + MakeTuple(p1)); +} + +// 1 - 2 +template <class T, typename P1, typename A1, typename A2> +inline typename Callback2<A1, A2>::Type* NewMutant(T* obj, + void (T::*method)(P1, A1, A2), + P1 p1) { + return new MutantImpl<T, void (T::*)(P1, A1, A2), Tuple1<P1>, Tuple2<A1, A2> > + (obj, method, MakeTuple(p1)); +} + +// 1 - 3 +template <class T, typename P1, typename A1, typename A2, typename A3> +inline typename Callback3<A1, A2, A3>::Type* +NewMutant(T* obj, void (T::*method)(P1, A1, A2, A3), P1 p1) { + return new MutantImpl<T, void (T::*)(P1, A1, A2, A3), Tuple1<P1>, + Tuple3<A1, A2, A3> >(obj, method, MakeTuple(p1)); +} + +// 1 - 4 +template <class T, typename P1, typename A1, typename A2, typename A3, + typename A4> +inline typename Callback4<A1, A2, A3, A4>::Type* +NewMutant(T* obj, void (T::*method)(P1, A1, A2, A3, A4), P1 p1) { + return new MutantImpl<T, void (T::*)(P1, A1, A2, A3, A4), Tuple1<P1>, + Tuple4<A1, A2, A3, A4> >(obj, method, MakeTuple(p1)); +} + + +// 2 - 1 +template <class T, typename P1, typename P2, typename A1> +inline typename Callback1<A1>::Type* +NewMutant(T* obj, void (T::*method)(P1, P2, A1), P1 p1, P2 p2) { + return new MutantImpl<T, void (T::*)(P1, P2, A1), Tuple2<P1, P2>, + Tuple1<A1> >(obj, method, MakeTuple(p1, p2)); +} + +// 2 - 2 +template <class T, typename P1, typename P2, typename A1, typename A2> +inline typename Callback2<A1, A2>::Type* +NewMutant(T* obj, void (T::*method)(P1, P2, A1, A2), P1 p1, P2 p2) { + return new MutantImpl<T, void (T::*)(P1, P2, A1, A2), Tuple2<P1, P2>, + Tuple2<A1, A2> >(obj, method, MakeTuple(p1, p2)); +} + +// 2 - 3 +template <class T, typename P1, typename P2, typename A1, typename A2, + typename A3> +inline typename Callback3<A1, A2, A3>::Type* +NewMutant(T* obj, void (T::*method)(P1, P2, A1, A2, A3), P1 p1, P2 p2) { + return new MutantImpl<T, void (T::*)(P1, P2, A1, A2, A3), Tuple2<P1, P2>, + Tuple3<A1, A2, A3> >(obj, method, MakeTuple(p1, p2)); +} + +// 2 - 4 +template <class T, typename P1, typename P2, typename A1, typename A2, + typename A3, typename A4> +inline typename Callback4<A1, A2, A3, A4>::Type* +NewMutant(T* obj, void (T::*method)(P1, P2, A1, A2, A3, A4), P1 p1, P2 p2) { + return new MutantImpl<T, void (T::*)(P1, P2, A1, A2, A3, A4), Tuple2<P1, P2>, + Tuple3<A1, A2, A3, A4> >(obj, method, MakeTuple(p1, p2)); +} + +// 3 - 1 +template <class T, typename P1, typename P2, typename P3, typename A1> +inline typename Callback1<A1>::Type* +NewMutant(T* obj, void (T::*method)(P1, P2, P3, A1), P1 p1, P2 p2, P3 p3) { + return new MutantImpl<T, void (T::*)(P1, P2, P3, A1), Tuple3<P1, P2, P3>, + Tuple1<A1> >(obj, method, MakeTuple(p1, p2, p3)); +} + +// 3 - 2 +template <class T, typename P1, typename P2, typename P3, typename A1, + typename A2> +inline typename Callback2<A1, A2>::Type* +NewMutant(T* obj, void (T::*method)(P1, P2, P3, A1, A2), P1 p1, P2 p2, P3 p3) { + return new MutantImpl<T, void (T::*)(P1, P2, P3, A1, A2), Tuple3<P1, P2, P3>, + Tuple2<A1, A2> >(obj, method, MakeTuple(p1, p2, p3)); +} + +// 3 - 3 +template <class T, typename P1, typename P2, typename P3, typename A1, + typename A2, typename A3> +inline typename Callback3<A1, A2, A3>::Type* +NewMutant(T* obj, void (T::*method)(P1, P2, P3, A1, A2, A3), P1 p1, P2 p2, + P3 p3) { + return new MutantImpl<T, void (T::*)(P1, P2, P3, A1, A2, A3), + Tuple3<P1, P2, P3>, Tuple3<A1, A2, A3> >(obj, method, + MakeTuple(p1, p2, p3)); +} + +// 3 - 4 +template <class T, typename P1, typename P2, typename P3, typename A1, + typename A2, typename A3, typename A4> +inline typename Callback4<A1, A2, A3, A4>::Type* +NewMutant(T* obj, void (T::*method)(P1, P2, P3, A1, A2, A3, A4), P1 p1, P2 p2, + P3 p3) { + return new MutantImpl<T, void (T::*)(P1, P2, P3, A1, A2, A3, A4), + Tuple3<P1, P2, P3>, Tuple3<A1, A2, A3, A4> >(obj, method, + MakeTuple(p1, p2, p3)); +} + +// 4 - 1 +template <class T, typename P1, typename P2, typename P3, typename P4, + typename A1> +inline typename Callback1<A1>::Type* +NewMutant(T* obj, void (T::*method)(P1, P2, P3, P4, A1), P1 p1, P2 p2, P3 p3, + P4 p4) { + return new MutantImpl<T, void (T::*)(P1, P2, P3, P4, A1), + Tuple4<P1, P2, P3, P4>, Tuple1<A1> >(obj, method, + MakeTuple(p1, p2, p3, p4)); +} + +// 4 - 2 +template <class T, typename P1, typename P2, typename P3, typename P4, + typename A1, typename A2> +inline typename Callback2<A1, A2>::Type* +NewMutant(T* obj, void (T::*method)(P1, P2, P3, P4, A1, A2), P1 p1, P2 p2, + P3 p3, P4 p4) { + return new MutantImpl<T, void (T::*)(P1, P2, P3, P4, A1, A2), + Tuple4<P1, P2, P3, P4>, Tuple2<A1, A2> >(obj, method, + MakeTuple(p1, p2, p3, p4)); +} + +// 4 - 3 +template <class T, typename P1, typename P2, typename P3, typename P4, + typename A1, typename A2, typename A3> +inline typename Callback3<A1, A2, A3>::Type* +NewMutant(T* obj, void (T::*method)(P1, P2, P3, P4, A1, A2, A3), P1 p1, P2 p2, + P3 p3, P4 p4) { + return new MutantImpl<T, void (T::*)(P1, P2, P3, P4, A1, A2, A3), + Tuple4<P1, P2, P3, P4>, Tuple3<A1, A2, A3> >(obj, method, + MakeTuple(p1, p2, p3, p4)); +} + +// 4 - 4 +template <class T, typename P1, typename P2, typename P3, typename P4, + typename A1, typename A2, typename A3, typename A4> +inline typename Callback4<A1, A2, A3, A4>::Type* +NewMutant(T* obj, void (T::*method)(P1, P2, P3, P4, A1, A2, A3, A4), + P1 p1, P2 p2, P3 p3, P4 p4) { + return new MutantImpl<T, void (T::*)(P1, P2, P3, P4, A1, A2, A3, A4), + Tuple4<P1, P2, P3, P4>, Tuple3<A1, A2, A3, A4> >(obj, method, + MakeTuple(p1, p2, p3, p4)); +} + +//////////////////////////////////////////////////////////////////////////////// +// Simple callback wrapper acting as a functor. +// Redirects operator() to CallbackRunner<Params>::Run +// We cannot delete the inner impl_ in object's destructor because +// this object is copied few times inside from GMock machinery. +template <typename Params> +struct CallbackFunctor { + explicit CallbackFunctor(CallbackRunner<Params>* cb) : impl_(cb) {} + + template <typename Arg1> + inline void operator()(const Arg1& a) { + impl_->Run(a); + delete impl_; + impl_ = NULL; + } + + template <typename Arg1, typename Arg2> + inline void operator()(const Arg1& a, const Arg2& b) { + impl_->Run(a, b); + delete impl_; + impl_ = NULL; + } + + template <typename Arg1, typename Arg2, typename Arg3> + inline void operator()(const Arg1& a, const Arg2& b, const Arg3& c) { + impl_->Run(a, b, c); + delete impl_; + impl_ = NULL; + } + + template <typename Arg1, typename Arg2, typename Arg3, typename Arg4> + inline void operator()(const Arg1& a, const Arg2& b, const Arg3& c, + const Arg4& d) { + impl_->Run(a, b, c, d); + delete impl_; + impl_ = NULL; + } + + private: + CallbackFunctor(); + CallbackRunner<Params>* impl_; +}; + + +/////////////////////////////////////////////////////////////////////////////// +// CallbackFunctors creation + +// 0 - 1 +template <class T, typename A1> +inline CallbackFunctor<Tuple1<A1> > +CBF(T* obj, void (T::*method)(A1)) { + return CallbackFunctor<Tuple1<A1> >(NewCallback(obj, method)); +} + +// 0 - 2 +template <class T, typename A1, typename A2> +inline CallbackFunctor<Tuple2<A1, A2> > +CBF(T* obj, void (T::*method)(A1, A2)) { + return CallbackFunctor<Tuple2<A1, A2> >(NewCallback(obj, method)); +} + +// 0 - 3 +template <class T, typename A1, typename A2, typename A3> +inline CallbackFunctor<Tuple3<A1, A2, A3> > +CBF(T* obj, void (T::*method)(A1, A2, A3)) { + return CallbackFunctor<Tuple3<A1, A2, A3> >(NewCallback(obj, method)); +} + +// 0 - 4 +template <class T, typename A1, typename A2, typename A3, typename A4> +inline CallbackFunctor<Tuple4<A1, A2, A3, A4> > +CBF(T* obj, void (T::*method)(A1, A2, A3, A4)) { + return CallbackFunctor<Tuple4<A1, A2, A3, A4> >(NewCallback(obj, method)); +} + +// 1 - 1 +template <class T, typename P1, typename A1> +inline CallbackFunctor<Tuple1<A1> > +CBF(T* obj, void (T::*method)(P1, A1), P1 p1) { + Callback1<A1>::Type* t = new MutantImpl<T, void (T::*)(P1, A1), Tuple1<P1>, + Tuple1<A1> >(obj, method, MakeTuple(p1)); + return CallbackFunctor<Tuple1<A1> >(t); +} + +// 1 - 2 +template <class T, typename P1, typename A1, typename A2> +inline CallbackFunctor<Tuple2<A1, A2> > +CBF(T* obj, void (T::*method)(P1, A1, A2), P1 p1) { + Callback2<A1, A2>::Type* t = new MutantImpl<T, void (T::*)(P1, A1, A2), + Tuple1<P1>, Tuple2<A1, A2> >(obj, method, MakeTuple(p1)); + return CallbackFunctor<Tuple2<A1, A2> >(t); +} + +// 1 - 3 +template <class T, typename P1, typename A1, typename A2, typename A3> +inline CallbackFunctor<Tuple3<A1, A2, A3> > +CBF(T* obj, void (T::*method)(P1, A1, A2, A3), P1 p1) { + Callback3<A1, A2, A3>::Type* t = + new MutantImpl<T, void (T::*)(P1, A1, A2, A3), Tuple1<P1>, + Tuple3<A1, A2, A3> >(obj, method, MakeTuple(p1)); + return CallbackFunctor<Tuple3<A1, A2, A3> >(t); +} + +// 1 - 4 +template <class T, typename P1, typename A1, typename A2, typename A3, + typename A4> +inline CallbackFunctor<Tuple4<A1, A2, A3, A4> > +CBF(T* obj, void (T::*method)(P1, A1, A2, A3, A4), P1 p1) { + Callback4<A1, A2, A3>::Type* t = + new MutantImpl<T, void (T::*)(P1, A1, A2, A3, A4), Tuple1<P1>, + Tuple4<A1, A2, A3, A4> >(obj, method, MakeTuple(p1)); + return CallbackFunctor<Tuple4<A1, A2, A3, A4> >(t); +} + +// 2 - 1 +template <class T, typename P1, typename P2, typename A1> +inline CallbackFunctor<Tuple1<A1> > +CBF(T* obj, void (T::*method)(P1, P2, A1), P1 p1, P2 p2) { + Callback1<A1>::Type* t = new MutantImpl<T, void (T::*)(P1, P2, A1), + Tuple2<P1, P2>, Tuple1<A1> >(obj, method, MakeTuple(p1, p2)); + return CallbackFunctor<Tuple1<A1> >(t); +} + +// 2 - 2 +template <class T, typename P1, typename P2, typename A1, typename A2> +inline CallbackFunctor<Tuple2<A1, A2> > +CBF(T* obj, void (T::*method)(P1, P2, A1, A2), P1 p1, P2 p2) { + Callback2<A1, A2>::Type* t = new MutantImpl<T, void (T::*)(P1, P2, A1, A2), + Tuple2<P1, P2>, Tuple2<A1, A2> >(obj, method, MakeTuple(p1, p2)); + return CallbackFunctor<Tuple2<A1, A2> >(t); +} + +// 2 - 3 +template <class T, typename P1, typename P2, typename A1, typename A2, + typename A3> inline CallbackFunctor<Tuple3<A1, A2, A3> > +CBF(T* obj, void (T::*method)(P1, P2, A1, A2, A3), P1 p1, P2 p2) { + Callback3<A1, A2, A3>::Type* t = + new MutantImpl<T, void (T::*)(P1, P2, A1, A2, A3), Tuple2<P1, P2>, + Tuple3<A1, A2, A3> >(obj, method, MakeTuple(p1, p2)); + return CallbackFunctor<Tuple3<A1, A2, A3> >(t); +} + +// 2 - 4 +template <class T, typename P1, typename P2, typename A1, typename A2, + typename A3, typename A4> +inline CallbackFunctor<Tuple4<A1, A2, A3, A4> > +CBF(T* obj, void (T::*method)(P1, P2, A1, A2, A3, A4), P1 p1, P2 p2) { + Callback4<A1, A2, A3>::Type* t = + new MutantImpl<T, void (T::*)(P1, P2, A1, A2, A3, A4), Tuple2<P1, P2>, + Tuple4<A1, A2, A3, A4> >(obj, method, MakeTuple(p1, p2)); + return CallbackFunctor<Tuple4<A1, A2, A3, A4> >(t); +} + + +// 3 - 1 +template <class T, typename P1, typename P2, typename P3, typename A1> +inline CallbackFunctor<Tuple1<A1> > +CBF(T* obj, void (T::*method)(P1, P2, P3, A1), P1 p1, P2 p2, P3 p3) { + Callback1<A1>::Type* t = new MutantImpl<T, void (T::*)(P1, P2, P3, A1), + Tuple3<P1, P2, P3>, Tuple1<A1> >(obj, method, MakeTuple(p1, p2, p3)); + return CallbackFunctor<Tuple1<A1> >(t); +} + + +// 3 - 2 +template <class T, typename P1, typename P2, typename P3, typename A1, + typename A2> inline CallbackFunctor<Tuple2<A1, A2> > +CBF(T* obj, void (T::*method)(P1, P2, P3, A1, A2), P1 p1, P2 p2, P3 p3) { + Callback2<A1, A2>::Type* t = + new MutantImpl<T, void (T::*)(P1, P2, P3, A1, A2), Tuple3<P1, P2, P3>, + Tuple2<A1, A2> >(obj, method, MakeTuple(p1, p2, p3)); + return CallbackFunctor<Tuple2<A1, A2> >(t); +} + +// 3 - 3 +template <class T, typename P1, typename P2, typename P3, typename A1, + typename A2, typename A3> +inline CallbackFunctor<Tuple3<A1, A2, A3> > +CBF(T* obj, void (T::*method)(P1, P2, P3, A1, A2, A3), P1 p1, P2 p2, P3 p3) { + Callback3<A1, A2, A3>::Type* t = + new MutantImpl<T, void (T::*)(P1, P2, P3, A1, A2, A3), Tuple3<P1, P2, P3>, + Tuple3<A1, A2, A3> >(obj, method, MakeTuple(p1, p2, p3)); + return CallbackFunctor<Tuple3<A1, A2, A3> >(t); +} + +// 3 - 4 +template <class T, typename P1, typename P2, typename P3, typename A1, + typename A2, typename A3, typename A4> +inline CallbackFunctor<Tuple4<A1, A2, A3, A4> > +CBF(T* obj, void (T::*method)(P1, P2, P3, A1, A2, A3, A4), + P1 p1, P2 p2, P3 p3) { + Callback4<A1, A2, A3>::Type* t = + new MutantImpl<T, void (T::*)(P1, P2, P3, A1, A2, A3, A4), + Tuple3<P1, P2, P3>, Tuple4<A1, A2, A3, A4> >(obj, method, + MakeTuple(p1, p2, p3)); + return CallbackFunctor<Tuple4<A1, A2, A3, A4> >(t); +} + + + +// 4 - 1 +template <class T, typename P1, typename P2, typename P3, typename P4, + typename A1> +inline CallbackFunctor<Tuple1<A1> > +CBF(T* obj, void (T::*method)(P1, P2, P3, P4, A1), P1 p1, P2 p2, P3 p3, P4 p4) { + Callback1<A1>::Type* t = new MutantImpl<T, void (T::*)(P1, P2, P3, P4, A1), + Tuple4<P1, P2, P3, P4>, Tuple1<A1> > + (obj, method, MakeTuple(p1, p2, p3, p4)); + return CallbackFunctor<Tuple1<A1> >(t); +} + + +// 4 - 2 +template <class T, typename P1, typename P2, typename P3, typename P4, + typename A1, typename A2> +inline CallbackFunctor<Tuple2<A1, A2> > +CBF(T* obj, void (T::*method)(P1, P2, P3, P4, A1, A2), + P1 p1, P2 p2, P3 p3, P4 p4) { + Callback2<A1, A2>::Type* t = + new MutantImpl<T, void (T::*)(P1, P2, P3, P4, A1, A2), + Tuple4<P1, P2, P3, P4>, + Tuple2<A1, A2> >(obj, method, MakeTuple(p1, p2, p3, p4)); + return CallbackFunctor<Tuple2<A1, A2> >(t); +} + +// 4 - 3 +template <class T, typename P1, typename P2, typename P3, typename P4, + typename A1, typename A2, typename A3> +inline CallbackFunctor<Tuple3<A1, A2, A3> > +CBF(T* obj, void (T::*method)(P1, P2, P3, P4, A1, A2, A3), + P1 p1, P2 p2, P3 p3, P4 p4) { + Callback3<A1, A2, A3>::Type* t = + new MutantImpl<T, void (T::*)(P1, P2, P3, P4, A1, A2, A3), + Tuple4<P1, P2, P3, P4>, Tuple3<A1, A2, A3> > + (obj, method, MakeTuple(p1, p2, p3, p4)); + return CallbackFunctor<Tuple3<A1, A2, A3> >(t); +} + +// 4 - 4 +template <class T, typename P1, typename P2, typename P3, typename P4, + typename A1, typename A2, typename A3, typename A4> +inline CallbackFunctor<Tuple4<A1, A2, A3, A4> > +CBF(T* obj, void (T::*method)(P1, P2, P3, P4, A1, A2, A3, A4), + P1 p1, P2 p2, P3 p3, P4 p4) { + Callback4<A1, A2, A3>::Type* t = + new MutantImpl<T, void (T::*)(P1, P2, P3, P4, A1, A2, A3, A4), + Tuple4<P1, P2, P3, P4>, Tuple4<A1, A2, A3, A4> >(obj, method, + MakeTuple(p1, p2, p3, p4)); + return CallbackFunctor<Tuple4<A1, A2, A3, A4> >(t); +} + + +// Simple task wrapper acting as a functor. +// Redirects operator() to Task::Run. We cannot delete the inner impl_ object +// in object's destructor because this object is copied few times inside +// from GMock machinery. +struct TaskHolder { + explicit TaskHolder(Task* impl) : impl_(impl) {} + void operator()() { + impl_->Run(); + delete impl_; + impl_ = NULL; + } + private: + TaskHolder(); + Task* impl_; +}; + +#endif // CHROME_FRAME_TEST_HELPER_GMOCK_H_ diff --git a/chrome_frame/test/html_util_test_data/basic_test.html b/chrome_frame/test/html_util_test_data/basic_test.html new file mode 100644 index 0000000..f0cd17a --- /dev/null +++ b/chrome_frame/test/html_util_test_data/basic_test.html @@ -0,0 +1,11 @@ +<HTML>
+
+ <HEAD>
+ <!-- Note the capitalization in CONtent to test the
+ case-insensitiveness -->
+ <META http-equiv="X-UA-Compatible" CONtent="chrome=1" />
+ </HEAD>
+ <BODY>
+
+ Wooo!
+ </BODY></HTML>
diff --git a/chrome_frame/test/html_util_test_data/degenerate_cases_test.html b/chrome_frame/test/html_util_test_data/degenerate_cases_test.html new file mode 100644 index 0000000..d527496a --- /dev/null +++ b/chrome_frame/test/html_util_test_data/degenerate_cases_test.html @@ -0,0 +1,7 @@ +<><foo ">
+</head>
+<"foo">
+<!-- Note that the meta tag shouldn't be picked up since we are still
+ inside a quote block. -->
+<META http-equiv="X-UA-Compatible" CONtent="chrome=1" />
+<fee>
diff --git a/chrome_frame/test/html_util_test_data/multiple_tags.html b/chrome_frame/test/html_util_test_data/multiple_tags.html new file mode 100644 index 0000000..9bd5cea --- /dev/null +++ b/chrome_frame/test/html_util_test_data/multiple_tags.html @@ -0,0 +1,17 @@ +<HTML><HEAD>
+
+ <META http-equiv="X-UA-Compatible" CONtent="chrome=1" />
+ <META http-equiv="X-UA-Compatible" content="chrome=1" />
+ <METAman http-equiv="X-UA-Compatible" CONtent="notchrome=1" />
+ <transMETA http-equiv="X-UA-Compatible" CONtent="notchrome=1" />
+ <IMETAGIRL http-equiv="X-UA-Compatible" CONtent="notchrome=1" />
+ <metA http-equiv="X-UA-Compatible" content="chrome=1" />
+ <!-- shouldn't pick up commented meta tags! -->
+ <!-- <metA http-equiv="X-UA-Compatible" content="chrome=1" /> -->
+
+ <!-- The following cases should also not be matched -->
+ <meta http-equiv="X-UA-Compatibleeee" content="chrome=1" />
+ <meta http-equiv="X-UA-Compatible!" content="chrome=1" />
+ <meta http-equiv="!X-UA-Compatible" content="chrome=1" />
+ <meta http-equiv="\"X-UA-Compatible\"" content="chrome=1" />
+<fee>
\ No newline at end of file diff --git a/chrome_frame/test/html_util_test_data/quotes_test.html b/chrome_frame/test/html_util_test_data/quotes_test.html new file mode 100644 index 0000000..03ce96d --- /dev/null +++ b/chrome_frame/test/html_util_test_data/quotes_test.html @@ -0,0 +1,10 @@ +<HTML>
+
+ <HEAD>
+ <DANGER red="herring>'" testing = "do'><><>quotes\"\\'work?">
+ <META http-equiv=X-UA-Compatible CONtent="chrome=1" />
+ </HEAD>
+ <BODY>
+
+ Wooo!
+ </BODY></HTML>
diff --git a/chrome_frame/test/html_util_unittests.cc b/chrome_frame/test/html_util_unittests.cc new file mode 100644 index 0000000..131b185 --- /dev/null +++ b/chrome_frame/test/html_util_unittests.cc @@ -0,0 +1,215 @@ +// Copyright (c) 2006-2009 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.
+
+#include <windows.h>
+#include <atlsecurity.h>
+#include <shellapi.h>
+
+#include "base/basictypes.h"
+#include "base/file_util.h"
+#include "base/message_loop.h"
+#include "base/path_service.h"
+#include "base/process_util.h"
+#include "base/ref_counted.h"
+#include "base/scoped_handle.h"
+#include "base/task.h"
+#include "base/win_util.h"
+#include "net/base/net_util.h"
+
+#include "chrome_frame/test/chrome_frame_unittests.h"
+#include "chrome_frame/chrome_frame_automation.h"
+#include "chrome_frame/chrome_frame_delegate.h"
+#include "chrome_frame/html_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class HtmlUtilUnittest : public testing::Test {
+ protected:
+ // Constructor
+ HtmlUtilUnittest() {}
+
+ // Returns the test path given a test case.
+ virtual bool GetTestPath(const std::wstring& test_case, std::wstring* path) {
+ if (!path) {
+ NOTREACHED();
+ return false;
+ }
+
+ std::wstring test_path;
+ if (!PathService::Get(base::DIR_SOURCE_ROOT, &test_path)) {
+ NOTREACHED();
+ return false;
+ }
+
+ file_util::AppendToPath(&test_path, L"chrome_frame");
+ file_util::AppendToPath(&test_path, L"test");
+ file_util::AppendToPath(&test_path, L"html_util_test_data");
+ file_util::AppendToPath(&test_path, test_case);
+
+ *path = test_path;
+ return true;
+ }
+
+ virtual bool GetTestData(const std::wstring& test_case, std::wstring* data) {
+ if (!data) {
+ NOTREACHED();
+ return false;
+ }
+
+ std::wstring path;
+ if (!GetTestPath(test_case, &path)) {
+ NOTREACHED();
+ return false;
+ }
+
+ std::string raw_data;
+ file_util::ReadFileToString(path, &raw_data);
+
+ // Convert to wide using the "best effort" assurance described in
+ // string_util.h
+ data->assign(UTF8ToWide(raw_data));
+ return true;
+ }
+};
+
+TEST_F(HtmlUtilUnittest, BasicTest) {
+ std::wstring test_data;
+ GetTestData(L"basic_test.html", &test_data);
+
+ HTMLScanner scanner(test_data.c_str());
+
+ // Grab the meta tag from the document and ensure that we get exactly one.
+ HTMLScanner::StringRangeList tag_list;
+ scanner.GetTagsByName(L"meta", &tag_list, L"body");
+ ASSERT_EQ(1, tag_list.size());
+
+ // Pull out the http-equiv attribute and check its value:
+ HTMLScanner::StringRange attribute_value;
+ EXPECT_TRUE(tag_list[0].GetTagAttribute(L"http-equiv", &attribute_value));
+ EXPECT_TRUE(attribute_value.Equals(L"X-UA-Compatible"));
+
+ // Pull out the content attribute and check its value:
+ EXPECT_TRUE(tag_list[0].GetTagAttribute(L"content", &attribute_value));
+ EXPECT_TRUE(attribute_value.Equals(L"chrome=1"));
+}
+
+TEST_F(HtmlUtilUnittest, QuotesTest) {
+ std::wstring test_data;
+ GetTestData(L"quotes_test.html", &test_data);
+
+ HTMLScanner scanner(test_data.c_str());
+
+ // Grab the meta tag from the document and ensure that we get exactly one.
+ HTMLScanner::StringRangeList tag_list;
+ scanner.GetTagsByName(L"meta", &tag_list, L"body");
+ ASSERT_EQ(1, tag_list.size());
+
+ // Pull out the http-equiv attribute and check its value:
+ HTMLScanner::StringRange attribute_value;
+ EXPECT_TRUE(tag_list[0].GetTagAttribute(L"http-equiv", &attribute_value));
+ EXPECT_TRUE(attribute_value.Equals(L"X-UA-Compatible"));
+
+ // Pull out the content attribute and check its value:
+ EXPECT_TRUE(tag_list[0].GetTagAttribute(L"content", &attribute_value));
+ EXPECT_TRUE(attribute_value.Equals(L"chrome=1"));
+}
+
+TEST_F(HtmlUtilUnittest, DegenerateCasesTest) {
+ std::wstring test_data;
+ GetTestData(L"degenerate_cases_test.html", &test_data);
+
+ HTMLScanner scanner(test_data.c_str());
+
+ // Scan for meta tags in the document. We expect not to pick up the one
+ // that appears to be there since it is technically inside a quote block.
+ HTMLScanner::StringRangeList tag_list;
+ scanner.GetTagsByName(L"meta", &tag_list, L"body");
+ EXPECT_TRUE(tag_list.empty());
+}
+
+TEST_F(HtmlUtilUnittest, MultipleTagsTest) {
+ std::wstring test_data;
+ GetTestData(L"multiple_tags.html", &test_data);
+
+ HTMLScanner scanner(test_data.c_str());
+
+ // Grab the meta tag from the document and ensure that we get exactly three.
+ HTMLScanner::StringRangeList tag_list;
+ scanner.GetTagsByName(L"meta", &tag_list, L"body");
+ EXPECT_EQ(7, tag_list.size());
+
+ // Pull out the content attribute for each tag and check its value:
+ HTMLScanner::StringRange attribute_value;
+ HTMLScanner::StringRangeList::const_iterator tag_list_iter(
+ tag_list.begin());
+ int valid_tag_count = 0;
+ for (; tag_list_iter != tag_list.end(); tag_list_iter++) {
+ HTMLScanner::StringRange attribute_value;
+ if (tag_list_iter->GetTagAttribute(L"http-equiv", &attribute_value) &&
+ attribute_value.Equals(L"X-UA-Compatible")) {
+ EXPECT_TRUE(tag_list_iter->GetTagAttribute(L"content", &attribute_value));
+ EXPECT_TRUE(attribute_value.Equals(L"chrome=1"));
+ valid_tag_count++;
+ }
+ }
+ EXPECT_EQ(3, valid_tag_count);
+}
+
+TEST_F(HtmlUtilUnittest, ShortDegenerateTest1) {
+ std::wstring test_data(
+ L"<foo><META http-equiv=X-UA-Compatible content='chrome=1'");
+
+ HTMLScanner scanner(test_data.c_str());
+
+ // Scan for meta tags in the document. We expect not to pick up the one
+ // that is there since it is not properly closed.
+ HTMLScanner::StringRangeList tag_list;
+ scanner.GetTagsByName(L"meta", &tag_list, L"body");
+ EXPECT_TRUE(tag_list.empty());
+}
+
+TEST_F(HtmlUtilUnittest, ShortDegenerateTest2) {
+ std::wstring test_data(
+ L"<foo <META http-equiv=X-UA-Compatible content='chrome=1'/>");
+
+ HTMLScanner scanner(test_data.c_str());
+
+ // Scan for meta tags in the document. We expect not to pick up the one
+ // that appears to be there since it is inside a non-closed tag.
+ HTMLScanner::StringRangeList tag_list;
+ scanner.GetTagsByName(L"meta", &tag_list, L"body");
+ EXPECT_TRUE(tag_list.empty());
+}
+
+TEST_F(HtmlUtilUnittest, QuoteInsideHTMLCommentTest) {
+ std::wstring test_data(
+ L"<!-- comment' --><META http-equiv=X-UA-Compatible content='chrome=1'/>");
+
+ HTMLScanner scanner(test_data.c_str());
+
+ // Grab the meta tag from the document and ensure that we get exactly one.
+ HTMLScanner::StringRangeList tag_list;
+ scanner.GetTagsByName(L"meta", &tag_list, L"body");
+ ASSERT_EQ(1, tag_list.size());
+
+ // Pull out the http-equiv attribute and check its value:
+ HTMLScanner::StringRange attribute_value;
+ EXPECT_TRUE(tag_list[0].GetTagAttribute(L"http-equiv", &attribute_value));
+ EXPECT_TRUE(attribute_value.Equals(L"X-UA-Compatible"));
+
+ // Pull out the content attribute and check its value:
+ EXPECT_TRUE(tag_list[0].GetTagAttribute(L"content", &attribute_value));
+ EXPECT_TRUE(attribute_value.Equals(L"chrome=1"));
+}
+
+TEST_F(HtmlUtilUnittest, CloseTagInsideHTMLCommentTest) {
+ std::wstring test_data(
+ L"<!-- comment> <META http-equiv=X-UA-Compatible content='chrome=1'/>-->");
+
+ HTMLScanner scanner(test_data.c_str());
+
+ // Grab the meta tag from the document and ensure that we get exactly one.
+ HTMLScanner::StringRangeList tag_list;
+ scanner.GetTagsByName(L"meta", &tag_list, L"body");
+ ASSERT_TRUE(tag_list.empty());
+}
diff --git a/chrome_frame/test/http_server.cc b/chrome_frame/test/http_server.cc new file mode 100644 index 0000000..f2cc333 --- /dev/null +++ b/chrome_frame/test/http_server.cc @@ -0,0 +1,56 @@ +// Copyright (c) 2006-2009 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. +#include "chrome_frame/test/http_server.h" + +const wchar_t kDocRoot[] = L"chrome_frame\\test\\data"; + +void ChromeFrameHTTPServer::SetUp() { + std::wstring document_root(kDocRoot); + server_ = HTTPTestServer::CreateServer(document_root, NULL, 30, 1000); + ASSERT_TRUE(server_ != NULL); + + // copy CFInstance.js into the test directory + FilePath cf_source_path; + PathService::Get(base::DIR_SOURCE_ROOT, &cf_source_path); + cf_source_path = cf_source_path.Append(FILE_PATH_LITERAL("chrome_frame")); + + file_util::CopyFile(cf_source_path.Append(FILE_PATH_LITERAL("CFInstance.js")), + cf_source_path.Append( + FILE_PATH_LITERAL("test")).Append( + FILE_PATH_LITERAL("data")).Append( + FILE_PATH_LITERAL("CFInstance.js"))); // NOLINT +} + +void ChromeFrameHTTPServer::TearDown() { + if (server_) { + server_ = NULL; + } + + // clobber CFInstance.js + FilePath cfi_path; + PathService::Get(base::DIR_SOURCE_ROOT, &cfi_path); + cfi_path = cfi_path + .Append(FILE_PATH_LITERAL("chrome_frame")) + .Append(FILE_PATH_LITERAL("test")) + .Append(FILE_PATH_LITERAL("data")) + .Append(FILE_PATH_LITERAL("CFInstance.js")); + + file_util::Delete(cfi_path, false); +} + +bool ChromeFrameHTTPServer::WaitToFinish(int milliseconds) { + if (!server_) + return true; + + return server_->WaitToFinish(milliseconds); +} + +GURL ChromeFrameHTTPServer::Resolve(const wchar_t* relative_url) { + return server_->TestServerPageW(relative_url); +} + +std::wstring ChromeFrameHTTPServer::GetDataDir() { + return server_->GetDataDirectory().ToWStringHack(); +} + diff --git a/chrome_frame/test/http_server.h b/chrome_frame/test/http_server.h new file mode 100644 index 0000000..acac5b5 --- /dev/null +++ b/chrome_frame/test/http_server.h @@ -0,0 +1,32 @@ +// Copyright (c) 2006-2009 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. +#ifndef CHROME_FRAME_TEST_HTTP_SERVER_H_ +#define CHROME_FRAME_TEST_HTTP_SERVER_H_ + +#include <windows.h> +#include <string> + +#include "googleurl/src/gurl.h" +#include "base/ref_counted.h" +#include "net/url_request/url_request_unittest.h" + +// chrome frame specilization of http server from net. +class ChromeFrameHTTPServer { + public: + void SetUp(); + void TearDown(); + bool WaitToFinish(int milliseconds); + GURL Resolve(const wchar_t* relative_url); + std::wstring GetDataDir(); + + HTTPTestServer* server() { + return server_; + } + + protected: + scoped_refptr<HTTPTestServer> server_; +}; + +#endif // CHROME_FRAME_TEST_HTTP_SERVER_H_ + diff --git a/chrome_frame/test/icu_stubs_unittests.cc b/chrome_frame/test/icu_stubs_unittests.cc new file mode 100644 index 0000000..4da4a40 --- /dev/null +++ b/chrome_frame/test/icu_stubs_unittests.cc @@ -0,0 +1,73 @@ +// Copyright (c) 2006-2009 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. + +#include "chrome_frame/test/chrome_frame_unittests.h" + +// Need to include these first since they're included +#include "base/logging.h" +#include "base/string_util.h" +#include "googleurl/src/url_canon.h" + +// Include the implementation of our stubs into a special namespace so that +// we can separate them from Chrome's implementation. +namespace icu_stubs { + // This struct is only to avoid build problems for the two googleurl stubs + // that currently are noops. + struct CanonOutputW { }; + + #include "chrome_frame/icu_stubs.cc" +} // namespace icu_stubs + +// anonymous namespace for test data. +namespace { + + // Test strings borrowed from base/string_util_unittest.cc + static const wchar_t* const kConvertRoundtripCases[] = { + L"", + L"Google Vid¯ôfY »" + L"\x7f51\x9875\x0020\x56fe\x7247\x0020\x8d44\x8baf\x66f4\x591a\x0020\x00bb", + // " ±³ºÌü¹¿Â ÃÄÌÂ" + L"\x03a0\x03b1\x03b3\x03ba\x03cc\x03c3\x03bc\x03b9" + L"\x03bf\x03c2\x0020\x0399\x03c3\x03c4\x03cc\x03c2", + // ">8A: AB@0=8F =0 @CAA:><" + L"\x041f\x043e\x0438\x0441\x043a\x0020\x0441\x0442" + L"\x0440\x0430\x043d\x0438\x0446\x0020\x043d\x0430" + L"\x0020\x0440\x0443\x0441\x0441\x043a\x043e\x043c", + // "È´ÌÁD¾¤Â" + L"\xc804\xccb4\xc11c\xbe44\xc2a4", + + // Test characters that take more than 16 bits. This will depend on whether + // wchar_t is 16 or 32 bits. + #if defined(WCHAR_T_IS_UTF16) + L"\xd800\xdf00", + // ????? (Mathematical Alphanumeric Symbols (U+011d40 - U+011d44 : A,B,C,D,E) + L"\xd807\xdd40\xd807\xdd41\xd807\xdd42\xd807\xdd43\xd807\xdd44", + #elif defined(WCHAR_T_IS_UTF32) + L"\x10300", + // ????? (Mathematical Alphanumeric Symbols (U+011d40 - U+011d44 : A,B,C,D,E) + L"\x11d40\x11d41\x11d42\x11d43\x11d44", + #endif + }; + +} // namespace + +TEST(IcuStubsTests, UTF8AndWideStubTest) { + // Test code borrowed from ConvertUTF8AndWide in base/string_util_unittest.cc. + + // The difference is that we want to make sure that our stubs work the same + // way as chrome's implementation of WideToUTF8 and UTF8ToWide. + for (size_t i = 0; i < arraysize(kConvertRoundtripCases); ++i) { + std::ostringstream utf8_base, utf8_stub; + utf8_base << WideToUTF8(kConvertRoundtripCases[i]); + utf8_stub << icu_stubs::WideToUTF8(kConvertRoundtripCases[i]); + + EXPECT_EQ(utf8_base.str(), utf8_stub.str()); + + std::wostringstream wide_base, wide_stub; + wide_base << UTF8ToWide(utf8_base.str()); + wide_stub << icu_stubs::UTF8ToWide(utf8_base.str()); + + EXPECT_EQ(wide_base.str(), wide_stub.str()); + } +} diff --git a/chrome_frame/test/net/dialog_watchdog.cc b/chrome_frame/test/net/dialog_watchdog.cc new file mode 100644 index 0000000..27a01a0 --- /dev/null +++ b/chrome_frame/test/net/dialog_watchdog.cc @@ -0,0 +1,146 @@ +// Copyright (c) 2009 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. + +#include <oleacc.h> + +#include "chrome_frame/test/net/dialog_watchdog.h" + +#include "base/logging.h" +#include "base/scoped_comptr_win.h" +#include "base/string_util.h" + +#include "chrome_frame/test/chrome_frame_test_utils.h" +#include "chrome_frame/function_stub.h" + +namespace { +// Uses the IAccessible interface for the window to set the focus. +// This can be useful when you don't have control over the thread that +// owns the window. +// NOTE: this depends on oleacc.lib which the net tests already depend on +// but other unit tests don't depend on oleacc so we can't just add the method +// directly into chrome_frame_test_utils.cc (without adding a +// #pragma comment(lib, "oleacc.lib")). +bool SetFocusToAccessibleWindow(HWND hwnd) { + bool ret = false; + ScopedComPtr<IAccessible> acc; + AccessibleObjectFromWindow(hwnd, OBJID_WINDOW, IID_IAccessible, + reinterpret_cast<void**>(acc.Receive())); + if (acc) { + VARIANT self = { VT_I4 }; + self.lVal = CHILDID_SELF; + ret = SUCCEEDED(acc->accSelect(SELFLAG_TAKEFOCUS, self)); + } + return ret; +} + +} // namespace + +SupplyProxyCredentials::SupplyProxyCredentials(const char* username, + const char* password) + : username_(username), password_(password) { +} + +bool SupplyProxyCredentials::OnDialogDetected(HWND hwnd, + const std::string& caption) { + // IE's dialog caption (en-US). + if (caption.compare("Windows Security") != 0) + return false; + + DialogProps props = {0}; + ::EnumChildWindows(hwnd, EnumChildren, reinterpret_cast<LPARAM>(&props)); + DCHECK(::IsWindow(props.username_)); + DCHECK(::IsWindow(props.password_)); + + // We can't use SetWindowText to set the username/password, so simulate + // keyboard input instead. + chrome_frame_test::ForceSetForegroundWindow(hwnd); + CHECK(SetFocusToAccessibleWindow(props.username_)); + chrome_frame_test::SendString(username_.c_str()); + Sleep(100); + + chrome_frame_test::SendVirtualKey(VK_TAB); + Sleep(100); + chrome_frame_test::SendString(password_.c_str()); + + Sleep(100); + chrome_frame_test::SendVirtualKey(VK_RETURN); + + return true; +} + +// static +BOOL SupplyProxyCredentials::EnumChildren(HWND hwnd, LPARAM param) { + if (!::IsWindowVisible(hwnd)) + return TRUE; // Ignore but continue to enumerate. + + DialogProps* props = reinterpret_cast<DialogProps*>(param); + + char class_name[MAX_PATH] = {0}; + ::GetClassNameA(hwnd, class_name, arraysize(class_name)); + if (lstrcmpiA(class_name, "Edit") == 0) { + if (props->username_ == NULL || props->username_ == hwnd) { + props->username_ = hwnd; + } else if (props->password_ == NULL) { + props->password_ = hwnd; + } + } else { + EnumChildWindows(hwnd, EnumChildren, param); + } + + return TRUE; +} + +DialogWatchdog::DialogWatchdog() : hook_(NULL), hook_stub_(NULL) { + Initialize(); +} + +DialogWatchdog::~DialogWatchdog() { + Uninitialize(); +} + +bool DialogWatchdog::Initialize() { + DCHECK(hook_ == NULL); + DCHECK(hook_stub_ == NULL); + hook_stub_ = FunctionStub::Create(reinterpret_cast<uintptr_t>(this), + WinEventHook); + hook_ = SetWinEventHook(EVENT_OBJECT_SHOW, EVENT_OBJECT_SHOW, NULL, + reinterpret_cast<WINEVENTPROC>(hook_stub_->code()), 0, + 0, WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS); + + return hook_ != NULL; +} + +void DialogWatchdog::Uninitialize() { + if (hook_) { + ::UnhookWinEvent(hook_); + hook_ = NULL; + FunctionStub::Destroy(hook_stub_); + hook_stub_ = NULL; + } +} + +// static +void DialogWatchdog::WinEventHook(DialogWatchdog* me, HWINEVENTHOOK hook, + DWORD event, HWND hwnd, LONG object_id, + LONG child_id, DWORD event_thread_id, + DWORD event_time) { + // Check for a dialog class ("#32770") and notify observers if we find one. + char class_name[MAX_PATH] = {0}; + ::GetClassNameA(hwnd, class_name, arraysize(class_name)); + if (lstrcmpA(class_name, "#32770") == 0) { + int len = ::GetWindowTextLength(hwnd); + std::string text; + ::GetWindowTextA(hwnd, WriteInto(&text, len + 1), len + 1); + me->OnDialogFound(hwnd, text); + } +} + +void DialogWatchdog::OnDialogFound(HWND hwnd, const std::string& caption) { + std::vector<DialogWatchdogObserver*>::iterator it = observers_.begin(); + while (it != observers_.end()) { + if ((*it)->OnDialogDetected(hwnd, caption)) + break; + it++; + } +} diff --git a/chrome_frame/test/net/dialog_watchdog.h b/chrome_frame/test/net/dialog_watchdog.h new file mode 100644 index 0000000..dfb8989 --- /dev/null +++ b/chrome_frame/test/net/dialog_watchdog.h @@ -0,0 +1,64 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_FRAME_TEST_NET_DIALOG_WATCHDOG_H_ +#define CHROME_FRAME_TEST_NET_DIALOG_WATCHDOG_H_ + +#include <windows.h> + +#include <string> +#include <vector> + +struct FunctionStub; + +class DialogWatchdogObserver { // NOLINT + public: + // returns true if this observer handled the dialog. + virtual bool OnDialogDetected(HWND hwnd, const std::string& caption) = 0; +}; + +class SupplyProxyCredentials : public DialogWatchdogObserver { + public: + SupplyProxyCredentials(const char* username, const char* password); + + protected: + struct DialogProps { + HWND username_; + HWND password_; + }; + + virtual bool OnDialogDetected(HWND hwnd, const std::string& caption); + static BOOL CALLBACK EnumChildren(HWND hwnd, LPARAM param); + + protected: + std::string username_; + std::string password_; +}; + +class DialogWatchdog { + public: + DialogWatchdog(); + ~DialogWatchdog(); + + inline void AddObserver(DialogWatchdogObserver* observer) { + observers_.push_back(observer); + } + + bool Initialize(); + void Uninitialize(); + + protected: + static void CALLBACK WinEventHook(DialogWatchdog* me, HWINEVENTHOOK hook, + DWORD event, HWND hwnd, LONG object_id, LONG child_id, + DWORD event_thread_id, DWORD event_time); + + void OnDialogFound(HWND hwnd, const std::string& caption); + + protected: + HWINEVENTHOOK hook_; + std::vector<DialogWatchdogObserver*> observers_; + FunctionStub* hook_stub_; +}; + +#endif // CHROME_FRAME_TEST_NET_DIALOG_WATCHDOG_H_ diff --git a/chrome_frame/test/net/fake_external_tab.cc b/chrome_frame/test/net/fake_external_tab.cc new file mode 100644 index 0000000..eebd2d9b --- /dev/null +++ b/chrome_frame/test/net/fake_external_tab.cc @@ -0,0 +1,391 @@ +// Copyright (c) 2009 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. + +#include "chrome_frame/test/net/fake_external_tab.h" + +#include <exdisp.h> + +#include "app/app_paths.h" +#include "app/resource_bundle.h" +#include "app/win_util.h" + +#include "base/command_line.h" +#include "base/file_util.h" +#include "base/icu_util.h" +#include "base/path_service.h" +#include "base/scoped_bstr_win.h" +#include "base/scoped_comptr_win.h" +#include "base/scoped_variant_win.h" + +#include "chrome/browser/browser_prefs.h" +#include "chrome/browser/chrome_thread.h" +#include "chrome/browser/process_singleton.h" +#include "chrome/browser/profile_manager.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/pref_names.h" +#include "chrome/browser/renderer_host/render_process_host.h" + +#include "chrome_frame/utils.h" +#include "chrome_frame/test/chrome_frame_test_utils.h" +#include "chrome_frame/test/net/dialog_watchdog.h" +#include "chrome_frame/test/net/test_automation_resource_message_filter.h" + +namespace { + +// A special command line switch to allow developers to manually launch the +// browser and debug CF inside the browser. +const wchar_t kManualBrowserLaunch[] = L"manual-browser"; + +// Pops up a message box after the test environment has been set up +// and before tearing it down. Useful for when debugging tests and not +// the test environment that's been set up. +const wchar_t kPromptAfterSetup[] = L"prompt-after-setup"; + +const int kTestServerPort = 4666; +// The test HTML we use to initialize Chrome Frame. +// Note that there's a little trick in there to avoid an extra URL request +// that the browser will otherwise make for the site's favicon. +// If we don't do this the browser will create a new URL request after +// the CF page has been initialized and that URL request will confuse the +// global URL instance counter in the unit tests and subsequently trip +// some DCHECKs. +const char kChromeFrameHtml[] = "<html><head>" + "<meta http-equiv=\"X-UA-Compatible\" content=\"chrome=1\" />" + "<link rel=\"shortcut icon\" href=\"file://c:\\favicon.ico\"/>" + "</head><body>Chrome Frame should now be loaded</body></html>"; + +bool ShouldLaunchBrowser() { + return !CommandLine::ForCurrentProcess()->HasSwitch(kManualBrowserLaunch); +} + +bool PromptAfterSetup() { + return CommandLine::ForCurrentProcess()->HasSwitch(kPromptAfterSetup); +} + +} // end namespace + +FakeExternalTab::FakeExternalTab() { + PathService::Get(chrome::DIR_USER_DATA, &overridden_user_dir_); + user_data_dir_ = FilePath::FromWStringHack(GetProfilePath()); + PathService::Override(chrome::DIR_USER_DATA, user_data_dir_); + process_singleton_.reset(new ProcessSingleton(user_data_dir_)); +} + +FakeExternalTab::~FakeExternalTab() { + if (!overridden_user_dir_.empty()) { + PathService::Override(chrome::DIR_USER_DATA, overridden_user_dir_); + } +} + +std::wstring FakeExternalTab::GetProfileName() { + return L"iexplore"; +} + +std::wstring FakeExternalTab::GetProfilePath() { + std::wstring path; + GetUserProfileBaseDirectory(&path); + file_util::AppendToPath(&path, GetProfileName()); + return path; +} + +void FakeExternalTab::Initialize() { + DCHECK(g_browser_process == NULL); + + // The gears plugin causes the PluginRequestInterceptor to kick in and it + // will cause problems when it tries to intercept URL requests. + PathService::Override(chrome::FILE_GEARS_PLUGIN, FilePath()); + + icu_util::Initialize(); + + chrome::RegisterPathProvider(); + app::RegisterPathProvider(); + + ResourceBundle::InitSharedInstance(L"en-US"); + ResourceBundle::GetSharedInstance().LoadThemeResources(); + + const CommandLine* cmd = CommandLine::ForCurrentProcess(); + browser_process_.reset(new BrowserProcessImpl(*cmd)); + RenderProcessHost::set_run_renderer_in_process(true); + // BrowserProcessImpl's constructor should set g_browser_process. + DCHECK(g_browser_process); + + Profile* profile = g_browser_process->profile_manager()-> + GetDefaultProfile(FilePath(user_data())); + PrefService* prefs = profile->GetPrefs(); + PrefService* local_state = browser_process_->local_state(); + local_state->RegisterStringPref(prefs::kApplicationLocale, L""); + local_state->RegisterBooleanPref(prefs::kMetricsReportingEnabled, false); + + browser::RegisterAllPrefs(prefs, local_state); + + // Override some settings to avoid hitting some preferences that have not + // been registered. + prefs->SetBoolean(prefs::kPasswordManagerEnabled, false); + prefs->SetBoolean(prefs::kAlternateErrorPagesEnabled, false); + prefs->SetBoolean(prefs::kSafeBrowsingEnabled, false); + + profile->InitExtensions(); +} + +void FakeExternalTab::Shutdown() { + browser_process_.reset(); + g_browser_process = NULL; + process_singleton_.reset(); + + ResourceBundle::CleanupSharedInstance(); +} + +CFUrlRequestUnittestRunner::CFUrlRequestUnittestRunner(int argc, char** argv) + : NetTestSuite(argc, argv), + chrome_frame_html_("/chrome_frame", kChromeFrameHtml) { + fake_chrome_.Initialize(); + pss_subclass_.reset(new ProcessSingletonSubclass(this)); + EXPECT_TRUE(pss_subclass_->Subclass(fake_chrome_.user_data())); + StartChromeFrameInHostBrowser(); +} + +CFUrlRequestUnittestRunner::~CFUrlRequestUnittestRunner() { + fake_chrome_.Shutdown(); +} + +DWORD WINAPI NavigateIE(void* param) { + return 0; + win_util::ScopedCOMInitializer com; + BSTR url = reinterpret_cast<BSTR>(param); + + bool found = false; + int retries = 0; + const int kMaxRetries = 20; + while (!found && retries < kMaxRetries) { + ScopedComPtr<IShellWindows> windows; + HRESULT hr = ::CoCreateInstance(__uuidof(ShellWindows), NULL, CLSCTX_ALL, + IID_IShellWindows, reinterpret_cast<void**>(windows.Receive())); + DCHECK(SUCCEEDED(hr)) << "CoCreateInstance"; + + if (SUCCEEDED(hr)) { + hr = HRESULT_FROM_WIN32(ERROR_NOT_FOUND); + long count = 0; // NOLINT + windows->get_Count(&count); + VARIANT i = { VT_I4 }; + for (i.lVal = 0; i.lVal < count; ++i.lVal) { + ScopedComPtr<IDispatch> folder; + windows->Item(i, folder.Receive()); + if (folder != NULL) { + ScopedComPtr<IWebBrowser2> browser; + if (SUCCEEDED(browser.QueryFrom(folder))) { + found = true; + browser->Stop(); + Sleep(1000); + VARIANT empty = ScopedVariant::kEmptyVariant; + hr = browser->Navigate(url, &empty, &empty, &empty, &empty); + DCHECK(SUCCEEDED(hr)) << "Failed to navigate"; + break; + } + } + } + } + if (!found) { + DLOG(INFO) << "Waiting for browser to initialize..."; + ::Sleep(100); + retries++; + } + } + + DCHECK(retries < kMaxRetries); + DCHECK(found); + + ::SysFreeString(url); + + return 0; +} + +void CFUrlRequestUnittestRunner::StartChromeFrameInHostBrowser() { + if (!ShouldLaunchBrowser()) + return; + + win_util::ScopedCOMInitializer com; + chrome_frame_test::CloseAllIEWindows(); + + test_http_server_.reset(new test_server::SimpleWebServer(kTestServerPort)); + test_http_server_->AddResponse(&chrome_frame_html_); + std::wstring url(StringPrintf(L"http://localhost:%i/chrome_frame", + kTestServerPort).c_str()); + + // Launch IE. This launches IE correctly on Vista too. + ScopedHandle ie_process(chrome_frame_test::LaunchIE(url)); + EXPECT_TRUE(ie_process.IsValid()); + + // NOTE: If you're running IE8 and CF is not being loaded, you need to + // disable IE8's prebinding until CF properly handles that situation. + // + // HKCU\Software\Microsoft\Internet Explorer\Main + // Value name: EnablePreBinding (REG_DWORD) + // Value: 0 +} + +void CFUrlRequestUnittestRunner::ShutDownHostBrowser() { + if (ShouldLaunchBrowser()) { + win_util::ScopedCOMInitializer com; + chrome_frame_test::CloseAllIEWindows(); + } +} + +// Override virtual void Initialize to not call icu initialize +void CFUrlRequestUnittestRunner::Initialize() { + DCHECK(::GetCurrentThreadId() == test_thread_id_); + + // Start by replicating some of the steps that would otherwise be + // done by TestSuite::Initialize. We can't call the base class + // directly because it will attempt to initialize some things such as + // ICU that have already been initialized for this process. + InitializeLogging(); + base::Time::EnableHiResClockForTests(); + +#if !defined(PURIFY) && defined(OS_WIN) + logging::SetLogAssertHandler(UnitTestAssertHandler); +#endif // !defined(PURIFY) + + // Next, do some initialization for NetTestSuite. + NetTestSuite::InitializeTestThread(); +} + +void CFUrlRequestUnittestRunner::Shutdown() { + DCHECK(::GetCurrentThreadId() == test_thread_id_); + NetTestSuite::Shutdown(); +} + +void CFUrlRequestUnittestRunner::OnConnectAutomationProviderToChannel( + const std::string& channel_id) { + Profile* profile = g_browser_process->profile_manager()-> + GetDefaultProfile(fake_chrome_.user_data()); + + AutomationProviderList* list = + g_browser_process->InitAutomationProviderList(); + DCHECK(list); + list->AddProvider(TestAutomationProvider::NewAutomationProvider(profile, + channel_id, this)); +} + +void CFUrlRequestUnittestRunner::OnInitialTabLoaded() { + test_http_server_.reset(); + StartTests(); +} + +void CFUrlRequestUnittestRunner::RunMainUIThread() { + DCHECK(MessageLoop::current()); + DCHECK(MessageLoop::current()->type() == MessageLoop::TYPE_UI); + + // Register the main thread by instantiating it, but don't call any methods. + ChromeThread main_thread; + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + + MessageLoop::current()->Run(); +} + +void CFUrlRequestUnittestRunner::StartTests() { + if (PromptAfterSetup()) + MessageBoxA(NULL, "click ok to run", "", MB_OK); + + DCHECK_EQ(test_thread_.IsValid(), false); + test_thread_.Set(::CreateThread(NULL, 0, RunAllUnittests, this, 0, + &test_thread_id_)); + DCHECK(test_thread_.IsValid()); +} + +// static +DWORD CFUrlRequestUnittestRunner::RunAllUnittests(void* param) { + PlatformThread::SetName("CFUrlRequestUnittestRunner"); + CFUrlRequestUnittestRunner* me = + reinterpret_cast<CFUrlRequestUnittestRunner*>(param); + me->Run(); + me->fake_chrome_.ui_loop()->PostTask(FROM_HERE, + NewRunnableFunction(TakeDownBrowser, me)); + return 0; +} + +// static +void CFUrlRequestUnittestRunner::TakeDownBrowser( + CFUrlRequestUnittestRunner* me) { + if (PromptAfterSetup()) + MessageBoxA(NULL, "click ok to exit", "", MB_OK); + + me->ShutDownHostBrowser(); +} + +void CFUrlRequestUnittestRunner::InitializeLogging() { + FilePath exe; + PathService::Get(base::FILE_EXE, &exe); + FilePath log_filename = exe.ReplaceExtension(FILE_PATH_LITERAL("log")); + logging::InitLogging(log_filename.value().c_str(), + logging::LOG_TO_BOTH_FILE_AND_SYSTEM_DEBUG_LOG, + logging::LOCK_LOG_FILE, + logging::DELETE_OLD_LOG_FILE); + // We want process and thread IDs because we may have multiple processes. + // Note: temporarily enabled timestamps in an effort to catch bug 6361. + logging::SetLogItems(true, true, true, true); +} + +void FilterDisabledTests() { + if (::testing::FLAGS_gtest_filter.GetLength() && + ::testing::FLAGS_gtest_filter.Compare("*") != 0) { + // Don't override user specified filters. + return; + } + + const char* disabled_tests[] = { + // Tests disabled since they're testing the same functionality used + // by the TestAutomationProvider. + "URLRequestTest.InterceptNetworkError",
+ "URLRequestTest.InterceptRestartRequired",
+ "URLRequestTest.InterceptRespectsCancelMain",
+ "URLRequestTest.InterceptRespectsCancelRedirect",
+ "URLRequestTest.InterceptRespectsCancelFinal",
+ "URLRequestTest.InterceptRespectsCancelInRestart", + "URLRequestTest.InterceptRedirect", + "URLRequestTest.InterceptServerError", + "URLRequestTestFTP.*", + + // Tests that are currently not working: + + // Temporarily disabled because they needs user input (login dialog). + "URLRequestTestHTTP.BasicAuth", + "URLRequestTestHTTP.BasicAuthWithCookies", + + // HTTPS tests temporarily disabled due to the certificate error dialog. + // TODO(tommi): The tests currently fail though, so need to fix. + "HTTPSRequestTest.HTTPSMismatchedTest", + "HTTPSRequestTest.HTTPSExpiredTest", + + // Tests chrome's network stack's cache (might not apply to CF). + "URLRequestTestHTTP.VaryHeader", + + // I suspect we can only get this one to work (if at all) on IE8 and + // later by using the new INTERNET_OPTION_SUPPRESS_BEHAVIOR flags + // See http://msdn.microsoft.com/en-us/library/aa385328(VS.85).aspx + "URLRequestTest.DoNotSaveCookies", + }; + + std::string filter("-"); // All following filters will be negative. + for (int i = 0; i < arraysize(disabled_tests); ++i) { + if (i > 0) + filter += ":"; + filter += disabled_tests[i]; + } + + ::testing::FLAGS_gtest_filter = filter; +} + +int main(int argc, char** argv) { + DialogWatchdog watchdog; + // See url_request_unittest.cc for these credentials. + SupplyProxyCredentials credentials("user", "secret"); + watchdog.AddObserver(&credentials); + testing::InitGoogleTest(&argc, argv); + FilterDisabledTests(); + CFUrlRequestUnittestRunner test_suite(argc, argv); + test_suite.RunMainUIThread(); + return 0; +} diff --git a/chrome_frame/test/net/fake_external_tab.h b/chrome_frame/test/net/fake_external_tab.h new file mode 100644 index 0000000..6ce4f93 --- /dev/null +++ b/chrome_frame/test/net/fake_external_tab.h @@ -0,0 +1,106 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_FRAME_TEST_NET_FAKE_EXTERNAL_TAB_H_ +#define CHROME_FRAME_TEST_NET_FAKE_EXTERNAL_TAB_H_ + +#include <string> + +#include "base/file_path.h" +#include "base/message_loop.h" + +#include "chrome/app/scoped_ole_initializer.h" +#include "chrome/browser/browser_process_impl.h" + +#include "chrome_frame/test/test_server.h" +#include "chrome_frame/test/net/test_automation_provider.h" +#include "chrome_frame/test/net/process_singleton_subclass.h" + +#include "net/base/net_test_suite.h" + +class ProcessSingleton; + +class FakeExternalTab { + public: + FakeExternalTab(); + ~FakeExternalTab(); + + virtual std::wstring GetProfileName(); + + virtual std::wstring GetProfilePath(); + virtual void Initialize(); + virtual void Shutdown(); + + const FilePath& user_data() const { + return user_data_dir_; + } + + MessageLoopForUI* ui_loop() { + return &loop_; + } + + protected: + MessageLoopForUI loop_; + scoped_ptr<BrowserProcess> browser_process_; + FilePath overridden_user_dir_; + FilePath user_data_dir_; + ScopedOleInitializer ole_initializer_; // For RegisterDropTarget etc to work. + scoped_ptr<ProcessSingleton> process_singleton_; +}; + +// The "master class" that spins the UI and test threads. +class CFUrlRequestUnittestRunner + : public NetTestSuite, + public ProcessSingletonSubclassDelegate, + public TestAutomationProviderDelegate { + public: + CFUrlRequestUnittestRunner(int argc, char** argv); + ~CFUrlRequestUnittestRunner(); + + virtual void StartChromeFrameInHostBrowser(); + + virtual void ShutDownHostBrowser(); + + // Overrides to not call icu initialize + virtual void Initialize(); + virtual void Shutdown(); + + // ProcessSingletonSubclassDelegate. + virtual void OnConnectAutomationProviderToChannel( + const std::string& channel_id); + + // TestAutomationProviderDelegate. + virtual void OnInitialTabLoaded(); + + void RunMainUIThread(); + + void StartTests(); + + protected: + // This is the thread that runs all the UrlRequest tests. + // Within its context, the Initialize() and Shutdown() routines above + // will be called. + static DWORD WINAPI RunAllUnittests(void* param); + + static void TakeDownBrowser(CFUrlRequestUnittestRunner* me); + + protected: + // Borrowed from TestSuite::Initialize(). + void InitializeLogging(); + + protected: + ScopedHandle test_thread_; + DWORD test_thread_id_; + scoped_ptr<MessageLoop> test_thread_message_loop_; + + scoped_ptr<test_server::SimpleWebServer> test_http_server_; + test_server::SimpleResponse chrome_frame_html_; + + // The fake chrome instance. This instance owns the UI message loop + // on the main thread. + FakeExternalTab fake_chrome_; + scoped_ptr<ProcessSingletonSubclass> pss_subclass_; +}; + +#endif // CHROME_FRAME_TEST_NET_FAKE_EXTERNAL_TAB_H_ diff --git a/chrome_frame/test/net/process_singleton_subclass.cc b/chrome_frame/test/net/process_singleton_subclass.cc new file mode 100644 index 0000000..2206a74 --- /dev/null +++ b/chrome_frame/test/net/process_singleton_subclass.cc @@ -0,0 +1,111 @@ +// Copyright (c) 2009 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. + +#include "chrome_frame/test/net/process_singleton_subclass.h" + +#include "base/command_line.h" +#include "base/path_service.h" +#include "base/string_util.h" +#include "chrome/browser/browser_process_impl.h" +#include "chrome/browser/profile_manager.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/chrome_switches.h" +#include "chrome_frame/test/net/test_automation_provider.h" +#include "chrome_frame/function_stub.h" +#include "testing/gtest/include/gtest/gtest.h" + +ProcessSingletonSubclass::ProcessSingletonSubclass( + ProcessSingletonSubclassDelegate* delegate) + : stub_(NULL), delegate_(delegate), original_wndproc_(NULL) { +} + +ProcessSingletonSubclass::~ProcessSingletonSubclass() { + if (stub_) { + stub_->BypassStub(reinterpret_cast<void*>(original_wndproc_)); + } +} + +bool ProcessSingletonSubclass::Subclass(const FilePath& user_data_dir) { + DCHECK(stub_ == NULL); + DCHECK(original_wndproc_ == NULL); + HWND hwnd = FindWindowEx(HWND_MESSAGE, NULL, chrome::kMessageWindowClass, + user_data_dir.ToWStringHack().c_str()); + if (!::IsWindow(hwnd)) + return false; + + // The window must be in this process for us to be able to subclass it. + DWORD pid = 0; + ::GetWindowThreadProcessId(hwnd, &pid); + EXPECT_EQ(pid, ::GetCurrentProcessId()); + + original_wndproc_ = reinterpret_cast<WNDPROC>(::GetWindowLongPtr(hwnd, + GWLP_WNDPROC)); + stub_ = FunctionStub::Create(reinterpret_cast<uintptr_t>(this), + &SubclassWndProc); + DCHECK(stub_); + ::SetWindowLongPtr(hwnd, GWLP_WNDPROC, + reinterpret_cast<LONG_PTR>(stub_->code())); + return true; +} + +// static +LRESULT ProcessSingletonSubclass::SubclassWndProc(ProcessSingletonSubclass* me, + HWND hwnd, UINT msg, + WPARAM wp, LPARAM lp) { + switch (msg) { + case WM_COPYDATA: + return me->OnCopyData(hwnd, reinterpret_cast<HWND>(wp), + reinterpret_cast<COPYDATASTRUCT*>(lp)); + default: + break; + } + + return me->original_wndproc_(hwnd, msg, wp, lp); +} + +// static +LRESULT ProcessSingletonSubclass::OnCopyData(HWND hwnd, HWND from_hwnd, + const COPYDATASTRUCT* cds) { + // We should have enough room for the shortest command (min_message_size) + // and also be a multiple of wchar_t bytes. The shortest command + // possible is L"START\0\0" (empty current directory and command line). + static const int kMinMessageSize = sizeof(L"START\0"); + EXPECT_TRUE(kMinMessageSize <= cds->cbData); + + if (kMinMessageSize > cds->cbData) + return TRUE; + + // We split the string into 4 parts on NULLs. + const wchar_t* begin = reinterpret_cast<const wchar_t*>(cds->lpData); + const wchar_t* end = begin + (cds->cbData / sizeof(wchar_t)); + const wchar_t kNull = L'\0'; + const wchar_t* eos = wmemchr(begin, kNull, end - begin); + EXPECT_NE(eos, end); + if (lstrcmpW(begin, L"START") == 0) { + begin = eos + 1; + EXPECT_TRUE(begin <= end); + eos = wmemchr(begin, kNull, end - begin); + EXPECT_NE(eos, end); + + // Get current directory. + const wchar_t* cur_dir = begin; + begin = eos + 1; + EXPECT_TRUE(begin <= end); + eos = wmemchr(begin, kNull, end - begin); + // eos might be equal to end at this point. + + // Get command line. + std::wstring cmd_line(begin, static_cast<size_t>(end - begin)); + + CommandLine parsed_command_line(L""); + parsed_command_line.ParseFromString(cmd_line); + std::string channel_id(WideToASCII(parsed_command_line.GetSwitchValue( + switches::kAutomationClientChannelID))); + EXPECT_FALSE(channel_id.empty()); + + delegate_->OnConnectAutomationProviderToChannel(channel_id); + } + return TRUE; +} diff --git a/chrome_frame/test/net/process_singleton_subclass.h b/chrome_frame/test/net/process_singleton_subclass.h new file mode 100644 index 0000000..dd9ce36 --- /dev/null +++ b/chrome_frame/test/net/process_singleton_subclass.h @@ -0,0 +1,36 @@ +// Copyright (c) 2009 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. +#ifndef CHROME_FRAME_TEST_NET_PROCESS_SINGLETON_SUBCLASS_H_ +#define CHROME_FRAME_TEST_NET_PROCESS_SINGLETON_SUBCLASS_H_ + +#include <windows.h> +#include "base/file_path.h" + +struct FunctionStub; + +class ProcessSingletonSubclassDelegate { + public: + virtual void OnConnectAutomationProviderToChannel( + const std::string& channel_id) = 0; +}; + +class ProcessSingletonSubclass { + public: + ProcessSingletonSubclass(ProcessSingletonSubclassDelegate* delegate); + ~ProcessSingletonSubclass(); + + bool Subclass(const FilePath& user_data_dir); + + protected: + static LRESULT CALLBACK SubclassWndProc(ProcessSingletonSubclass* me, + HWND hwnd, UINT msg, WPARAM wp, LPARAM lp); + LRESULT OnCopyData(HWND hwnd, HWND from_hwnd, const COPYDATASTRUCT* cds); + protected: + FunctionStub* stub_; + ProcessSingletonSubclassDelegate* delegate_; + WNDPROC original_wndproc_; +}; + +#endif // CHROME_FRAME_TEST_NET_PROCESS_SINGLETON_SUBCLASS_H_ + diff --git a/chrome_frame/test/net/test_automation_provider.cc b/chrome_frame/test/net/test_automation_provider.cc new file mode 100644 index 0000000..3a56aa4 --- /dev/null +++ b/chrome_frame/test/net/test_automation_provider.cc @@ -0,0 +1,89 @@ +// Copyright (c) 2009 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. + +#include "chrome_frame/test/net/test_automation_provider.h" + +#include "base/command_line.h" +#include "chrome/test/automation/automation_messages.h" + +#include "chrome_frame/test/net/test_automation_resource_message_filter.h" + +namespace { + +// A special command line switch to just run the unit tests without CF in +// the picture. Can be useful when the harness itself needs to be debugged. +const wchar_t kNoCfTestRun[] = L"no-cf-test-run"; + +bool CFTestsDisabled() { + static bool switch_present = CommandLine::ForCurrentProcess()-> + HasSwitch(kNoCfTestRun); + return switch_present; +} + +} // end namespace + +TestAutomationProvider::TestAutomationProvider( + Profile* profile, + TestAutomationProviderDelegate* delegate) + : AutomationProvider(profile), tab_handle_(-1), delegate_(delegate) { + filter_ = new TestAutomationResourceMessageFilter(this); + URLRequest::RegisterRequestInterceptor(this); +} + +TestAutomationProvider::~TestAutomationProvider() { + URLRequest::UnregisterRequestInterceptor(this); +} + +void TestAutomationProvider::OnMessageReceived(const IPC::Message& msg) { + if (filter_->OnMessageReceived(msg)) + return; // Message handled by the filter. + + __super::OnMessageReceived(msg); +} + +// IPC override to grab the tab handle. +bool TestAutomationProvider::Send(IPC::Message* msg) { + if (msg->type() == AutomationMsg_TabLoaded::ID) { + DCHECK(tab_handle_ == -1) << "Currently only support one tab"; + void* iter = NULL; + CHECK(msg->ReadInt(&iter, &tab_handle_)); + DLOG(INFO) << "Got tab handle: " << tab_handle_; + DCHECK(tab_handle_ != -1 && tab_handle_ != 0); + delegate_->OnInitialTabLoaded(); + } + + return AutomationProvider::Send(msg); +} + +URLRequestJob* TestAutomationProvider::MaybeIntercept(URLRequest* request) { + if (CFTestsDisabled()) + return NULL; + + if (request->url().SchemeIs("http") || request->url().SchemeIs("https")) { + // Only look at requests that don't have any user data. + // ResourceDispatcherHost uses the user data for requests that it manages. + // We don't want to mess with those. + + // We could also check if the current thread is our TestUrlRequest thread + // and only intercept requests that belong to that thread. + if (request->GetUserData(NULL) == NULL) { + DCHECK(tab_handle_ != -1); + URLRequestAutomationJob* job = new URLRequestAutomationJob(request, + tab_handle_, filter_); + return job; + } + } + + return NULL; +} + +// static +TestAutomationProvider* TestAutomationProvider::NewAutomationProvider( + Profile* p, const std::string& channel, + TestAutomationProviderDelegate* delegate) { + TestAutomationProvider* automation = new TestAutomationProvider(p, delegate); + automation->ConnectToChannel(channel); + automation->SetExpectedTabCount(1); + return automation; +} diff --git a/chrome_frame/test/net/test_automation_provider.h b/chrome_frame/test/net/test_automation_provider.h new file mode 100644 index 0000000..75830ba --- /dev/null +++ b/chrome_frame/test/net/test_automation_provider.h @@ -0,0 +1,52 @@ +// Copyright (c) 2009 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. +#ifndef CHROME_FRAME_TEST_NET_TEST_AUTOMATION_PROVIDER_H_ +#define CHROME_FRAME_TEST_NET_TEST_AUTOMATION_PROVIDER_H_ + +#include "chrome/browser/automation/automation_provider.h" + +class TestAutomationResourceMessageFilter; + +// Callback interface for TestAutomationProvider. +class TestAutomationProviderDelegate { + public: + virtual void OnInitialTabLoaded() = 0; +}; + +// A slightly customized version of AutomationProvider. +// We override AutomationProvider to be able to filter received messages +// (see TestAutomationResourceMessageFilter) and know when the initial +// ExternalTab has been loaded. +// In order to intercept UrlRequests and make the URLRequestAutomationJob class +// handle requests from unit tests, we also implement URLRequest::Interceptor. +class TestAutomationProvider + : public AutomationProvider, + public URLRequest::Interceptor { + public: + explicit TestAutomationProvider(Profile* profile, + TestAutomationProviderDelegate* delegate); + + virtual ~TestAutomationProvider(); + + // AutomationProvider overrides. + virtual void OnMessageReceived(const IPC::Message& msg); + virtual bool Send(IPC::Message* msg); + + // URLRequest::Interceptor. + virtual URLRequestJob* MaybeIntercept(URLRequest* request); + + // Call to instantiate and initialize a new instance of + // TestAutomationProvider. + static TestAutomationProvider* NewAutomationProvider( + Profile* p, + const std::string& channel, + TestAutomationProviderDelegate* delegate); + + protected: + scoped_refptr<TestAutomationResourceMessageFilter> filter_; + int tab_handle_; + TestAutomationProviderDelegate* delegate_; +}; + +#endif CHROME_FRAME_TEST_NET_TEST_AUTOMATION_PROVIDER_H_ diff --git a/chrome_frame/test/net/test_automation_resource_message_filter.cc b/chrome_frame/test/net/test_automation_resource_message_filter.cc new file mode 100644 index 0000000..32ef532 --- /dev/null +++ b/chrome_frame/test/net/test_automation_resource_message_filter.cc @@ -0,0 +1,60 @@ +// Copyright (c) 2009 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. + +#include "chrome_frame/test/net/test_automation_resource_message_filter.h" + +TestAutomationResourceMessageFilter::TestAutomationResourceMessageFilter( + AutomationProvider* automation) : automation_(automation) { +} + +bool TestAutomationResourceMessageFilter::Send(IPC::Message* message) { + return automation_->Send(message); +} + +// static +void TestAutomationResourceMessageFilter::OnRequestMessage( + URLRequestAutomationJob* job, IPC::Message* msg) { + job->OnMessage(*msg); + delete msg; +} + +bool TestAutomationResourceMessageFilter::OnMessageReceived( + const IPC::Message& message) { + // See if we want to process this message... call the base class + // for filter messages, send the message to the correct thread + // for URL requests. + bool handled = false; + int request_id = URLRequestAutomationJob::MayFilterMessage(message); + if (request_id) { + RequestMap::iterator it = requests_.find(request_id); + if (it != requests_.end()) { + handled = true; + IPC::Message* msg = new IPC::Message(message); + RequestJob& job = it->second; + job.loop_->PostTask(FROM_HERE, NewRunnableFunction(OnRequestMessage, + job.job_, msg)); + } + } else { + handled = AutomationResourceMessageFilter::OnMessageReceived(message); + } + return handled; +} + +// Add request to the list of outstanding requests. +bool TestAutomationResourceMessageFilter::RegisterRequest( + URLRequestAutomationJob* job) { + // Store the request in an internal map like the parent class + // does, but also store the current loop pointer so we can send + // request messages to that loop. + DCHECK(requests_.end() == requests_.find(job->id())); + RequestJob request_job = { MessageLoop::current(), job }; + requests_[job->id()] = request_job; + return true; +} + +// Remove request from the list of outstanding requests. +void TestAutomationResourceMessageFilter::UnRegisterRequest( + URLRequestAutomationJob* job) { + requests_.erase(job->id()); +} diff --git a/chrome_frame/test/net/test_automation_resource_message_filter.h b/chrome_frame/test/net/test_automation_resource_message_filter.h new file mode 100644 index 0000000..310307a --- /dev/null +++ b/chrome_frame/test/net/test_automation_resource_message_filter.h @@ -0,0 +1,50 @@ +// Copyright (c) 2009 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. +#ifndef CHROME_FRAME_TEST_NET_TEST_AUTOMATION_RESOURCE_MESSAGE_FILTER_H_ +#define CHROME_FRAME_TEST_NET_TEST_AUTOMATION_RESOURCE_MESSAGE_FILTER_H_ + +#include "chrome/browser/automation/automation_provider.h" +#include "chrome/browser/automation/url_request_automation_job.h" + +// Performs the same duties as AutomationResourceMessageFilter but with one +// difference. Instead of being tied to an IPC channel running on Chrome's +// IO thread, this instance runs on the unit test's IO thread (all URL request +// tests have their own IO loop) and is tied to an instance of +// AutomationProvider (TestAutomationProvider to be exact). +// +// Messages from the AutomationProvider that are destined to request objects +// owned by this class are marshaled over to the request's IO thread instead +// of using the thread the messages are received on. This way we allow the +// URL request tests to run sequentially as they were written while still +// allowing the automation layer to work as it normally does (i.e. process +// its messages on the receiving thread). +class TestAutomationResourceMessageFilter + : public AutomationResourceMessageFilter { + public: + TestAutomationResourceMessageFilter(AutomationProvider* automation); + + virtual bool Send(IPC::Message* message); + + static void OnRequestMessage(URLRequestAutomationJob* job, + IPC::Message* msg); + + virtual bool OnMessageReceived(const IPC::Message& message); + + // Add request to the list of outstanding requests. + virtual bool RegisterRequest(URLRequestAutomationJob* job); + // Remove request from the list of outstanding requests. + virtual void UnRegisterRequest(URLRequestAutomationJob* job); + + protected: + AutomationProvider* automation_; + // declare the request map. + struct RequestJob { + MessageLoop* loop_; + scoped_refptr<URLRequestAutomationJob> job_; + }; + typedef std::map<int, RequestJob> RequestMap; + RequestMap requests_; +}; + +#endif // CHROME_FRAME_TEST_NET_TEST_AUTOMATION_RESOURCE_MESSAGE_FILTER_H_ diff --git a/chrome_frame/test/perf/chrome_frame_perftest.cc b/chrome_frame/test/perf/chrome_frame_perftest.cc new file mode 100644 index 0000000..79bbd7b --- /dev/null +++ b/chrome_frame/test/perf/chrome_frame_perftest.cc @@ -0,0 +1,1137 @@ +// Copyright (c) 2006-2009 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. +#include "chrome_frame/test/perf/chrome_frame_perftest.h" + +#include <atlwin.h> +#include <atlhost.h> +#include <map> +#include <vector> +#include <string> + +#include "chrome_tab.h" // Generated from chrome_tab.idl. + +#include "base/file_util.h" +#include "base/registry.h" +#include "base/scoped_ptr.h" +#include "base/scoped_bstr_win.h" +#include "base/scoped_comptr_win.h" +#include "base/string_util.h" +#include "base/time.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/test/chrome_process_util.h" +#include "chrome/test/perf/mem_usage.h" +#include "chrome/test/ui/ui_test.h" + +#include "chrome_frame/test_utils.h" +#include "chrome_frame/utils.h" + +const wchar_t kSilverlightControlKey[] = + L"CLSID\\{DFEAF541-F3E1-4c24-ACAC-99C30715084A}\\InprocServer32"; + +const wchar_t kFlashControlKey[] = + L"CLSID\\{D27CDB6E-AE6D-11cf-96B8-444553540000}\\InprocServer32"; + +using base::TimeDelta; +using base::TimeTicks; + +// Callback description for onload, onloaderror, onmessage +static _ATL_FUNC_INFO g_single_param = {CC_STDCALL, VT_EMPTY, 1, {VT_VARIANT}}; +// Simple class that forwards the callbacks. +template <typename T> +class DispCallback + : public IDispEventSimpleImpl<1, DispCallback<T>, &IID_IDispatch> { + public: + typedef HRESULT (T::*Method)(VARIANT* param); + + DispCallback(T* owner, Method method) : owner_(owner), method_(method) { + } + + BEGIN_SINK_MAP(DispCallback) + SINK_ENTRY_INFO(1, IID_IDispatch, DISPID_VALUE, OnCallback, &g_single_param) + END_SINK_MAP() + + virtual ULONG STDMETHODCALLTYPE AddRef() { + return owner_->AddRef(); + } + virtual ULONG STDMETHODCALLTYPE Release() { + return owner_->Release(); + } + + STDMETHOD(OnCallback)(VARIANT param) { + return (owner_->*method_)(¶m); + } + + IDispatch* ToDispatch() { + return reinterpret_cast<IDispatch*>(this); + } + + T* owner_; + Method method_; +}; + +// This class implements an ActiveX container which hosts the ChromeFrame +// ActiveX control. It provides hooks which can be implemented by derived +// classes for implementing performance measurement, etc. +class ChromeFrameActiveXContainer + : public CWindowImpl<ChromeFrameActiveXContainer, CWindow, CWinTraits < + WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, + WS_EX_APPWINDOW | WS_EX_WINDOWEDGE> >, + public CComObjectRootEx<CComSingleThreadModel>, + public IPropertyNotifySink { + public: + ~ChromeFrameActiveXContainer() { + if (m_hWnd) + DestroyWindow(); + } + + DECLARE_WND_CLASS_EX(L"ChromeFrameActiveX_container", 0, 0) + + BEGIN_COM_MAP(ChromeFrameActiveXContainer) + COM_INTERFACE_ENTRY(IPropertyNotifySink) + END_COM_MAP() + + BEGIN_MSG_MAP(ChromeFrameActiveXContainer) + MESSAGE_HANDLER(WM_CREATE, OnCreate) + MESSAGE_HANDLER(WM_DESTROY, OnDestroy) + END_MSG_MAP() + + HRESULT OnMessageCallback(VARIANT* param) { + DLOG(INFO) << __FUNCTION__; + OnMessageCallbackImpl(param); + return S_OK; + } + + HRESULT OnLoadErrorCallback(VARIANT* param) { + DLOG(INFO) << __FUNCTION__ << " " << param->bstrVal; + OnLoadErrorCallbackImpl(param); + return S_OK; + } + + HRESULT OnLoadCallback(VARIANT* param) { + DLOG(INFO) << __FUNCTION__ << " " << param->bstrVal; + OnLoadCallbackImpl(param); + return S_OK; + } + + ChromeFrameActiveXContainer() : + prop_notify_cookie_(0), + onmsg_(this, &ChromeFrameActiveXContainer::OnMessageCallback), + onloaderror_(this, &ChromeFrameActiveXContainer::OnLoadErrorCallback), + onload_(this, &ChromeFrameActiveXContainer::OnLoadCallback) { + } + + LRESULT OnCreate(UINT , WPARAM , LPARAM , BOOL& ) { + chromeview_.Attach(m_hWnd); + return 0; + } + + // This will be called twice. + // Once from CAxHostWindow::OnDestroy (through DefWindowProc) + // and once more from the ATL since CAxHostWindow::OnDestroy claims the + // message is not handled. + LRESULT OnDestroy(UINT, WPARAM, LPARAM, BOOL& handled) { // NOLINT + if (prop_notify_cookie_) { + AtlUnadvise(tab_, IID_IPropertyNotifySink, prop_notify_cookie_); + prop_notify_cookie_ = 0; + } + + tab_.Release(); + return 0; + } + + virtual void OnFinalMessage(HWND /*hWnd*/) { + ::PostQuitMessage(6); + } + + static const wchar_t* GetWndCaption() { + return L"ChromeFrame Container"; + } + + // IPropertyNotifySink + STDMETHOD(OnRequestEdit)(DISPID disp_id) { + OnRequestEditImpl(disp_id); + return S_OK; + } + + STDMETHOD(OnChanged)(DISPID disp_id) { + if (disp_id != DISPID_READYSTATE) + return S_OK; + + long ready_state; + HRESULT hr = tab_->get_readyState(&ready_state); + DCHECK(hr == S_OK); + + OnReadyStateChanged(ready_state); + + if (ready_state == READYSTATE_COMPLETE) { + if(!starting_url_.empty()) { + Navigate(starting_url_.c_str()); + } else { + PostMessage(WM_CLOSE); + } + } else if (ready_state == READYSTATE_UNINITIALIZED) { + DLOG(ERROR) << __FUNCTION__ << " Chrome launch failed."; + } + + return S_OK; + } + + void CreateChromeFrameWindow(const std::string& starting_url) { + starting_url_ = starting_url; + RECT rc = { 0, 0, 800, 600 }; + Create(NULL, rc); + DCHECK(m_hWnd); + ShowWindow(SW_SHOWDEFAULT); + } + + void CreateControl(bool setup_event_sinks) { + HRESULT hr = chromeview_.CreateControl(L"ChromeTab.ChromeFrame"); + EXPECT_HRESULT_SUCCEEDED(hr); + hr = chromeview_.QueryControl(tab_.Receive()); + EXPECT_HRESULT_SUCCEEDED(hr); + + if (setup_event_sinks) + SetupEventSinks(); + } + + void Navigate(const char* url) { + BeforeNavigateImpl(url); + + HRESULT hr = tab_->put_src(ScopedBstr(UTF8ToWide(url).c_str())); + DCHECK(hr == S_OK) << "Chrome frame NavigateToURL(" << url + << StringPrintf(L") failed 0x%08X", hr); + } + + void SetupEventSinks() { + HRESULT hr = AtlAdvise(tab_, this, IID_IPropertyNotifySink, + &prop_notify_cookie_); + DCHECK(hr == S_OK) << "AtlAdvice for IPropertyNotifySink failed " << hr; + + CComVariant onmessage(onmsg_.ToDispatch()); + CComVariant onloaderror(onloaderror_.ToDispatch()); + CComVariant onload(onload_.ToDispatch()); + EXPECT_HRESULT_SUCCEEDED(tab_->put_onmessage(onmessage)); + EXPECT_HRESULT_SUCCEEDED(tab_->put_onloaderror(onloaderror)); + EXPECT_HRESULT_SUCCEEDED(tab_->put_onload(onload)); + } + + protected: + // These functions are implemented by derived classes for special behavior + // like performance measurement, etc. + virtual void OnReadyStateChanged(long ready_state) {} + virtual void OnRequestEditImpl(DISPID disp_id) {} + + virtual void OnMessageCallbackImpl(VARIANT* param) {} + + virtual void OnLoadCallbackImpl(VARIANT* param) { + PostMessage(WM_CLOSE); + } + + virtual void OnLoadErrorCallbackImpl(VARIANT* param) { + PostMessage(WM_CLOSE); + } + virtual void BeforeNavigateImpl(const char* url) {} + + CAxWindow chromeview_; + ScopedComPtr<IChromeFrame> tab_; + DWORD prop_notify_cookie_; + DispCallback<ChromeFrameActiveXContainer> onmsg_; + DispCallback<ChromeFrameActiveXContainer> onloaderror_; + DispCallback<ChromeFrameActiveXContainer> onload_; + std::string starting_url_; +}; + +// This class overrides the hooks provided by the ChromeFrameActiveXContainer +// class and measures performance at various stages, like initialzation of +// the Chrome frame widget, navigation, etc. +class ChromeFrameActiveXContainerPerf : public ChromeFrameActiveXContainer { + public: + ChromeFrameActiveXContainerPerf() {} + + void CreateControl(bool setup_event_sinks) { + perf_initialize_.reset(new PerfTimeLogger("Fully initialized")); + PerfTimeLogger perf_create("Create Control"); + + HRESULT hr = chromeview_.CreateControl(L"ChromeTab.ChromeFrame"); + EXPECT_HRESULT_SUCCEEDED(hr); + hr = chromeview_.QueryControl(tab_.Receive()); + EXPECT_HRESULT_SUCCEEDED(hr); + + perf_create.Done(); + if (setup_event_sinks) + SetupEventSinks(); + } + + protected: + virtual void OnReadyStateChanged(long ready_state) { + // READYSTATE_COMPLETE is fired when the automation server is ready. + if (ready_state == READYSTATE_COMPLETE) { + perf_initialize_->Done(); + } else if (ready_state == READYSTATE_INTERACTIVE) { + // Window ready. Currently we never receive this notification because it + // is fired before we finish setting up our hosting environment. + // This is because of how ATL is written. Moving forward we might + // have our own hosting classes and then have more control over when we + // set up the prop notify sink. + } else { + DCHECK(ready_state != READYSTATE_UNINITIALIZED) << "failed to initialize"; + } + } + + virtual void OnLoadCallbackImpl(VARIANT* param) { + PostMessage(WM_CLOSE); + perf_navigate_->Done(); + } + + virtual void OnLoadErrorCallbackImpl(VARIANT* param) { + PostMessage(WM_CLOSE); + perf_navigate_->Done(); + } + + virtual void BeforeNavigateImpl(const char* url ) { + std::string test_name = "Navigate "; + test_name += url; + perf_navigate_.reset(new PerfTimeLogger(test_name.c_str())); + } + + scoped_ptr<PerfTimeLogger> perf_initialize_; + scoped_ptr<PerfTimeLogger> perf_navigate_; +}; + +// This class provides common functionality which can be used for most of the +// ChromeFrame/Tab performance tests. +class ChromeFramePerfTestBase : public UITest { + public: + ChromeFramePerfTestBase() {} + protected: + scoped_ptr<ScopedChromeFrameRegistrar> chrome_frame_registrar_; +}; + +class ChromeFrameStartupTest : public ChromeFramePerfTestBase { + public: + ChromeFrameStartupTest() {} + + virtual void SetUp() { + ASSERT_TRUE(PathService::Get(chrome::DIR_APP, &dir_app_)); + + chrome_dll_ = dir_app_.Append(FILE_PATH_LITERAL("chrome.dll")); + chrome_exe_ = dir_app_.Append( + FilePath::FromWStringHack(chrome::kBrowserProcessExecutableName)); + chrome_frame_dll_ = dir_app_.Append( + FILE_PATH_LITERAL("servers\\npchrome_tab.dll")); + } + virtual void TearDown() {} + + // TODO(iyengar) + // This function is similar to the RunStartupTest function used in chrome + // startup tests. Refactor into a common implementation. + void RunStartupTest(const char* graph, const char* trace, + const char* startup_url, bool test_cold, + int total_binaries, const FilePath binaries_to_evict[], + bool important, bool ignore_cache_error) { + const int kNumCycles = 20; + + startup_url_ = startup_url; + + TimeDelta timings[kNumCycles]; + + for (int i = 0; i < kNumCycles; ++i) { + if (test_cold) { + for (int binary_index = 0; binary_index < total_binaries; + binary_index++) { + bool result = EvictFileFromSystemCacheWrapper( + binaries_to_evict[binary_index]); + if (!ignore_cache_error) { + ASSERT_TRUE(result); + } else if (!result) { + printf("\nFailed to evict file %ls from cache. Not running test\n", + binaries_to_evict[binary_index].value().c_str()); + return; + } + } + } + + TimeTicks start_time, end_time; + + RunStartupTestImpl(&start_time, &end_time); + + timings[i] = end_time - start_time; + + CoFreeUnusedLibraries(); + ASSERT_TRUE(GetModuleHandle(L"npchrome_tab.dll") == NULL); + + // TODO(beng): Can't shut down so quickly. Figure out why, and fix. If we + // do, we crash. + PlatformThread::Sleep(50); + } + + std::string times; + for (int i = 0; i < kNumCycles; ++i) + StringAppendF(×, "%.2f,", timings[i].InMillisecondsF()); + + PrintResultList(graph, "", trace, times, "ms", important); + } + + FilePath dir_app_; + FilePath chrome_dll_; + FilePath chrome_exe_; + FilePath chrome_frame_dll_; + + protected: + // Individual startup tests should implement this function. + virtual void RunStartupTestImpl(TimeTicks* start_time, + TimeTicks* end_time) {} + + // The host is torn down by this function. It should not be used after + // this function returns. + static void ReleaseHostComReferences(CAxWindow& host) { + CComPtr<IAxWinHostWindow> spWinHost; + host.QueryHost(&spWinHost); + ASSERT_TRUE(spWinHost != NULL); + + // Hack to get the host to release all interfaces and thus ensure that + // the COM server can be unloaded. + CAxHostWindow* host_window = static_cast<CAxHostWindow*>(spWinHost.p); + host_window->ReleaseAll(); + host.DestroyWindow(); + } + + std::string startup_url_; +}; + +class ChromeFrameStartupTestActiveX : public ChromeFrameStartupTest { + public: + virtual void SetUp() { + // Register the Chrome Frame DLL in the build directory. + chrome_frame_registrar_.reset(new ScopedChromeFrameRegistrar); + + ChromeFrameStartupTest::SetUp(); + } + + protected: + virtual void RunStartupTestImpl(TimeTicks* start_time, + TimeTicks* end_time) { + *start_time = TimeTicks::Now(); + SimpleModule module; + AtlAxWinInit(); + CComObjectStackEx<ChromeFrameActiveXContainer> wnd; + wnd.CreateChromeFrameWindow(startup_url_); + wnd.CreateControl(true); + module.RunMessageLoop(); + *end_time = TimeTicks::Now(); + } +}; + +// This class measures the load time of chrome and chrome frame binaries +class ChromeFrameBinariesLoadTest : public ChromeFrameStartupTestActiveX { + protected: + virtual void RunStartupTestImpl(TimeTicks* start_time, + TimeTicks* end_time) { + *start_time = TimeTicks::Now(); + + HMODULE chrome_exe = LoadLibrary(chrome_exe_.ToWStringHack().c_str()); + ASSERT_TRUE(chrome_exe != NULL); + + HMODULE chrome_dll = LoadLibrary(chrome_dll_.ToWStringHack().c_str()); + ASSERT_TRUE(chrome_dll != NULL); + + HMODULE chrome_tab_dll = + LoadLibrary(chrome_frame_dll_.ToWStringHack().c_str()); + ASSERT_TRUE(chrome_tab_dll != NULL); + + *end_time = TimeTicks::Now(); + + FreeLibrary(chrome_exe); + FreeLibrary(chrome_dll); + FreeLibrary(chrome_tab_dll); + } +}; + +// This class provides functionality to run the startup performance test for +// the ChromeFrame ActiveX against a reference build. At this point we only run +// this test in warm mode. +class ChromeFrameStartupTestActiveXReference + : public ChromeFrameStartupTestActiveX { + public: + // override the browser directory to use the reference build instead. + virtual void SetUp() { + // Register the reference build Chrome Frame DLL. + chrome_frame_registrar_.reset(new ScopedChromeFrameRegistrar); + chrome_frame_registrar_->RegisterReferenceChromeFrameBuild(); + + ChromeFrameStartupTest::SetUp(); + chrome_frame_dll_ = FilePath::FromWStringHack( + chrome_frame_registrar_->GetChromeFrameDllPath()); + } + + virtual void TearDown() { + // Reregister the Chrome Frame DLL in the build directory. + chrome_frame_registrar_.reset(NULL); + } +}; + +// This class provides base functionality to measure ChromeFrame memory +// usage. +// TODO(iyengar) +// Some of the functionality in this class like printing the results, etc +// is based on the chrome\test\memory_test.cc. We need to factor out +// the common code. +class ChromeFrameMemoryTest : public ChromeFramePerfTestBase { + + // Contains information about the memory consumption of a process. + class ProcessMemoryInfo { + public: + // Default constructor + // Added to enable us to add ProcessMemoryInfo instances to a map. + ProcessMemoryInfo() + : process_id_(0), + peak_virtual_size_(0), + virtual_size_(0), + peak_working_set_size_(0), + working_set_size_(0), + chrome_browser_process_(false), + chrome_frame_memory_test_instance_(NULL) {} + + ProcessMemoryInfo(base::ProcessId process_id, bool chrome_browser_process, + ChromeFrameMemoryTest* memory_test_instance) + : process_id_(process_id), + peak_virtual_size_(0), + virtual_size_(0), + peak_working_set_size_(0), + working_set_size_(0), + chrome_browser_process_(chrome_browser_process), + chrome_frame_memory_test_instance_(memory_test_instance) {} + + bool GetMemoryConsumptionDetails() { + return GetMemoryInfo(process_id_, + &peak_virtual_size_, + &virtual_size_, + &peak_working_set_size_, + &working_set_size_); + } + + void Print(const char* test_name) { + std::string trace_name(test_name); + + ASSERT_TRUE(chrome_frame_memory_test_instance_ != NULL); + + if (chrome_browser_process_) { + chrome_frame_memory_test_instance_->PrintResult( + "vm_final_browser", "", trace_name + "_vm_b", + virtual_size_ / 1024, "KB", false /* not important */); + chrome_frame_memory_test_instance_->PrintResult( + "ws_final_browser", "", trace_name + "_ws_b", + working_set_size_ / 1024, "KB", false /* not important */); + } else if (process_id_ == GetCurrentProcessId()) { + chrome_frame_memory_test_instance_->PrintResult( + "vm_current_process", "", trace_name + "_vm_c", + virtual_size_ / 1024, "KB", false /* not important */); + chrome_frame_memory_test_instance_->PrintResult( + "ws_current_process", "", trace_name + "_ws_c", + working_set_size_ / 1024, "KB", false /* not important */); + } + + printf("\n"); + } + + int process_id_; + size_t peak_virtual_size_; + size_t virtual_size_; + size_t peak_working_set_size_; + size_t working_set_size_; + // Set to true if this is the chrome browser process. + bool chrome_browser_process_; + + // A reference to the ChromeFrameMemoryTest instance. Used to print memory + // consumption information. + ChromeFrameMemoryTest* chrome_frame_memory_test_instance_; + }; + + // This map tracks memory usage for a process. It is keyed on the process + // id. + typedef std::map<DWORD, ProcessMemoryInfo> ProcessMemoryConsumptionMap; + + public: + ChromeFrameMemoryTest() + : current_url_index_(0), + browser_pid_(0) {} + + virtual void SetUp() { + // Register the Chrome Frame DLL in the build directory. + chrome_frame_registrar_.reset(new ScopedChromeFrameRegistrar); + } + + void RunTest(const char* test_name, char* urls[], int total_urls) { + ASSERT_TRUE(urls != NULL); + ASSERT_GT(total_urls, 0); + + // Record the initial CommitCharge. This is a system-wide measurement, + // so if other applications are running, they can create variance in this + // test. + start_commit_charge_ = GetSystemCommitCharge(); + + for (int i = 0; i < total_urls; i++) + urls_.push_back(urls[i]); + + std::string url; + GetNextUrl(&url); + ASSERT_TRUE(!url.empty()); + + StartTest(url, test_name); + } + + void OnNavigationSuccess(VARIANT* param) { + ASSERT_TRUE(param != NULL); + ASSERT_EQ(VT_BSTR, param->vt); + + DLOG(INFO) << __FUNCTION__ << " " << param->bstrVal; + InitiateNextNavigation(); + } + + void OnNavigationFailure(VARIANT* param) { + ASSERT_TRUE(param != NULL); + ASSERT_EQ(VT_BSTR, param->vt); + + DLOG(INFO) << __FUNCTION__ << " " << param->bstrVal; + InitiateNextNavigation(); + } + + protected: + bool GetNextUrl(std::string* url) { + if (current_url_index_ >= urls_.size()) + return false; + + *url = urls_[current_url_index_++]; + return true; + } + + // Returns the path of the current chrome.exe being used by this test. + // This could return the regular chrome path or that of the reference + // build. + std::wstring GetChromeExePath() { + std::wstring chrome_exe_path = + chrome_frame_registrar_->GetChromeFrameDllPath(); + EXPECT_FALSE(chrome_exe_path.empty()); + + file_util::UpOneDirectory(&chrome_exe_path); + + std::wstring chrome_exe_test_path = chrome_exe_path; + file_util::AppendToPath(&chrome_exe_test_path, + chrome::kBrowserProcessExecutableName); + + if (!file_util::PathExists(chrome_exe_test_path)) { + file_util::UpOneDirectory(&chrome_exe_path); + + chrome_exe_test_path = chrome_exe_path; + file_util::AppendToPath(&chrome_exe_test_path, + chrome::kBrowserProcessExecutableName); + } + + EXPECT_TRUE(file_util::PathExists(chrome_exe_test_path)); + + return chrome_exe_path; + } + + void InitiateNextNavigation() { + if (browser_pid_ == 0) { + std::wstring profile_directory; + if (GetUserProfileBaseDirectory(&profile_directory)) { + file_util::AppendToPath(&profile_directory, + GetHostProcessName(false)); + } + + user_data_dir_ = FilePath::FromWStringHack(profile_directory); + browser_pid_ = ChromeBrowserProcessId(user_data_dir_); + } + + EXPECT_TRUE(static_cast<int>(browser_pid_) > 0); + + // Get the memory consumption information for the child processes + // of the chrome browser. + ChromeProcessList child_processes = GetBrowserChildren(); + ChromeProcessList::iterator index; + for (index = child_processes.begin(); index != child_processes.end(); + ++index) { + AccountProcessMemoryUsage(*index); + } + + // TODO(iyengar): Bug 2953 + // Need to verify if this is still true. + // The automation crashes periodically if we cycle too quickly. + // To make these tests more reliable, slowing them down a bit. + Sleep(200); + + std::string url; + bool next_url = GetNextUrl(&url); + if (!url.empty()) { + NavigateImpl(url); + } else { + TestCompleted(); + } + } + + void PrintResults(const char* test_name) { + PrintMemoryUsageInfo(test_name); + memory_consumption_map_.clear(); + + // Added to give the OS some time to flush the used pages for the + // chrome processes which would have exited by now. + Sleep(200); + + size_t end_commit_charge = GetSystemCommitCharge(); + size_t commit_size = end_commit_charge - start_commit_charge_; + + std::string trace_name(test_name); + trace_name.append("_cc"); + + PrintResult("commit_charge", "", trace_name, + commit_size / 1024, "KB", true /* important */); + printf("\n"); + } + + ChromeProcessList GetBrowserChildren() { + ChromeProcessList list = GetRunningChromeProcesses(user_data_dir_); + ChromeProcessList::iterator browser = + std::find(list.begin(), list.end(), browser_pid_); + if (browser != list.end()) { + list.erase(browser); + } + return list; + } + + void AccountProcessMemoryUsage(DWORD process_id) { + ProcessMemoryInfo process_memory_info(process_id, + process_id == browser_pid_, this); + + ASSERT_TRUE(process_memory_info.GetMemoryConsumptionDetails()); + + memory_consumption_map_[process_id] = process_memory_info; + } + + void PrintMemoryUsageInfo(const char* test_name) { + printf("\n"); + + std::string trace_name(test_name); + + ProcessMemoryConsumptionMap::iterator index; + size_t total_virtual_size = 0; + size_t total_working_set_size = 0; + + for (index = memory_consumption_map_.begin(); + index != memory_consumption_map_.end(); + ++index) { + ProcessMemoryInfo& memory_info = (*index).second; + memory_info.Print(test_name); + + total_virtual_size += memory_info.virtual_size_; + total_working_set_size += memory_info.working_set_size_; + } + + printf("\n"); + + PrintResult("vm_final_total", "", trace_name + "_vm", + total_virtual_size / 1024, "KB", + false /* not important */); + PrintResult("ws_final_total", "", trace_name + "_ws", + total_working_set_size / 1024, "KB", + true /* important */); + } + + // Should never get called. + virtual void StartTest(const std::string& url, + const std::string& test_name) = 0 { + ASSERT_FALSE(false); + } + + // Should never get called. + virtual void NavigateImpl(const std::string& url) = 0 { + ASSERT_FALSE(false); + } + + virtual void TestCompleted() = 0 { + ASSERT_FALSE(false); + } + + // Holds the commit charge at the start of the memory test run. + size_t start_commit_charge_; + + // The index of the URL being tested. + size_t current_url_index_; + + // The chrome browser pid. + base::ProcessId browser_pid_; + + // Contains the list of urls against which the tests are run. + std::vector<std::string> urls_; + + ProcessMemoryConsumptionMap memory_consumption_map_; +}; + +// This class provides functionality to run the memory test against a reference +// chrome frame build. +class ChromeFrameMemoryTestReference : public ChromeFrameMemoryTest { + public: + virtual void SetUp() { + chrome_frame_registrar_.reset(new ScopedChromeFrameRegistrar); + chrome_frame_registrar_->RegisterReferenceChromeFrameBuild(); + } + + virtual void TearDown() { + // Reregisters the chrome frame DLL in the build directory. + chrome_frame_registrar_.reset(NULL); + } +}; + +// This class overrides the hooks provided by the ChromeFrameActiveXContainer +// class and calls back into the ChromeFrameMemoryTest object instance, +// which measures ChromeFrame memory usage. +class ChromeFrameActiveXContainerMemory : public ChromeFrameActiveXContainer { + public: + ChromeFrameActiveXContainerMemory() + : delegate_(NULL) {} + + ~ChromeFrameActiveXContainerMemory() {} + + void Initialize(ChromeFrameMemoryTest* delegate) { + ASSERT_TRUE(delegate != NULL); + delegate_ = delegate; + } + + protected: + virtual void OnLoadCallbackImpl(VARIANT* param) { + delegate_->OnNavigationSuccess(param); + } + + virtual void OnLoadErrorCallbackImpl(VARIANT* param) { + delegate_->OnNavigationFailure(param); + } + + ChromeFrameMemoryTest* delegate_; +}; + +// This class runs memory tests against the ChromeFrame ActiveX. +template<class MemoryTestBase> +class ChromeFrameActiveXMemoryTest : public MemoryTestBase { + public: + ChromeFrameActiveXMemoryTest() + : chrome_frame_container_(NULL), + test_completed_(false) {} + + ~ChromeFrameActiveXMemoryTest() { + } + + void StartTest(const std::string& url, const std::string& test_name) { + ASSERT_TRUE(chrome_frame_container_ == NULL); + + test_name_ = test_name; + + SimpleModule module; + AtlAxWinInit(); + + CComObject<ChromeFrameActiveXContainerMemory>::CreateInstance( + &chrome_frame_container_); + chrome_frame_container_->AddRef(); + + chrome_frame_container_->Initialize(this); + + chrome_frame_container_->CreateChromeFrameWindow(url.c_str()); + chrome_frame_container_->CreateControl(true); + + module.RunMessageLoop(); + + chrome_frame_container_->Release(); + + PrintResults(test_name_.c_str()); + + CoFreeUnusedLibraries(); + //ASSERT_TRUE(GetModuleHandle(L"npchrome_tab.dll") == NULL); + } + + void NavigateImpl(const std::string& url) { + ASSERT_TRUE(chrome_frame_container_ != NULL); + ASSERT_TRUE(!url.empty()); + chrome_frame_container_->Navigate(url.c_str()); + } + + void TestCompleted() { + // This can get called multiple times if the last url results in a + // redirect. + if (!test_completed_) { + ASSERT_NE(browser_pid_, 0); + + // Measure memory usage for the browser process. + AccountProcessMemoryUsage(browser_pid_); + // Measure memory usage for the current process. + AccountProcessMemoryUsage(GetCurrentProcessId()); + + test_completed_ = true; + EXPECT_TRUE(PostMessage(static_cast<HWND>(*chrome_frame_container_), + WM_CLOSE, 0, 0)); + } + } + + protected: + CComObject<ChromeFrameActiveXContainerMemory>* chrome_frame_container_; + std::string test_name_; + bool test_completed_; +}; + +// This class runs tests to measure chrome frame creation only. This will help +// track overall page load performance with chrome frame instances. +class ChromeFrameCreationTest : public ChromeFrameStartupTest { + protected: + virtual void RunStartupTestImpl(TimeTicks* start_time, + TimeTicks* end_time) { + SimpleModule module; + AtlAxWinInit(); + CComObjectStackEx<ChromeFrameActiveXContainer> wnd; + wnd.CreateChromeFrameWindow(startup_url_); + *start_time = TimeTicks::Now(); + wnd.CreateControl(false); + *end_time = TimeTicks::Now(); + } +}; + +// This class provides functionality to run the chrome frame +// performance test against a reference build. +class ChromeFrameCreationTestReference : public ChromeFrameCreationTest { + public: + // override the browser directory to use the reference build instead. + virtual void SetUp() { + chrome_frame_registrar_.reset(new ScopedChromeFrameRegistrar); + chrome_frame_registrar_->RegisterReferenceChromeFrameBuild(); + ChromeFrameStartupTest::SetUp(); + } + + virtual void TearDown() { + chrome_frame_registrar_.reset(NULL); + } +}; + +// This class measures the creation time for Flash, which would be used +// as a baseline to measure chrome frame creation performance. +class FlashCreationTest : public ChromeFrameStartupTest { + protected: + virtual void RunStartupTestImpl(TimeTicks* start_time, + TimeTicks* end_time) { + SimpleModule module; + AtlAxWinInit(); + CAxWindow host; + RECT rc = {0, 0, 800, 600}; + host.Create(NULL, rc, NULL, + WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, + WS_EX_APPWINDOW | WS_EX_WINDOWEDGE); + EXPECT_TRUE(host.m_hWnd != NULL); + + *start_time = TimeTicks::Now(); + HRESULT hr = host.CreateControl(L"ShockwaveFlash.ShockwaveFlash"); + EXPECT_HRESULT_SUCCEEDED(hr); + *end_time = TimeTicks::Now(); + + ReleaseHostComReferences(host); + } +}; + +// This class measures the creation time for Silverlight, which would be used +// as a baseline to measure chrome frame creation performance. +class SilverlightCreationTest : public ChromeFrameStartupTest { + protected: + virtual void RunStartupTestImpl(TimeTicks* start_time, + TimeTicks* end_time) { + SimpleModule module; + AtlAxWinInit(); + CAxWindow host; + RECT rc = {0, 0, 800, 600}; + host.Create(NULL, rc, NULL, + WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, + WS_EX_APPWINDOW | WS_EX_WINDOWEDGE); + EXPECT_TRUE(host.m_hWnd != NULL); + + *start_time = TimeTicks::Now(); + HRESULT hr = host.CreateControl(L"AgControl.AgControl"); + EXPECT_HRESULT_SUCCEEDED(hr); + *end_time = TimeTicks::Now(); + + ReleaseHostComReferences(host); + } +}; + +TEST(ChromeFramePerf, DISABLED_HostActiveX) { + // TODO(stoyan): Create a low integrity level thread && perform the test there + SimpleModule module; + AtlAxWinInit(); + CComObjectStackEx<ChromeFrameActiveXContainerPerf> wnd; + wnd.CreateChromeFrameWindow("http://www.google.com"); + wnd.CreateControl(true); + module.RunMessageLoop(); +} + +TEST(ChromeFramePerf, DISABLED_HostActiveXInvalidURL) { + // TODO(stoyan): Create a low integrity level thread && perform the test there + SimpleModule module; + AtlAxWinInit(); + CComObjectStackEx<ChromeFrameActiveXContainerPerf> wnd; + wnd.CreateChromeFrameWindow("http://non-existent-domain.org/"); + wnd.CreateControl(true); + module.RunMessageLoop(); +} + +TEST_F(ChromeFrameStartupTestActiveX, PerfWarm) { + RunStartupTest("warm", "t", "about:blank", false /* cold */, 0, NULL, + true /* important */, false); +} + +TEST_F(ChromeFrameBinariesLoadTest, PerfWarm) { + RunStartupTest("binary_load_warm", "t", "", false /* cold */, 0, NULL, + true /* important */, false); +} + +TEST_F(ChromeFrameStartupTestActiveX, PerfCold) { + FilePath binaries_to_evict[] = {chrome_exe_, chrome_dll_, chrome_frame_dll_}; + RunStartupTest("cold", "t", "about:blank", true /* cold */, + arraysize(binaries_to_evict), binaries_to_evict, + false /* not important */, false); +} + +TEST_F(ChromeFrameBinariesLoadTest, PerfCold) { + FilePath binaries_to_evict[] = {chrome_exe_, chrome_dll_, chrome_frame_dll_}; + RunStartupTest("binary_load_cold", "t", "", true /* cold */, + arraysize(binaries_to_evict), binaries_to_evict, + false /* not important */, false); +} + +TEST_F(ChromeFrameStartupTestActiveXReference, PerfWarm) { + RunStartupTest("warm", "t_ref", "about:blank", false /* cold */, 0, NULL, + true /* important */, false); +} + +TEST_F(ChromeFrameStartupTestActiveX, PerfChromeFrameInitializationWarm) { + RunStartupTest("ChromeFrame_init_warm", "t", "", false /* cold */, 0, + NULL, true /* important */, false); +} + +TEST_F(ChromeFrameStartupTestActiveX, PerfChromeFrameInitializationCold) { + FilePath binaries_to_evict[] = {chrome_frame_dll_}; + RunStartupTest("ChromeFrame_init_cold", "t", "", true /* cold */, + arraysize(binaries_to_evict), binaries_to_evict, + false /* not important */, false); +} + +TEST_F(ChromeFrameStartupTestActiveXReference, + PerfChromeFrameInitializationWarm) { + RunStartupTest("ChromeFrame_init_warm", "t_ref", "", false /* cold */, 0, + NULL, true /* important */, false); +} + +typedef ChromeFrameActiveXMemoryTest<ChromeFrameMemoryTest> + RegularChromeFrameActiveXMemoryTest; + +TEST_F(RegularChromeFrameActiveXMemoryTest, MemoryTestAboutBlank) { + char *urls[] = {"about:blank"}; + RunTest("memory_about_blank", urls, arraysize(urls)); +} + +// TODO(iyengar) +// Revisit why the chrome frame dll does not unload correctly when this test is +// run. +TEST_F(RegularChromeFrameActiveXMemoryTest, DISABLED_MemoryTestUrls) { + // TODO(iyengar) + // We should use static pages to measure memory usage. + char *urls[] = { + "http://www.youtube.com/watch?v=PN2HAroA12w", + "http://www.youtube.com/watch?v=KmLJDrsaJmk&feature=channel" + }; + + RunTest("memory", urls, arraysize(urls)); +} + +typedef ChromeFrameActiveXMemoryTest<ChromeFrameMemoryTestReference> + ReferenceBuildChromeFrameActiveXMemoryTest; + +TEST_F(ReferenceBuildChromeFrameActiveXMemoryTest, MemoryTestAboutBlank) { + char *urls[] = {"about:blank"}; + RunTest("memory_about_blank_reference", urls, arraysize(urls)); +} + +// TODO(iyengar) +// Revisit why the chrome frame dll does not unload correctly when this test is +// run. +TEST_F(ReferenceBuildChromeFrameActiveXMemoryTest, DISABLED_MemoryTestUrls) { + // TODO(iyengar) + // We should use static pages to measure memory usage. + char *urls[] = { + "http://www.youtube.com/watch?v=PN2HAroA12w", + "http://www.youtube.com/watch?v=KmLJDrsaJmk&feature=channel" + }; + + RunTest("memory_reference", urls, arraysize(urls)); +} + +TEST_F(ChromeFrameCreationTest, PerfWarm) { + RunStartupTest("creation_warm", "t", "", false /* cold */, 0, + NULL, true /* important */, false); +} + +TEST_F(ChromeFrameCreationTestReference, PerfWarm) { + RunStartupTest("creation_warm", "t_ref", "about:blank", false /* cold */, 0, + NULL, true /* not important */, false); +} + +TEST_F(FlashCreationTest, PerfWarm) { + RunStartupTest("creation_warm", "t_flash", "", false /* cold */, 0, NULL, + true /* not important */, false); +} + +TEST_F(SilverlightCreationTest, DISABLED_PerfWarm) { + RunStartupTest("creation_warm", "t_silverlight", "", false /* cold */, 0, + NULL, false /* not important */, false); +} + +TEST_F(ChromeFrameCreationTest, PerfCold) { + FilePath binaries_to_evict[] = {chrome_frame_dll_}; + + RunStartupTest("creation_cold", "t", "", true /* cold */, + arraysize(binaries_to_evict), binaries_to_evict, + true /* important */, false); +} + +// Attempt to evict the Flash control can fail on the buildbot as the dll +// is marked read only. The test run is aborted if we fail to evict the file +// from the cache. This could also fail if the Flash control is in use. +// On Vista this could fail because of UAC +TEST_F(FlashCreationTest, PerfCold) { + RegKey flash_key(HKEY_CLASSES_ROOT, kFlashControlKey); + + std::wstring plugin_path; + ASSERT_TRUE(flash_key.ReadValue(L"", &plugin_path)); + ASSERT_FALSE(plugin_path.empty()); + + FilePath flash_path = FilePath::FromWStringHack(plugin_path); + FilePath binaries_to_evict[] = {flash_path}; + + RunStartupTest("creation_cold", "t_flash", "", true /* cold */, + arraysize(binaries_to_evict), binaries_to_evict, + false/* important */, true); +} + +// This test would fail on Vista due to UAC or if the Silverlight control is +// in use. The test run is aborted if we fail to evict the file from the cache. +// Disabling this test as the Silverlight dll does not seem to get unloaded +// correctly causing the attempt to evict the dll from the system cache to +// fail. +TEST_F(SilverlightCreationTest, DISABLED_PerfCold) { + RegKey silverlight_key(HKEY_CLASSES_ROOT, kSilverlightControlKey); + + std::wstring plugin_path; + ASSERT_TRUE(silverlight_key.ReadValue(L"", &plugin_path)); + ASSERT_FALSE(plugin_path.empty()); + + FilePath silverlight_path = FilePath::FromWStringHack(plugin_path); + FilePath binaries_to_evict[] = {silverlight_path}; + + RunStartupTest("creation_cold", "t_silverlight", "", true /* cold */, + arraysize(binaries_to_evict), binaries_to_evict, + false /* important */, true); +} diff --git a/chrome_frame/test/perf/chrome_frame_perftest.h b/chrome_frame/test/perf/chrome_frame_perftest.h new file mode 100644 index 0000000..5b895f3 --- /dev/null +++ b/chrome_frame/test/perf/chrome_frame_perftest.h @@ -0,0 +1,21 @@ +// Copyright (c) 2006-2009 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. +#ifndef CHROME_FRAME_TEST_PERF_CHROME_FRAME_PERFTEST_H_ +#define CHROME_FRAME_TEST_PERF_CHROME_FRAME_PERFTEST_H_ +#include <atlbase.h> +#include "base/logging.h" +#include "base/perftimer.h" +#include "testing/gtest/include/gtest/gtest.h" + +class SimpleModule : public CAtlExeModuleT<SimpleModule> { + public: + // The ATL code does not set _pAtlModule to NULL on destruction, and therefore + // creating new module (for another test) will ASSERT in constructor. + ~SimpleModule() { + Term(); + _pAtlModule = NULL; + } +}; +#endif // CHROME_FRAME_TEST_PERF_CHROME_FRAME_PERFTEST_H_ + diff --git a/chrome_frame/test/perf/chrometab_perftests.vcproj b/chrome_frame/test/perf/chrometab_perftests.vcproj new file mode 100644 index 0000000..b894e6a --- /dev/null +++ b/chrome_frame/test/perf/chrometab_perftests.vcproj @@ -0,0 +1,247 @@ +<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="8.00"
+ Name="chrometab_perftests"
+ ProjectGUID="{760ABE9D-2B3E-48C5-A571-6CD221A04BD6}"
+ RootNamespace="chrometab_perftests"
+ Keyword="Win32Proj"
+ >
+ <Platforms>
+ <Platform
+ Name="Win32"
+ />
+ </Platforms>
+ <ToolFiles>
+ </ToolFiles>
+ <Configurations>
+ <Configuration
+ Name="Debug|Win32"
+ OutputDirectory="..\..\chrome\$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="1"
+ InheritedPropertySheets=".\chrometab_perftests.vsprops;$(SolutionDir)..\build\debug.vsprops"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="0"
+ AdditionalIncludeDirectories=".."
+ PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE"
+ MinimalRebuild="true"
+ BasicRuntimeChecks="3"
+ RuntimeLibrary="1"
+ UsePrecompiledHeader="0"
+ WarningLevel="3"
+ Detect64BitPortabilityProblems="false"
+ DebugInformationFormat="4"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ LinkIncremental="2"
+ GenerateDebugInformation="true"
+ SubSystem="1"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCWebDeploymentTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ <Configuration
+ Name="Release|Win32"
+ OutputDirectory="..\..\chrome\$(ConfigurationName)"
+ IntermediateDirectory="$(ConfigurationName)"
+ ConfigurationType="1"
+ InheritedPropertySheets=".\chrometab_perftests.vsprops;$(SolutionDir)..\build\release.vsprops"
+ WholeProgramOptimization="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=".."
+ PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE"
+ RuntimeLibrary="0"
+ UsePrecompiledHeader="0"
+ WarningLevel="3"
+ Detect64BitPortabilityProblems="false"
+ DebugInformationFormat="3"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLinkerTool"
+ LinkIncremental="1"
+ GenerateDebugInformation="true"
+ SubSystem="1"
+ OptimizeReferences="2"
+ EnableCOMDATFolding="2"
+ TargetMachine="1"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCManifestTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCAppVerifierTool"
+ />
+ <Tool
+ Name="VCWebDeploymentTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <Filter
+ Name="Source Files"
+ Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx"
+ UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
+ >
+ <File
+ RelativePath=".\chrometab_perftest.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\chrometab_perftest.h"
+ >
+ </File>
+ <File
+ RelativePath=".\silverlight.cc"
+ >
+ </File>
+ <Filter
+ Name="Common"
+ >
+ <File
+ RelativePath="..\..\chrome\test\chrome_process_util.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\..\chrome\test\chrome_process_util.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\chrome\test\chrome_process_util_win.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\html_utils.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\..\chrome\test\perf\mem_usage.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\..\base\perf_test_suite.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\base\perftimer.cc"
+ >
+ </File>
+ <File
+ RelativePath="run_all.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\test_utils.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\test_utils.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\chrome\test\ui\ui_test.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\utils.cc"
+ >
+ </File>
+ </Filter>
+ </Filter>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
diff --git a/chrome_frame/test/perf/chrometab_perftests.vsprops b/chrome_frame/test/perf/chrometab_perftests.vsprops new file mode 100644 index 0000000..3891381 --- /dev/null +++ b/chrome_frame/test/perf/chrometab_perftests.vsprops @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioPropertySheet
+ ProjectType="Visual C++"
+ Version="8.00"
+ Name="chrometeb_perftests"
+ InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\skia\using_skia.vsprops;$(SolutionDir)..\third_party\libxml\build\using_libxml.vsprops;$(SolutionDir)..\third_party\libxslt\build\using_libxslt.vsprops;..\..\testing\using_gtest.vsprops;$(SolutionDir)..\chrome\third_party\wtl\using_wtl.vsprops;$(SolutionDir)..\tools\grit\build\using_generated_resources.vsprops"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ PreprocessorDefinitions="_ATL_APARTMENT_THREADED;_ATL_CSTRING_EXPLICIT_CONSTRUCTORS"
+ AdditionalIncludeDirectories=""
+ />
+
+</VisualStudioPropertySheet>
diff --git a/chrome_frame/test/perf/run_all.cc b/chrome_frame/test/perf/run_all.cc new file mode 100644 index 0000000..93a5a67 --- /dev/null +++ b/chrome_frame/test/perf/run_all.cc @@ -0,0 +1,17 @@ +// Copyright (c) 2006-2009 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. +#include "base/platform_thread.h" +#include "base/perf_test_suite.h" +#include "base/scoped_ptr.h" +#include "chrome/common/chrome_paths.h" +#include "chrome_frame/test_utils.h" + +int main(int argc, char **argv) { + PerfTestSuite perf_suite(argc, argv); + chrome::RegisterPathProvider(); + PlatformThread::SetName("ChromeFrame perf tests"); + // Use ctor/raii to register the local Chrome Frame dll. + scoped_ptr<ScopedChromeFrameRegistrar> registrar(new ScopedChromeFrameRegistrar); + return perf_suite.Run(); +} diff --git a/chrome_frame/test/perf/silverlight.cc b/chrome_frame/test/perf/silverlight.cc new file mode 100644 index 0000000..3016c2b --- /dev/null +++ b/chrome_frame/test/perf/silverlight.cc @@ -0,0 +1,165 @@ +// Copyright (c) 2009 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.
+#include <atlbase.h>
+#include <atlwin.h>
+#include <atlhost.h>
+#include "base/scoped_comptr_win.h"
+#include "chrome_frame/test/perf/chrome_frame_perftest.h"
+
+interface IXcpControlDownloadCallback;
+interface __declspec(uuid("1B36028E-B491-4bb2-8584-8A9E0A677D6E"))
+IXcpControlHost : public IUnknown {
+ typedef enum {
+ XcpHostOption_FreezeOnInitialFrame = 0x001,
+ XcpHostOption_DisableFullScreen = 0x002,
+ XcpHostOption_DisableManagedExecution = 0x008,
+ XcpHostOption_EnableCrossDomainDownloads = 0x010,
+ XcpHostOption_UseCustomAppDomain = 0x020,
+ XcpHostOption_DisableNetworking = 0x040,
+ XcpHostOption_DisableScriptCallouts = 0x080,
+ XcpHostOption_EnableHtmlDomAccess = 0x100,
+ XcpHostOption_EnableScriptableObjectAccess = 0x200,
+ } XcpHostOptions;
+
+ STDMETHOD(GetHostOptions)(DWORD* pdwOptions) PURE;
+ STDMETHOD(NotifyLoaded()) PURE;
+ STDMETHOD(NotifyError)(BSTR bstrError, BSTR bstrSource,
+ long nLine, long nColumn) PURE;
+ STDMETHOD(InvokeHandler)(BSTR bstrName, VARIANT varArg1, VARIANT varArg2,
+ VARIANT* pvarResult) PURE;
+ STDMETHOD(GetBaseUrl)(BSTR* pbstrUrl) PURE;
+ STDMETHOD(GetNamedSource)(BSTR bstrSourceName, BSTR* pbstrSource) PURE;
+ STDMETHOD(DownloadUrl)(BSTR bstrUrl, IXcpControlDownloadCallback* pCallback,
+ IStream** ppStream) PURE;
+};
+
+// Not templatized, to trade execution speed vs typing
+class IXcpControlHostImpl : public IXcpControlHost {
+ public:
+ STDMETHOD(GetHostOptions)(DWORD* pdwOptions) {
+ return E_NOTIMPL;
+ }
+
+ STDMETHOD(NotifyLoaded()) {
+ return E_NOTIMPL;
+ }
+
+ STDMETHOD(NotifyError)(BSTR bstrError, BSTR bstrSource,
+ long nLine, long nColumn) {
+ return E_NOTIMPL;
+ }
+
+ STDMETHOD(InvokeHandler)(BSTR bstrName, VARIANT varArg1, VARIANT varArg2,
+ VARIANT* pvarResult) {
+ return E_NOTIMPL;
+ }
+
+ STDMETHOD(GetBaseUrl)(BSTR* pbstrUrl) {
+ return E_NOTIMPL;
+ }
+
+ STDMETHOD(GetNamedSource)(BSTR bstrSourceName, BSTR* pbstrSource) {
+ return E_NOTIMPL;
+ }
+
+ STDMETHOD(DownloadUrl)(BSTR bstrUrl, IXcpControlDownloadCallback* pCallback,
+ IStream** ppStream) {
+ return E_NOTIMPL;
+ }
+};
+
+// Silverlight container. Supports do-nothing implementation of IXcpControlHost.
+// Should be extended to do some real movie-or-something download.
+class SilverlightContainer :
+ public IServiceProviderImpl<SilverlightContainer>,
+ public IXcpControlHostImpl,
+ public CWindowImpl<SilverlightContainer, CWindow, CWinTraits<
+ WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
+ WS_EX_APPWINDOW | WS_EX_WINDOWEDGE> >,
+ public CComObjectRootEx<CComSingleThreadModel> {
+ public:
+ DECLARE_WND_CLASS_EX(L"Silverlight_container", 0, 0)
+ BEGIN_COM_MAP(SilverlightContainer)
+ COM_INTERFACE_ENTRY(IServiceProvider)
+ COM_INTERFACE_ENTRY(IXcpControlHost)
+ END_COM_MAP()
+
+ BEGIN_SERVICE_MAP(SilverlightContainer)
+ SERVICE_ENTRY(__uuidof(IXcpControlHost))
+ END_SERVICE_MAP()
+
+ BEGIN_MSG_MAP(ChromeFrameActiveXContainer)
+ MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
+ END_MSG_MAP()
+
+ LRESULT OnDestroy(UINT, WPARAM, LPARAM, BOOL& handled) {
+ host_.Release();
+ return 0;
+ }
+
+ virtual void OnFinalMessage(HWND ) {
+ }
+
+ static const wchar_t* GetWndCaption() {
+ return L"Silverlight Container";
+ }
+
+ HRESULT CreateWndAndHost(RECT* r) {
+ Create(NULL, r);
+ ShowWindow(SW_SHOWDEFAULT);
+
+ CComPtr<IUnknown> spUnkContainer;
+ HRESULT hr = CAxHostWindow::_CreatorClass::CreateInstance(NULL,
+ __uuidof(IAxWinHostWindow), reinterpret_cast<void**>(&host_));
+ if (SUCCEEDED(hr)) {
+ CComPtr<IObjectWithSite> p;
+ hr = host_.QueryInterface(&p);
+ if (SUCCEEDED(hr)) {
+ p->SetSite(GetUnknown());
+ }
+ }
+ return hr;
+ }
+
+ HRESULT CreateControl() {
+ HRESULT hr = host_->CreateControl(L"AgControl.AgControl", m_hWnd, NULL);
+ EXPECT_HRESULT_SUCCEEDED(hr);
+ return hr;
+ }
+
+ ScopedComPtr<IAxWinHostWindow> host_;
+};
+
+// Create and in-place Silverlight control. Should be extended to do something
+// more meaningful.
+TEST(ChromeFramePerf, DISABLED_HostSilverlight2) {
+ SimpleModule module;
+ AtlAxWinInit();
+ CComObjectStackEx<SilverlightContainer> wnd;
+ RECT rc = {0, 0, 800, 600};
+ wnd.CreateWndAndHost(&rc);
+ PerfTimeLogger perf_create("Create Silverlight Control2");
+ wnd.CreateControl();
+ perf_create.Done();
+ wnd.DestroyWindow();
+}
+
+// Simplest test - creates in-place Silverlight control.
+TEST(ChromeFramePerf, DISABLED_HostSilverlight) {
+ SimpleModule module;
+ AtlAxWinInit();
+ CAxWindow host;
+ RECT rc = {0, 0, 800, 600};
+ PerfTimeLogger perf_create("Create Silverlight Control");
+ host.Create(NULL, rc, L"AgControl.AgControl",
+ WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
+ WS_EX_APPWINDOW | WS_EX_WINDOWEDGE);
+ EXPECT_TRUE(host.m_hWnd != NULL);
+ ScopedComPtr<IDispatch> disp;
+ HRESULT hr = host.QueryControl(disp.Receive());
+ EXPECT_HRESULT_SUCCEEDED(hr);
+ disp.Release();
+ perf_create.Done();
+}
+
diff --git a/chrome_frame/test/run_all_unittests.cc b/chrome_frame/test/run_all_unittests.cc new file mode 100644 index 0000000..787d765 --- /dev/null +++ b/chrome_frame/test/run_all_unittests.cc @@ -0,0 +1,49 @@ +// Copyright (c) 2006-2008 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. + +#include <atlbase.h> + +#include "base/at_exit.h" +#include "base/platform_thread.h" +#include "base/process_util.h" +#include "base/test_suite.h" +#include "base/command_line.h" +#include "chrome/common/chrome_paths.h" + +#include "chrome_frame/test_utils.h" + +// To enable ATL-based code to run in this module +class ChromeFrameUnittestsModule + : public CAtlExeModuleT<ChromeFrameUnittestsModule> { +}; + +ChromeFrameUnittestsModule _AtlModule; + +const wchar_t kNoRegistrationSwitch[] = L"no-registration"; + +int main(int argc, char **argv) { + base::EnableTerminationOnHeapCorruption(); + PlatformThread::SetName("ChromeFrame tests"); + + TestSuite test_suite(argc, argv); + + // If mini_installer is used to register CF, we use the switch + // --no-registration to avoid repetitive registration. + if (CommandLine::ForCurrentProcess()->HasSwitch(kNoRegistrationSwitch)) { + return test_suite.Run(); + } else { + // Register paths needed by the ScopedChromeFrameRegistrar. + chrome::RegisterPathProvider(); + + // This will register the chrome frame in the build directory. It currently + // leaves that chrome frame registered once the tests are done. It must be + // constructed AFTER the TestSuite is created since TestSuites create THE + // AtExitManager. + // TODO(robertshield): Make these tests restore the original registration + // once done. + ScopedChromeFrameRegistrar registrar; + + return test_suite.Run(); + } +} diff --git a/chrome_frame/test/test_server.cc b/chrome_frame/test/test_server.cc new file mode 100644 index 0000000..79ea2cf --- /dev/null +++ b/chrome_frame/test/test_server.cc @@ -0,0 +1,211 @@ +// Copyright (c) 2006-2008 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. + +#include "base/logging.h" +#include "base/registry.h" +#include "base/string_util.h" + +#include "chrome_frame/test/test_server.h" + +#include "net/base/winsock_init.h" +#include "net/http/http_util.h" + +namespace test_server { +const char kDefaultHeaderTemplate[] = + "HTTP/1.1 %hs\r\n" + "Connection: close\r\n" + "Content-Type: %hs\r\n" + "Content-Length: %i\r\n\r\n"; +const char kStatusOk[] = "200 OK"; +const char kStatusNotFound[] = "404 Not Found"; +const char kDefaultContentType[] = "text/html; charset=UTF-8"; + +void Request::ParseHeaders(const std::string& headers) { + size_t pos = headers.find("\r\n"); + DCHECK(pos != std::string::npos); + if (pos != std::string::npos) { + headers_ = headers.substr(pos + 2); + + StringTokenizer tokenizer(headers.begin(), headers.begin() + pos, " "); + std::string* parse[] = { &method_, &path_, &version_ }; + int field = 0; + while (tokenizer.GetNext() && field < arraysize(parse)) { + parse[field++]->assign(tokenizer.token_begin(), + tokenizer.token_end()); + } + } + + // Check for content-length in case we're being sent some data. + net::HttpUtil::HeadersIterator it(headers_.begin(), headers_.end(), + "\r\n"); + while (it.GetNext()) { + if (LowerCaseEqualsASCII(it.name(), "content-length")) { + content_length_ = StringToInt(it.values().c_str()); + break; + } + } +} + +bool Connection::CheckRequestReceived() { + bool ready = false; + if (request_.method().length()) { + // Headers have already been parsed. Just check content length. + ready = (data_.size() >= request_.content_length()); + } else { + size_t index = data_.find("\r\n\r\n"); + if (index != std::string::npos) { + // Parse the headers before returning and chop them of the + // data buffer we've already received. + std::string headers(data_.substr(0, index + 2)); + request_.ParseHeaders(headers); + data_.erase(0, index + 4); + ready = (data_.size() >= request_.content_length()); + } + } + + return ready; +} + +bool FileResponse::GetContentType(std::string* content_type) const { + size_t length = ContentLength(); + char buffer[4096]; + void* data = NULL; + + if (length) { + // Create a copy of the first few bytes of the file. + // If we try and use the mapped file directly, FindMimeFromData will crash + // 'cause it cheats and temporarily tries to write to the buffer! + length = std::min(arraysize(buffer), length); + memcpy(buffer, file_->data(), length); + data = buffer; + } + + LPOLESTR mime_type = NULL; + FindMimeFromData(NULL, file_path_.value().c_str(), data, length, NULL, + FMFD_DEFAULT, &mime_type, 0); + if (mime_type) { + *content_type = WideToASCII(mime_type); + ::CoTaskMemFree(mime_type); + } + + return content_type->length() > 0; +} + +void FileResponse::WriteContents(ListenSocket* socket) const { + DCHECK(file_.get()); + if (file_.get()) { + socket->Send(reinterpret_cast<const char*>(file_->data()), + file_->length(), false); + } +} + +size_t FileResponse::ContentLength() const { + if (file_.get() == NULL) { + file_.reset(new file_util::MemoryMappedFile()); + if (!file_->Initialize(file_path_)) { + NOTREACHED(); + file_.reset(); + } + } + return file_.get() ? file_->length() : 0; +} + +bool RedirectResponse::GetCustomHeaders(std::string* headers) const { + *headers = StringPrintf("HTTP/1.1 302 Found\r\n" + "Connection: close\r\n" + "Content-Length: 0\r\n" + "Content-Type: text/html\r\n" + "Location: %hs\r\n\r\n", redirect_url_.c_str()); + return true; +} + +SimpleWebServer::SimpleWebServer(int port) { + CHECK(MessageLoop::current()) << "SimpleWebServer requires a message loop"; + net::EnsureWinsockInit(); + AddResponse(&quit_); + server_ = ListenSocket::Listen("127.0.0.1", port, this); + DCHECK(server_.get() != NULL); +} + +SimpleWebServer::~SimpleWebServer() { + ConnectionList::const_iterator it; + for (it = connections_.begin(); it != connections_.end(); it++) + delete (*it); + connections_.clear(); +} + +void SimpleWebServer::AddResponse(Response* response) { + responses_.push_back(response); +} + +Response* SimpleWebServer::FindResponse(const Request& request) const { + std::list<Response*>::const_iterator it; + for (it = responses_.begin(); it != responses_.end(); it++) { + Response* response = (*it); + if (response->Matches(request)) { + return response; + } + } + return NULL; +} + +Connection* SimpleWebServer::FindConnection(const ListenSocket* socket) const { + ConnectionList::const_iterator it; + for (it = connections_.begin(); it != connections_.end(); it++) { + if ((*it)->IsSame(socket)) { + return (*it); + } + } + return NULL; +} + +void SimpleWebServer::DidAccept(ListenSocket* server, + ListenSocket* connection) { + connections_.push_back(new Connection(connection)); +} + +void SimpleWebServer::DidRead(ListenSocket* connection, + const std::string& data) { + Connection* c = FindConnection(connection); + DCHECK(c); + c->AddData(data); + if (c->CheckRequestReceived()) { + const Request& request = c->request(); + Response* response = FindResponse(request); + if (response) { + std::string headers; + if (!response->GetCustomHeaders(&headers)) { + std::string content_type; + if (!response->GetContentType(&content_type)) + content_type = kDefaultContentType; + headers = StringPrintf(kDefaultHeaderTemplate, kStatusOk, + content_type.c_str(), response->ContentLength()); + } + + connection->Send(headers, false); + response->WriteContents(connection); + response->IncrementAccessCounter(); + } else { + std::string payload = "sorry, I can't find " + request.path(); + std::string headers(StringPrintf(kDefaultHeaderTemplate, kStatusNotFound, + kDefaultContentType, payload.length())); + connection->Send(headers, false); + connection->Send(payload, false); + } + } +} + +void SimpleWebServer::DidClose(ListenSocket* sock) { + // To keep the historical list of connections reasonably tidy, we delete + // 404's when the connection ends. + Connection* c = FindConnection(sock); + DCHECK(c); + if (!FindResponse(c->request())) { + // extremely inefficient, but in one line and not that common... :) + connections_.erase(std::find(connections_.begin(), connections_.end(), c)); + delete c; + } +} + +} // namespace test_server diff --git a/chrome_frame/test/test_server.h b/chrome_frame/test/test_server.h new file mode 100644 index 0000000..896b2b3 --- /dev/null +++ b/chrome_frame/test/test_server.h @@ -0,0 +1,296 @@ +// Copyright (c) 2006-2008 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. + +#ifndef CHROME_FRAME_TEST_TEST_SERVER_H_ +#define CHROME_FRAME_TEST_TEST_SERVER_H_ + +// Implementation of an HTTP server for tests. +// To instantiate the server, make sure you have a message loop on the +// current thread and then create an instance of the SimpleWebServer class. +// The server uses two basic concepts, a request and a response. +// The Response interface represents an item (e.g. a document) available from +// the server. A Request object represents a request from a client (e.g. a +// browser). There are several basic Response classes implemented in this file, +// all derived from the Response interface. +// +// Here's a simple example that starts a web server that can serve up +// a single document (http://localhost:1337/foo). +// All other requests will get a 404. +// +// MessageLoopForUI loop; +// test_server::SimpleWebServer server(1337); +// test_server::SimpleResponse document("/foo", "Hello World!"); +// test_server.AddResponse(&document); +// loop.MessageLoop::Run(); +// +// To close the web server, just go to http://localhost:1337/quit. +// +// All Response classes count how many times they have been accessed. Just +// call Response::accessed(). +// +// To implement a custom response object (e.g. to match against a request +// based on some data, serve up dynamic content or take some action on the +// server), just inherit from one of the response classes or directly from the +// Response interface and add your response object to the server's list of +// response objects. + +#include <list> +#include <string> + +#include "base/basictypes.h" +#include "base/file_util.h" +#include "net/base/listen_socket.h" + +namespace test_server { + +class Request { + public: + Request() : content_length_(0) { + } + + void ParseHeaders(const std::string& headers); + + const std::string& method() const { + return method_; + } + + const std::string& path() const { + return path_; + } + + const std::string& headers() const { + return headers_; + } + + size_t content_length() const { + return content_length_; + } + + protected: + std::string method_; + std::string path_; + std::string version_; + std::string headers_; + size_t content_length_; + + private: + DISALLOW_COPY_AND_ASSIGN(Request); +}; + +// Manages request headers for a single request. +// For each successful request that's made, the server will keep an instance +// of this class so that they can be checked even after the server has been +// shut down. +class Connection { + public: + explicit Connection(ListenSocket* sock) : socket_(sock) { + } + + ~Connection() { + } + + bool IsSame(const ListenSocket* socket) const { + return socket_ == socket; + } + + void AddData(const std::string& data) { + data_ += data; + } + + bool CheckRequestReceived(); + + const Request& request() const { + return request_; + } + + protected: + scoped_refptr<ListenSocket> socket_; + std::string data_; + Request request_; + + private: + DISALLOW_COPY_AND_ASSIGN(Connection); +}; + +// Abstract interface with default implementations for some of the methods and +// a counter for how many times the response object has served requests. +class Response { + public: + Response() : accessed_(0) { + } + + virtual ~Response() { + } + + // Returns true if this response object should be used for a given request. + virtual bool Matches(const Request& r) const = 0; + + // Response objects can optionally supply their own HTTP headers, completely + // bypassing the default ones. + virtual bool GetCustomHeaders(std::string* headers) const { + return false; + } + + // Optionally provide a content type. Return false if you don't specify + // a content type. + virtual bool GetContentType(std::string* content_type) const { + return false; + } + + virtual size_t ContentLength() const { + return 0; + } + + virtual void WriteContents(ListenSocket* socket) const { + } + + void IncrementAccessCounter() { + accessed_++; + } + + size_t accessed() const { + return accessed_; + } + + protected: + size_t accessed_; + + private: + DISALLOW_COPY_AND_ASSIGN(Response); +}; + +// Partial implementation of Response that matches a request's path. +// This is just a convenience implementation for the boilerplate implementation +// of Matches(). Don't instantiate directly. +class ResponseForPath : public Response { + public: + explicit ResponseForPath(const char* request_path) + : request_path_(request_path) { + } + + virtual bool Matches(const Request& r) const { + return r.path().compare(request_path_) == 0; + } + + protected: + std::string request_path_; + + private: + DISALLOW_COPY_AND_ASSIGN(ResponseForPath); +}; + +// A very basic implementation of a response. +// A simple response matches a single document path on the server +// (e.g. "/foo") and returns a document in the form of a string. +class SimpleResponse : public ResponseForPath { + public: + SimpleResponse(const char* request_path, const std::string& contents) + : ResponseForPath(request_path), contents_(contents) { + } + + virtual void WriteContents(ListenSocket* socket) const { + socket->Send(contents_.c_str(), contents_.length(), false); + } + + virtual size_t ContentLength() const { + return contents_.length(); + } + + protected: + std::string contents_; + + private: + DISALLOW_COPY_AND_ASSIGN(SimpleResponse); +}; + +// To serve up files from the web server, create an instance of FileResponse +// and add it to the server's list of responses. The content type of the +// file will be determined by calling FindMimeFromData which examines the +// contents of the file and performs registry lookups. +class FileResponse : public ResponseForPath { + public: + FileResponse(const char* request_path, const FilePath& file_path) + : ResponseForPath(request_path), file_path_(file_path) { + } + + virtual bool GetContentType(std::string* content_type) const; + virtual void WriteContents(ListenSocket* socket) const; + virtual size_t ContentLength() const; + + protected: + FilePath file_path_; + mutable scoped_ptr<file_util::MemoryMappedFile> file_; + + private: + DISALLOW_COPY_AND_ASSIGN(FileResponse); +}; + +// Returns a 302 (temporary redirect) to redirect the client from a path +// on the test server to a different URL. +class RedirectResponse : public ResponseForPath { + public: + RedirectResponse(const char* request_path, const std::string& redirect_url) + : ResponseForPath(request_path), redirect_url_(redirect_url) { + } + + virtual bool GetCustomHeaders(std::string* headers) const; + + protected: + std::string redirect_url_; + + private: + DISALLOW_COPY_AND_ASSIGN(RedirectResponse); +}; + +// typedef for a list of connections. Used by SimpleWebServer. +typedef std::list<Connection*> ConnectionList; + +// Implementation of a simple http server. +// Before creating an instance of the server, make sure the current thread +// has a message loop. +class SimpleWebServer : public ListenSocket::ListenSocketDelegate { + public: + explicit SimpleWebServer(int port); + virtual ~SimpleWebServer(); + + void AddResponse(Response* response); + + // ListenSocketDelegate overrides. + virtual void DidAccept(ListenSocket* server, ListenSocket* connection); + virtual void DidRead(ListenSocket* connection, const std::string& data); + virtual void DidClose(ListenSocket* sock); + + const ConnectionList& connections() { + return connections_; + } + + protected: + class QuitResponse : public SimpleResponse { + public: + QuitResponse() + : SimpleResponse("/quit", "So long and thanks for all the fish.") { + } + + virtual void QuitResponse::WriteContents(ListenSocket* socket) const { + SimpleResponse::WriteContents(socket); + MessageLoop::current()->Quit(); + } + }; + + Response* FindResponse(const Request& request) const; + Connection* FindConnection(const ListenSocket* socket) const; + + protected: + scoped_refptr<ListenSocket> server_; + ConnectionList connections_; + std::list<Response*> responses_; + QuitResponse quit_; + + private: + DISALLOW_COPY_AND_ASSIGN(SimpleWebServer); +}; + +} // namespace test_server + +#endif // CHROME_FRAME_TEST_TEST_SERVER_H_ diff --git a/chrome_frame/test/test_server_test.cc b/chrome_frame/test/test_server_test.cc new file mode 100644 index 0000000..4bd139e --- /dev/null +++ b/chrome_frame/test/test_server_test.cc @@ -0,0 +1,194 @@ +// Copyright (c) 2006-2008 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. + +#include <windows.h> +#include <wininet.h> + +#include "base/basictypes.h" +#include "base/path_service.h" +#include "base/scoped_handle_win.h" +#include "chrome_frame/test/test_server.h" +#include "net/base/cookie_monster.h" +#include "net/base/host_resolver_proc.h" +#include "net/disk_cache/disk_cache.h" +#include "net/http/http_cache.h" +#include "net/proxy/proxy_service.h" +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_unittest.h" +#include "testing/gtest/include/gtest/gtest.h" + +class TestServerTest: public testing::Test { + protected: + virtual void SetUp() { + PathService::Get(base::DIR_SOURCE_ROOT, &source_path_); + source_path_ = source_path_.Append(FILE_PATH_LITERAL("chrome_frame")); + } + virtual void TearDown() { + } + + public: + const FilePath& source_path() const { + return source_path_; + } + + protected: + FilePath source_path_; +}; + +namespace { + +class ScopedInternet { + public: + explicit ScopedInternet(HINTERNET handle) + : h_(handle) { + } + ~ScopedInternet() { + if (h_) { + InternetCloseHandle(h_); + } + } + + operator HINTERNET() { + return h_; + } + + protected: + HINTERNET h_; +}; + +class URLRequestTestContext : public URLRequestContext { + public: + URLRequestTestContext() { + host_resolver_ = net::CreateSystemHostResolver(); + proxy_service_ = net::ProxyService::CreateNull(); + ssl_config_service_ = new net::SSLConfigServiceDefaults; + http_transaction_factory_ = + new net::HttpCache( + net::HttpNetworkLayer::CreateFactory(host_resolver_, proxy_service_, + ssl_config_service_), + disk_cache::CreateInMemoryCacheBackend(0)); + // In-memory cookie store. + cookie_store_ = new net::CookieMonster(); + } + + virtual ~URLRequestTestContext() { + delete http_transaction_factory_; + } +}; + +class TestURLRequest : public URLRequest { + public: + TestURLRequest(const GURL& url, Delegate* delegate) + : URLRequest(url, delegate) { + set_context(new URLRequestTestContext()); + } +}; + +class UrlTaskChain { + public: + UrlTaskChain(const char* url, UrlTaskChain* next) + : url_(url), next_(next) { + } + + void Run() { + EXPECT_EQ(0, delegate_.response_started_count()); + + MessageLoopForIO loop; + + TestURLRequest r(GURL(url_), &delegate_); + r.Start(); + EXPECT_TRUE(r.is_pending()); + + MessageLoop::current()->Run(); + + EXPECT_EQ(1, delegate_.response_started_count()); + EXPECT_FALSE(delegate_.received_data_before_response()); + EXPECT_NE(0, delegate_.bytes_received()); + } + + UrlTaskChain* next() const { + return next_; + } + + const std::string& response() const { + return delegate_.data_received(); + } + + protected: + std::string url_; + TestDelegate delegate_; + UrlTaskChain* next_; +}; + +DWORD WINAPI FetchUrl(void* param) { + UrlTaskChain* task = reinterpret_cast<UrlTaskChain*>(param); + while (task != NULL) { + task->Run(); + task = task->next(); + } + + return 0; +} + +struct QuitMessageHit { + explicit QuitMessageHit(MessageLoopForUI* loop) : loop_(loop), hit_(false) { + } + + MessageLoopForUI* loop_; + bool hit_; +}; + +void QuitMessageLoop(QuitMessageHit* msg) { + msg->hit_ = true; + msg->loop_->PostTask(FROM_HERE, new MessageLoop::QuitTask); +} + +} // end namespace + +TEST_F(TestServerTest, DISABLED_TestServer) { + // The web server needs a loop to exist on this thread during construction + // the loop must be created before we construct the server. + MessageLoopForUI loop; + + test_server::SimpleWebServer server(1337); + test_server::SimpleResponse person("/person", "Guthrie Govan!"); + server.AddResponse(&person); + test_server::FileResponse file("/file", source_path().Append( + FILE_PATH_LITERAL("CFInstance.js"))); + server.AddResponse(&file); + test_server::RedirectResponse redir("/goog", "http://www.google.com/"); + server.AddResponse(&redir); + + // We should never hit this, but it's our way to break out of the test if + // things start hanging. + QuitMessageHit quit_msg(&loop); + loop.PostDelayedTask(FROM_HERE, + NewRunnableFunction(QuitMessageLoop, &quit_msg), + 10 * 1000); + + UrlTaskChain quit_task("http://localhost:1337/quit", NULL); + UrlTaskChain fnf_task("http://localhost:1337/404", &quit_task); + UrlTaskChain person_task("http://localhost:1337/person", &fnf_task); + UrlTaskChain file_task("http://localhost:1337/file", &person_task); + UrlTaskChain goog_task("http://localhost:1337/goog", &file_task); + + DWORD tid = 0; + ScopedHandle worker(::CreateThread(NULL, 0, FetchUrl, &goog_task, 0, &tid)); + loop.MessageLoop::Run(); + + EXPECT_FALSE(quit_msg.hit_); + if (!quit_msg.hit_) { + EXPECT_EQ(::WaitForSingleObject(worker, 10 * 1000), WAIT_OBJECT_0); + + EXPECT_EQ(person.accessed(), 1); + EXPECT_EQ(file.accessed(), 1); + EXPECT_EQ(redir.accessed(), 1); + + EXPECT_TRUE(person_task.response().find("Guthrie") != std::string::npos); + EXPECT_TRUE(file_task.response().find("function") != std::string::npos); + EXPECT_TRUE(goog_task.response().find("<title>") != std::string::npos); + } else { + ::TerminateThread(worker, ~0); + } +} diff --git a/chrome_frame/test/util_unittests.cc b/chrome_frame/test/util_unittests.cc new file mode 100644 index 0000000..dc3d72d --- /dev/null +++ b/chrome_frame/test/util_unittests.cc @@ -0,0 +1,120 @@ +// Copyright (c) 2006-2009 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.
+
+#include "base/file_version_info.h"
+#include "chrome_frame/test/chrome_frame_unittests.h"
+#include "chrome_frame/utils.h"
+
+const wchar_t kChannelName[] = L"-dev";
+const wchar_t kSuffix[] = L"-fix";
+
+TEST(UtilTests, AppendSuffixToChannelNameTest) {
+ std::wstring str_base;
+ std::wstring channel_name(kChannelName);
+ std::wstring suffix(kSuffix);
+
+ str_base = L"2.0-dev-bar";
+ EXPECT_TRUE(AppendSuffixToChannelName(&str_base, channel_name, suffix));
+ EXPECT_STREQ(L"2.0-dev-fix-bar", str_base.c_str());
+
+ str_base = L"2.0-dev-fix-bar";
+ EXPECT_FALSE(AppendSuffixToChannelName(&str_base, channel_name, suffix));
+ EXPECT_STREQ(L"2.0-dev-fix-bar", str_base.c_str());
+
+ str_base = L"2.0-dev-bar-dev-bar";
+ EXPECT_TRUE(AppendSuffixToChannelName(&str_base, channel_name, suffix));
+ EXPECT_STREQ(L"2.0-dev-fix-bar-dev-bar", str_base.c_str());
+
+ str_base = L"2.0";
+ EXPECT_FALSE(AppendSuffixToChannelName(&str_base, channel_name, suffix));
+ EXPECT_STREQ(L"2.0", str_base.c_str());
+
+ str_base = L"2.0-devvvv";
+ EXPECT_TRUE(AppendSuffixToChannelName(&str_base, channel_name, suffix));
+ EXPECT_STREQ(L"2.0-dev-fixvvv", str_base.c_str());
+}
+
+TEST(UtilTests, RemoveSuffixFromStringTest) {
+ std::wstring str_base;
+ std::wstring channel_name(kChannelName);
+ std::wstring suffix(kSuffix);
+
+ str_base = L"2.0-dev-fix";
+ EXPECT_TRUE(RemoveSuffixFromChannelName(&str_base, channel_name, suffix));
+ EXPECT_STREQ(L"2.0-dev", str_base.c_str());
+
+ str_base = L"2.0-dev-fix-full";
+ EXPECT_TRUE(RemoveSuffixFromChannelName(&str_base, channel_name, suffix));
+ EXPECT_STREQ(L"2.0-dev-full", str_base.c_str());
+
+ str_base = L"2.0";
+ EXPECT_FALSE(RemoveSuffixFromChannelName(&str_base, channel_name, suffix));
+ EXPECT_STREQ(L"2.0", str_base.c_str());
+
+ str_base = L"2.0-dev";
+ EXPECT_FALSE(RemoveSuffixFromChannelName(&str_base, channel_name, suffix));
+ EXPECT_STREQ(L"2.0-dev", str_base.c_str());
+
+ str_base = L"2.0-fix";
+ EXPECT_FALSE(RemoveSuffixFromChannelName(&str_base, channel_name, suffix));
+ EXPECT_STREQ(L"2.0-fix", str_base.c_str());
+
+ str_base = L"2.0-full-fix";
+ EXPECT_FALSE(RemoveSuffixFromChannelName(&str_base, channel_name, suffix));
+ EXPECT_STREQ(L"2.0-full-fix", str_base.c_str());
+
+ str_base = L"2.0-dev-dev-fix";
+ EXPECT_TRUE(RemoveSuffixFromChannelName(&str_base, channel_name, suffix));
+ EXPECT_STREQ(L"2.0-dev-dev", str_base.c_str());
+}
+
+TEST(UtilTests, GetModuleVersionTest) {
+ HMODULE mod = GetModuleHandle(L"kernel32.dll");
+ EXPECT_NE(mod, static_cast<HMODULE>(NULL));
+ wchar_t path[MAX_PATH] = {0};
+ GetModuleFileName(mod, path, arraysize(path));
+ + // Use the method that goes to disk + scoped_ptr<FileVersionInfo> base_info( + FileVersionInfo::CreateFileVersionInfo(path)); + EXPECT_TRUE(base_info.get() != NULL); + + // Use the method that doesn't go to disk + uint32 low = 0, high = 0; + EXPECT_TRUE(GetModuleVersion(mod, &high, &low)); + EXPECT_NE(high, 0); + EXPECT_NE(low, 0); + + // Make sure they give the same results. + VS_FIXEDFILEINFO* fixed_info = base_info->fixed_file_info(); + EXPECT_TRUE(fixed_info != NULL); +
+ EXPECT_EQ(fixed_info->dwFileVersionMS, static_cast<DWORD>(high));
+ EXPECT_EQ(fixed_info->dwFileVersionLS, static_cast<DWORD>(low));
+}
+
+TEST(UtilTests, HaveSameOrigin) {
+ struct OriginCompare {
+ const char* a;
+ const char* b;
+ bool same_origin;
+ } test_cases[] = {
+ { "", "", true },
+ { "*", "*", true },
+ { "*", "+", false },
+ { "http://www.google.com/", "http://www.google.com/", true },
+ { "http://www.google.com", "http://www.google.com/", true },
+ { "http://www.google.com:80/", "http://www.google.com/", true },
+ { "http://www.google.com:8080/", "http://www.google.com/", false },
+ { "https://www.google.com/", "http://www.google.com/", false },
+ { "http://docs.google.com/", "http://www.google.com/", false },
+ { "https://www.google.com/", "https://www.google.com:443/", true },
+ { "https://www.google.com/", "https://www.google.com:443", true },
+ };
+
+ for (int i = 0; i < arraysize(test_cases); ++i) {
+ const OriginCompare& test = test_cases[i];
+ EXPECT_EQ(test.same_origin, HaveSameOrigin(test.a, test.b));
+ }
+}
diff --git a/chrome_frame/test_utils.cc b/chrome_frame/test_utils.cc new file mode 100644 index 0000000..2d47c71 --- /dev/null +++ b/chrome_frame/test_utils.cc @@ -0,0 +1,95 @@ +// Copyright (c) 2009 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. + +#include "chrome_frame/test_utils.h" + +#include <atlbase.h> +#include <atlwin.h> + +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/path_service.h" +#include "chrome/common/chrome_paths.h" +#include "testing/gtest/include/gtest/gtest.h" + +// Statics + +std::wstring ScopedChromeFrameRegistrar::GetChromeFrameBuildPath() { + std::wstring build_path; + PathService::Get(chrome::DIR_APP, &build_path); + file_util::AppendToPath(&build_path, L"servers\\npchrome_tab.dll"); + file_util::PathExists(build_path); + return build_path; +} + +void ScopedChromeFrameRegistrar::RegisterDefaults() { + std::wstring dll_path_ = GetChromeFrameBuildPath(); + RegisterAtPath(dll_path_); +} + +void ScopedChromeFrameRegistrar::RegisterAtPath( + const std::wstring& path) { + + ASSERT_FALSE(path.empty()); + HMODULE chrome_frame_dll_handle = LoadLibrary(path.c_str()); + ASSERT_TRUE(chrome_frame_dll_handle != NULL); + + typedef HRESULT (STDAPICALLTYPE* DllRegisterServerFn)(); + DllRegisterServerFn register_server = + reinterpret_cast<DllRegisterServerFn>(GetProcAddress( + chrome_frame_dll_handle, "DllRegisterServer")); + + ASSERT_TRUE(register_server != NULL); + EXPECT_HRESULT_SUCCEEDED((*register_server)()); + + DllRegisterServerFn register_npapi_server = + reinterpret_cast<DllRegisterServerFn>(GetProcAddress( + chrome_frame_dll_handle, "RegisterNPAPIPlugin")); + + if (register_npapi_server != NULL) + EXPECT_HRESULT_SUCCEEDED((*register_npapi_server)()); + + ASSERT_TRUE(FreeLibrary(chrome_frame_dll_handle)); +} + +// Non-statics + +ScopedChromeFrameRegistrar::ScopedChromeFrameRegistrar() { + original_dll_path_ = GetChromeFrameBuildPath(); + RegisterChromeFrameAtPath(original_dll_path_); +} + +ScopedChromeFrameRegistrar::~ScopedChromeFrameRegistrar() { + if (FilePath(original_dll_path_) != FilePath(new_chrome_frame_dll_path_)) { + RegisterChromeFrameAtPath(original_dll_path_); + } +} + +void ScopedChromeFrameRegistrar::RegisterChromeFrameAtPath( + const std::wstring& path) { + RegisterAtPath(path); + new_chrome_frame_dll_path_ = path; +} + +void ScopedChromeFrameRegistrar::RegisterReferenceChromeFrameBuild() { + std::wstring reference_build_dir; + ASSERT_TRUE(PathService::Get(chrome::DIR_APP, &reference_build_dir)); + + file_util::UpOneDirectory(&reference_build_dir); + file_util::UpOneDirectory(&reference_build_dir); + + file_util::AppendToPath(&reference_build_dir, L"chrome_frame"); + file_util::AppendToPath(&reference_build_dir, L"tools"); + file_util::AppendToPath(&reference_build_dir, L"test"); + file_util::AppendToPath(&reference_build_dir, L"reference_build"); + file_util::AppendToPath(&reference_build_dir, L"chrome"); + file_util::AppendToPath(&reference_build_dir, L"servers"); + file_util::AppendToPath(&reference_build_dir, L"npchrome_tab.dll"); + + RegisterChromeFrameAtPath(reference_build_dir); +} + +std::wstring ScopedChromeFrameRegistrar::GetChromeFrameDllPath() const { + return new_chrome_frame_dll_path_; +} diff --git a/chrome_frame/test_utils.h b/chrome_frame/test_utils.h new file mode 100644 index 0000000..fb470a0 --- /dev/null +++ b/chrome_frame/test_utils.h @@ -0,0 +1,39 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_FRAME_TEST_UTILS_H_ +#define CHROME_FRAME_TEST_UTILS_H_ + +#include <string> + +// Helper class used to register different chrome frame DLLs while running +// tests. At construction, this registers the DLL found in the build path. +// At destruction, again registers the DLL found in the build path if another +// DLL has since been registered. Triggers GTEST asserts on failure. +// +// TODO(robertshield): Ideally, make this class restore the originally +// registered chrome frame DLL (e.g. by looking in HKCR) on destruction. +class ScopedChromeFrameRegistrar { + public: + ScopedChromeFrameRegistrar(); + virtual ~ScopedChromeFrameRegistrar(); + + void RegisterChromeFrameAtPath(const std::wstring& path); + void RegisterReferenceChromeFrameBuild(); + + std::wstring GetChromeFrameDllPath() const; + + static std::wstring GetChromeFrameBuildPath(); + static void RegisterAtPath(const std::wstring& path); + static void RegisterDefaults(); + + private: + // Contains the path of the most recently registered npchrome_tab.dll. + std::wstring new_chrome_frame_dll_path_; + + // Contains the path of the npchrome_tab.dll to be registered at destruction. + std::wstring original_dll_path_; +}; + +#endif // CHROME_FRAME_TEST_UTILS_H_ diff --git a/chrome_frame/tools/test/page_cycler/cf_cycler.py b/chrome_frame/tools/test/page_cycler/cf_cycler.py new file mode 100644 index 0000000..fd49ae8 --- /dev/null +++ b/chrome_frame/tools/test/page_cycler/cf_cycler.py @@ -0,0 +1,99 @@ +# Copyright (c) 2009 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.
+
+"""Automates IE to visit a list of web sites while running CF in full tab mode.
+
+The page cycler automates IE and navigates it to a series of URLs. It is
+designed to be run with Chrome Frame configured to load every URL inside
+CF full tab mode.
+
+TODO(robertshield): Make use of the python unittest module as per
+review comments.
+"""
+
+import optparse
+import sys
+import time
+import win32com.client
+import win32gui
+
+def LoadSiteList(path):
+ """Loads a list of URLs from |path|.
+
+ Expects the URLs to be separated by newlines, with no leading or trailing
+ whitespace.
+
+ Args:
+ path: The path to a file containing a list of new-line separated URLs.
+
+ Returns:
+ A list of strings, each one a URL.
+ """
+ f = open(path)
+ urls = f.readlines()
+ f.close()
+ return urls
+
+def LaunchIE():
+ """Starts up IE, makes it visible and returns the automation object.
+
+ Returns:
+ The IE automation object.
+ """
+ ie = win32com.client.Dispatch("InternetExplorer.Application")
+ ie.visible = 1
+ win32gui.SetForegroundWindow(ie.HWND)
+ return ie
+
+def RunTest(url, ie):
+ """Loads |url| into the InternetExplorer.Application instance in |ie|.
+
+ Waits for the Document object to be created and then waits for
+ the document ready state to reach READYSTATE_COMPLETE.
+ Args:
+ url: A string containing the url to navigate to.
+ ie: The IE automation object to navigate.
+ """
+
+ print "Navigating to " + url
+ ie.Navigate(url)
+ timer = 0
+
+ READYSTATE_COMPLETE = 4
+
+ last_ready_state = -1
+ for retry in xrange(60):
+ try:
+ # TODO(robertshield): Become an event sink instead of polling for
+ # changes to the ready state.
+ last_ready_state = ie.Document.ReadyState
+ if last_ready_state == READYSTATE_COMPLETE:
+ break
+ except:
+ # TODO(robertshield): Find the precise exception related to ie.Document
+ # being not accessible and handle it here.
+ print "Unexpected error:", sys.exc_info()[0]
+ raise
+ time.sleep(1)
+
+ if last_ready_state != READYSTATE_COMPLETE:
+ print "Timeout waiting for " + url
+
+def main():
+ parser = optparse.OptionParser()
+ parser.add_option('-u', '--url_list', default='urllist',
+ help='The path to the list of URLs')
+ (opts, args) = parser.parse_args()
+
+ urls = LoadSiteList(opts.url_list)
+ ie = LaunchIE()
+ for url in urls:
+ RunTest(url, ie)
+ time.sleep(1)
+ ie.visible = 0
+ ie.Quit()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/chrome_frame/tools/test/page_cycler/urllist b/chrome_frame/tools/test/page_cycler/urllist new file mode 100644 index 0000000..3242774 --- /dev/null +++ b/chrome_frame/tools/test/page_cycler/urllist @@ -0,0 +1,500 @@ +http://www.yahoo.com +http://www.msn.com +http://www.google.com +http://www.youtube.com +http://www.live.com +http://www.myspace.com +http://www.baidu.com +http://www.orkut.com +http://www.wikipedia.org +http://www.qq.com +http://www.yahoo.co.jp +http://www.microsoft.com +http://www.megaupload.com +http://www.sina.com.cn +http://www.hi5.com +http://www.blogger.com +http://www.facebook.com +http://www.rapidshare.com +http://www.ebay.com +http://www.fotolog.net +http://www.friendster.com +http://www.sohu.com +http://www.163.com +http://www.google.co.uk +http://www.mail.ru +http://www.google.fr +http://www.google.com.br +http://www.google.de +http://www.passport.net +http://www.taobao.com +http://www.amazon.com +http://www.skyblog.com +http://www.yahoo.com.cn +http://www.google.cl +http://www.bbc.co.uk +http://www.google.es +http://www.google.cn +http://www.google.pl +http://www.imdb.com +http://www.tom.com +http://www.yandex.ru +http://www.wretch.cc +http://www.google.co.jp +http://www.uol.com.br +http://www.flickr.com +http://www.photobucket.com +http://www.craigslist.org +http://www.go.com +http://www.onet.pl +http://www.imageshack.us +http://www.google.com.sa +http://www.google.com.mx +http://www.aol.com +http://www.google.com.tr +http://www.dailymotion.com +http://www.allegro.pl +http://www.rambler.ru +http://www.fc2.com +http://www.xunlei.com +http://www.ebay.co.uk +http://www.google.com.ar +http://www.google.ca +http://www.seznam.cz +http://www.mixi.jp +http://www.free.fr +http://www.imagevenue.com +http://www.google.com.pe +http://www.google.it +http://www.google.co.in +http://www.terra.com.br +http://www.cnn.com +http://www.livejournal.com +http://www.skyrock.com +http://www.ebay.de +http://www.geocities.com +http://www.adultfriendfinder.com +http://www.wp.pl +http://www.googlesyndication.com +http://www.adobe.com +http://www.google.co.ve +http://www.bebo.com +http://www.xanga.com +http://www.globo.com +http://www.google.com.co +http://www.google.com.eg +http://www.one.lt +http://www.wordpress.com +http://www.rakuten.co.jp +http://www.uwants.com +http://www.sendspace.com +http://www.apple.com +http://www.google.com.vn +http://www.discuss.com.hk +http://www.deviantart.com +http://www.digg.com +http://www.tagged.com +http://www.livedoor.com +http://www.vnexpress.net +http://www.eastmoney.com +http://www.soso.com +http://www.starware.com +http://www.badongo.com +http://www.google.co.th +http://www.naver.com +http://www.rediff.com +http://www.iwiw.hu +http://www.icq.com +http://www.vnet.cn +http://www.ig.com.br +http://www.badoo.com +http://www.alibaba.com +http://www.google.com.au +http://www.maktoob.com +http://www.newsgroup.la +http://www.about.com +http://www.download.com +http://www.kooora.com +http://www.google.co.il +http://www.comcast.net +http://www.google.nl +http://www.google.be +http://www.megarotic.com +http://www.sexyono.com +http://www.sourceforge.net +http://www.walla.co.il +http://www.fotka.pl +http://www.pchome.com.tw +http://www.theplanet.com +http://www.google.ae +http://www.orange.fr +http://www.interia.pl +http://www.invisionfree.com +http://www.rapidshare.de +http://www.tudou.com +http://www.statcounter.com +http://www.56.com +http://www.netlog.com +http://www.4shared.com +http://www.mywebsearch.com +http://www.digitalpoint.com +http://www.mininova.org +http://www.dantri.com.vn +http://www.126.com +http://www.goo.ne.jp +http://www.dell.com +http://www.google.co.hu +http://www.google.pt +http://www.information.com +http://www.youporn.com +http://www.metacafe.com +http://www.google.lt +http://www.google.ro +http://www.nba.com +http://www.metroflog.com +http://www.miniclip.com +http://www.cnet.com +http://www.sogou.com +http://www.ebay.fr +http://www.weather.com +http://www.depositfiles.com +http://www.hp.com +http://www.google.ru +http://www.51.com +http://www.infoseek.co.jp +http://www.linkedin.com +http://www.gmx.net +http://www.xinhuanet.com +http://www.narod.ru +http://www.multiply.com +http://www.pomoho.com +http://www.nytimes.com +http://www.yourfilehost.com +http://www.gamespot.com +http://www.typepad.com +http://www.ask.com +http://www.89.com +http://www.geocities.jp +http://www.yam.com +http://www.neopets.com +http://www.mercadolivre.com.br +http://www.centrum.cz +http://www.mapquest.com +http://www.univision.com +http://www.zol.com.cn +http://www.veoh.com +http://www.amazon.co.jp +http://www.6park.com +http://www.24h.com.vn +http://www.china.com +http://www.minijuegos.com +http://www.sh3bwah.com +http://www.mop.com +http://www.miarroba.com +http://www.tianya.cn +http://www.ign.com +http://www.technorati.com +http://www.yesky.com +http://www.pornotube.com +http://www.google.hr +http://www.cmfu.com +http://www.aebn.net +http://www.mynet.com +http://www.nifty.com +http://www.livejasmin.com +http://www.wwe.com +http://www.daum.net +http://www.nastydollars.com +http://www.filefront.com +http://www.bangbros1.com +http://www.doubleclick.com +http://www.torrentspy.com +http://www.zaycev.net +http://www.chinaren.com +http://www.youku.com +http://www.altavista.com +http://www.spiegel.de +http://www.icio.us +http://www.hinet.net +http://www.anonym.to +http://www.usercash.com +http://www.gamer.com.tw +http://www.sapo.pt +http://www.2ch.net +http://www.tripod.com +http://www.reference.com +http://www.blogchina.com +http://www.piczo.com +http://www.startimes2.com +http://www.zshare.net +http://www.uusee.com +http://www.dada.net +http://www.amazon.co.uk +http://www.panet.co.il +http://www.6rb.com +http://www.smileycentral.com +http://www.juggcrew.com +http://www.liveinternet.ru +http://www.google.com.my +http://www.mercadolibre.com.ar +http://www.sakura.ne.jp +http://www.gamefaqs.com +http://www.jrj.com.cn +http://www.fanfiction.net +http://www.libero.it +http://www.google.se +http://www.milliyet.com.tr +http://www.imeem.com +http://www.archive.org +http://www.mediafire.com +http://www.google.com.sg +http://www.blogfa.com +http://www.filefactory.com +http://www.softonic.com +http://www.homeway.com.cn +http://www.google.com.tw +http://www.pconline.com.cn +http://www.blog.cz +http://www.symantec.com +http://www.biglobe.ne.jp +http://www.mlb.com +http://www.webshots.com +http://www.hatena.ne.jp +http://www.phoenixtv.com +http://www.google.co.cr +http://www.nate.com +http://www.google.gr +http://www.mobile.de +http://www.pornaccess.com +http://www.ynet.co.il +http://www.amazon.de +http://www.imagefap.com +http://www.whenu.com +http://www.vietnamnet.vn +http://www.ebay.it +http://www.hurriyet.com.tr +http://www.freewebs.com +http://www.tuoitre.com.vn +http://www.php.net +http://www.broadcaster.com +http://www.google.at +http://www.xnxx.com +http://www.studiverzeichnis.com +http://www.elmundo.es +http://www.it168.com +http://www.lide.cz +http://www.wikimedia.org +http://www.wefong.com +http://www.mercadolibre.com.mx +http://www.thepiratebay.org +http://www.istockphoto.com +http://www.sanook.com +http://www.petardas.com +http://www.answers.com +http://www.no-ip.com +http://www.match.com +http://www.ebay.com.au +http://www.payserve.com +http://www.soufun.com +http://www.flurl.com +http://www.alice.it +http://www.globalsearch.cz +http://www.888.com +http://www.isohunt.com +http://www.poco.cn +http://www.ameblo.jp +http://www.heise.de +http://www.6rbtop.com +http://www.google.co.id +http://www.godaddy.com +http://www.google.fi +http://www.bala.com.cn +http://www.break.com +http://www.stumbleupon.com +http://www.feedburner.com +http://www.ngoisao.net +http://www.atnext.com +http://www.screensavers.com +http://www.17173.com +http://www.domaintools.com +http://www.nih.gov +http://www.novinky.cz +http://www.freeones.com +http://www.last.fm +http://www.skype.com +http://www.google.bg +http://www.21cn.com +http://www.o2.pl +http://www.musica.com +http://www.pplive.com +http://www.hkjc.com +http://www.grono.net +http://www.bramjnet.com +http://www.adbrite.com +http://www.divx.com +http://www.pcpop.com +http://www.verycd.com +http://www.cnfol.com +http://www.google.ch +http://www.myway.com +http://www.wangyou.com +http://www.gazeta.pl +http://www.milta1980.co.uk +http://www.prizee.com +http://www.livescore.com +http://www.facebox.com +http://www.overture.com +http://www.google.com.ua +http://www.cartoonnetwork.com +http://www.nicovideo.jp +http://www.ku6.com +http://www.it.com.cn +http://www.one.lv +http://www.t-online.de +http://www.kooxoo.com +http://www.super.cz +http://www.google.co.ma +http://www.xuite.net +http://www.qihoo.com +http://www.freelotto.com +http://www.sedoparking.com +http://www.monografias.com +http://www.dmoz.org +http://www.foxsports.com +http://www.mocxi.com +http://www.google.sk +http://www.as7apcool.com +http://www.igw.net.sa +http://www.slide.com +http://www.zhanzuo.com +http://www.idnes.cz +http://www.mobile9.com +http://www.hawaaworld.com +http://www.aljazeera.net +http://www.nana.co.il +http://www.seesaa.net +http://www.51job.com +http://www.abv.bg +http://www.paipai.com +http://www.real.com +http://www.cctv.com +http://www.tv.com +http://www.terra.cl +http://www.google.com.ph +http://www.wannawatch.com +http://www.thottbot.com +http://www.4399.com +http://www.5show.com +http://www.vkontakte.ru +http://www.cricinfo.com +http://www.hyves.nl +http://www.google.ie +http://www.cnnic.cn +http://www.tinypic.com +http://www.forumer.com +http://www.demonoid.com +http://www.ouou.com +http://www.mozilla.com +http://www.naukri.com +http://www.volam.com.vn +http://www.torrentz.com +http://www.google.co.za +http://www.google.jo +http://www.dvd4arab.com +http://www.msn.ca +http://www.clarin.com +http://www.rincondelvago.com +http://www.dmm.co.jp +http://www.1ting.com +http://www.everythinggirl.com +http://www.freemail.hu +http://www.xtube.com +http://www.4399.net +http://www.ocn.ne.jp +http://www.aweber.com +http://www.monster.com +http://www.chinaz.com +http://www.careerbuilder.com +http://www.megaclick.com +http://www.jeeran.com +http://www.w3.org +http://www.uume.com +http://www.sitepoint.com +http://www.people.com.cn +http://www.google.com.uy +http://www.mybloglog.com +http://www.9you.com +http://www.ebay.com.cn +http://www.zedo.com +http://www.juegosjuegos.com +http://www.sweetim.com +http://www.ikea.com +http://www.walmart.com +http://www.earthlink.net +http://www.turboupload.com +http://www.crunchyroll.com +http://www.google.com.bh +http://www.flogao.com.br +http://www.usps.com +http://www.6arab.com +http://www.target.com +http://www.wordreference.com +http://www.perfspot.com +http://www.gigasize.com +http://www.mercadolibre.com.ve +http://www.stockstar.com +http://www.3721.com +http://www.upspiral.com +http://www.clubbox.co.kr +http://www.trademe.co.nz +http://www.nokia.com +http://www.met-art.com +http://www.ppstream.com +http://www.ibm.com +http://www.qianlong.com +http://www.softpedia.com +http://www.adultadworld.com +http://www.webmasterworld.com +http://www.ifolder.ru +http://www.mofile.com +http://www.delfi.lt +http://www.sina.com.hk +http://www.google.com.bo +http://www.excite.co.jp +http://www.myfreepaysite.com +http://www.ev1servers.net +http://www.dyndns.org +http://www.wordpress.org +http://www.drivecleaner.com +http://www.google.co.nz +http://www.people.com +http://www.expedia.com +http://www.sonyericsson.com +http://www.msn.com.cn +http://www.hubotv.com +http://www.bangbros.com +http://www.mediaplex.com +http://www.google.com.qa +http://www.videosz.com +http://www.leo.org +http://www.atlas.cz +http://www.kinghost.com +http://www.aljayyash.net +http://www.irc-galleria.net +http://www.xiaonei.com +http://www.nnm.ru +http://www.tv-links.co.uk +http://www.indiatimes.com +http://www.icbc.com.cn +http://www.draugiem.lv +http://www.6rooms.com +http://www.hc360.com +http://www.focus.cn +http://www.atdmt.com +http://www.chilewarez.org +http://www.tripadvisor.com +http://www.mcanime.net +http://www.esnips.com +http://www.chinamobile.com +http://www.ebay.ca +http://www.im.tv diff --git a/chrome_frame/tools/test/reference_build/chrome/First Run b/chrome_frame/tools/test/reference_build/chrome/First Run new file mode 100644 index 0000000..852ad16 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/First Run @@ -0,0 +1 @@ +krome
diff --git a/chrome_frame/tools/test/reference_build/chrome/avcodec-52.dll b/chrome_frame/tools/test/reference_build/chrome/avcodec-52.dll Binary files differnew file mode 100644 index 0000000..9b55506 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/avcodec-52.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/avformat-52.dll b/chrome_frame/tools/test/reference_build/chrome/avformat-52.dll Binary files differnew file mode 100644 index 0000000..33d3dcf --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/avformat-52.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/avutil-50.dll b/chrome_frame/tools/test/reference_build/chrome/avutil-50.dll Binary files differnew file mode 100644 index 0000000..63f4d2d --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/avutil-50.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/chrome.dll b/chrome_frame/tools/test/reference_build/chrome/chrome.dll Binary files differnew file mode 100644 index 0000000..bc17fe4 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/chrome.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/chrome.exe b/chrome_frame/tools/test/reference_build/chrome/chrome.exe Binary files differnew file mode 100644 index 0000000..040fbaa --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/chrome.exe diff --git a/chrome_frame/tools/test/reference_build/chrome/chrome_dll.pdb b/chrome_frame/tools/test/reference_build/chrome/chrome_dll.pdb Binary files differnew file mode 100644 index 0000000..203f157 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/chrome_dll.pdb diff --git a/chrome_frame/tools/test/reference_build/chrome/chrome_exe.pdb b/chrome_frame/tools/test/reference_build/chrome/chrome_exe.pdb Binary files differnew file mode 100644 index 0000000..5d79f8b --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/chrome_exe.pdb diff --git a/chrome_frame/tools/test/reference_build/chrome/crash_service.exe b/chrome_frame/tools/test/reference_build/chrome/crash_service.exe Binary files differnew file mode 100644 index 0000000..1eac8ee --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/crash_service.exe diff --git a/chrome_frame/tools/test/reference_build/chrome/gears.dll b/chrome_frame/tools/test/reference_build/chrome/gears.dll Binary files differnew file mode 100644 index 0000000..db6d864 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/gears.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/icudt42.dll b/chrome_frame/tools/test/reference_build/chrome/icudt42.dll Binary files differnew file mode 100644 index 0000000..40a49c1 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/icudt42.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/ar.dll b/chrome_frame/tools/test/reference_build/chrome/locales/ar.dll Binary files differnew file mode 100644 index 0000000..d925ac3 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/ar.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/bg.dll b/chrome_frame/tools/test/reference_build/chrome/locales/bg.dll Binary files differnew file mode 100644 index 0000000..90d4933 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/bg.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/bn.dll b/chrome_frame/tools/test/reference_build/chrome/locales/bn.dll Binary files differnew file mode 100644 index 0000000..3364aaa --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/bn.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/ca.dll b/chrome_frame/tools/test/reference_build/chrome/locales/ca.dll Binary files differnew file mode 100644 index 0000000..9d8a07a --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/ca.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/cs.dll b/chrome_frame/tools/test/reference_build/chrome/locales/cs.dll Binary files differnew file mode 100644 index 0000000..c4b5d1c --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/cs.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/da.dll b/chrome_frame/tools/test/reference_build/chrome/locales/da.dll Binary files differnew file mode 100644 index 0000000..239cf00 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/da.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/de.dll b/chrome_frame/tools/test/reference_build/chrome/locales/de.dll Binary files differnew file mode 100644 index 0000000..44da5d3 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/de.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/el.dll b/chrome_frame/tools/test/reference_build/chrome/locales/el.dll Binary files differnew file mode 100644 index 0000000..4afbade --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/el.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/en-GB.dll b/chrome_frame/tools/test/reference_build/chrome/locales/en-GB.dll Binary files differnew file mode 100644 index 0000000..a3cc174 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/en-GB.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/en-US.dll b/chrome_frame/tools/test/reference_build/chrome/locales/en-US.dll Binary files differnew file mode 100644 index 0000000..6b4db3c --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/en-US.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/es-419.dll b/chrome_frame/tools/test/reference_build/chrome/locales/es-419.dll Binary files differnew file mode 100644 index 0000000..58ee8bc --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/es-419.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/es.dll b/chrome_frame/tools/test/reference_build/chrome/locales/es.dll Binary files differnew file mode 100644 index 0000000..96168ad --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/es.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/et.dll b/chrome_frame/tools/test/reference_build/chrome/locales/et.dll Binary files differnew file mode 100644 index 0000000..237750df --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/et.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/fi.dll b/chrome_frame/tools/test/reference_build/chrome/locales/fi.dll Binary files differnew file mode 100644 index 0000000..579a2a2 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/fi.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/fil.dll b/chrome_frame/tools/test/reference_build/chrome/locales/fil.dll Binary files differnew file mode 100644 index 0000000..12cc22e --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/fil.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/fr.dll b/chrome_frame/tools/test/reference_build/chrome/locales/fr.dll Binary files differnew file mode 100644 index 0000000..87003ac --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/fr.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/gu.dll b/chrome_frame/tools/test/reference_build/chrome/locales/gu.dll Binary files differnew file mode 100644 index 0000000..0fdc291 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/gu.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/he.dll b/chrome_frame/tools/test/reference_build/chrome/locales/he.dll Binary files differnew file mode 100644 index 0000000..332fa93 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/he.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/hi.dll b/chrome_frame/tools/test/reference_build/chrome/locales/hi.dll Binary files differnew file mode 100644 index 0000000..1eee966 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/hi.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/hr.dll b/chrome_frame/tools/test/reference_build/chrome/locales/hr.dll Binary files differnew file mode 100644 index 0000000..debe9d0 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/hr.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/hu.dll b/chrome_frame/tools/test/reference_build/chrome/locales/hu.dll Binary files differnew file mode 100644 index 0000000..8f6a8c2 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/hu.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/id.dll b/chrome_frame/tools/test/reference_build/chrome/locales/id.dll Binary files differnew file mode 100644 index 0000000..3c78bcc --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/id.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/it.dll b/chrome_frame/tools/test/reference_build/chrome/locales/it.dll Binary files differnew file mode 100644 index 0000000..25c39f7 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/it.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/ja.dll b/chrome_frame/tools/test/reference_build/chrome/locales/ja.dll Binary files differnew file mode 100644 index 0000000..1378e47 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/ja.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/kn.dll b/chrome_frame/tools/test/reference_build/chrome/locales/kn.dll Binary files differnew file mode 100644 index 0000000..6154988 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/kn.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/ko.dll b/chrome_frame/tools/test/reference_build/chrome/locales/ko.dll Binary files differnew file mode 100644 index 0000000..bbe62ed --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/ko.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/lt.dll b/chrome_frame/tools/test/reference_build/chrome/locales/lt.dll Binary files differnew file mode 100644 index 0000000..fd9069e --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/lt.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/lv.dll b/chrome_frame/tools/test/reference_build/chrome/locales/lv.dll Binary files differnew file mode 100644 index 0000000..a402aec --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/lv.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/ml.dll b/chrome_frame/tools/test/reference_build/chrome/locales/ml.dll Binary files differnew file mode 100644 index 0000000..6902ead --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/ml.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/mr.dll b/chrome_frame/tools/test/reference_build/chrome/locales/mr.dll Binary files differnew file mode 100644 index 0000000..d63aafe --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/mr.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/nb.dll b/chrome_frame/tools/test/reference_build/chrome/locales/nb.dll Binary files differnew file mode 100644 index 0000000..7043aa6 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/nb.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/nl.dll b/chrome_frame/tools/test/reference_build/chrome/locales/nl.dll Binary files differnew file mode 100644 index 0000000..52fbf1d --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/nl.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/or.dll b/chrome_frame/tools/test/reference_build/chrome/locales/or.dll Binary files differnew file mode 100644 index 0000000..34428ad --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/or.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/pl.dll b/chrome_frame/tools/test/reference_build/chrome/locales/pl.dll Binary files differnew file mode 100644 index 0000000..2836731 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/pl.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/pt-BR.dll b/chrome_frame/tools/test/reference_build/chrome/locales/pt-BR.dll Binary files differnew file mode 100644 index 0000000..5aa52e8 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/pt-BR.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/pt-PT.dll b/chrome_frame/tools/test/reference_build/chrome/locales/pt-PT.dll Binary files differnew file mode 100644 index 0000000..cc523c9 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/pt-PT.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/ro.dll b/chrome_frame/tools/test/reference_build/chrome/locales/ro.dll Binary files differnew file mode 100644 index 0000000..0ee4d70 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/ro.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/ru.dll b/chrome_frame/tools/test/reference_build/chrome/locales/ru.dll Binary files differnew file mode 100644 index 0000000..3fbfcfe --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/ru.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/sk.dll b/chrome_frame/tools/test/reference_build/chrome/locales/sk.dll Binary files differnew file mode 100644 index 0000000..da49806 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/sk.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/sl.dll b/chrome_frame/tools/test/reference_build/chrome/locales/sl.dll Binary files differnew file mode 100644 index 0000000..186d2f0 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/sl.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/sr.dll b/chrome_frame/tools/test/reference_build/chrome/locales/sr.dll Binary files differnew file mode 100644 index 0000000..9278069 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/sr.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/sv.dll b/chrome_frame/tools/test/reference_build/chrome/locales/sv.dll Binary files differnew file mode 100644 index 0000000..29ff6bf --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/sv.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/ta.dll b/chrome_frame/tools/test/reference_build/chrome/locales/ta.dll Binary files differnew file mode 100644 index 0000000..711739a --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/ta.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/te.dll b/chrome_frame/tools/test/reference_build/chrome/locales/te.dll Binary files differnew file mode 100644 index 0000000..5468bed --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/te.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/th.dll b/chrome_frame/tools/test/reference_build/chrome/locales/th.dll Binary files differnew file mode 100644 index 0000000..bb41ca2 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/th.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/tr.dll b/chrome_frame/tools/test/reference_build/chrome/locales/tr.dll Binary files differnew file mode 100644 index 0000000..5a9333c --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/tr.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/uk.dll b/chrome_frame/tools/test/reference_build/chrome/locales/uk.dll Binary files differnew file mode 100644 index 0000000..830904a --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/uk.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/vi.dll b/chrome_frame/tools/test/reference_build/chrome/locales/vi.dll Binary files differnew file mode 100644 index 0000000..3c3c425 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/vi.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/zh-CN.dll b/chrome_frame/tools/test/reference_build/chrome/locales/zh-CN.dll Binary files differnew file mode 100644 index 0000000..0a09475 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/zh-CN.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/locales/zh-TW.dll b/chrome_frame/tools/test/reference_build/chrome/locales/zh-TW.dll Binary files differnew file mode 100644 index 0000000..237c46c --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/locales/zh-TW.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/mini_installer.pdb b/chrome_frame/tools/test/reference_build/chrome/mini_installer.pdb Binary files differnew file mode 100644 index 0000000..111f61d --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/mini_installer.pdb diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/BottomUpProfileDataGridTree.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/BottomUpProfileDataGridTree.js new file mode 100644 index 0000000..89b4ddc --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/BottomUpProfileDataGridTree.js @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2009 280 North Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// Bottom Up Profiling shows the entire callstack backwards: +// The root node is a representation of each individual function called, and each child of that node represents +// a reverse-callstack showing how many of those calls came from it. So, unlike top-down, the statistics in +// each child still represent the root node. We have to be particularly careful of recursion with this mode +// because a root node can represent itself AND an ancestor. + +WebInspector.BottomUpProfileDataGridTree = function(/*ProfileView*/ aProfileView, /*ProfileNode*/ aProfileNode) +{ + WebInspector.ProfileDataGridTree.call(this, aProfileView, aProfileNode); + + // Iterate each node in pre-order. + var profileNodeUIDs = 0; + var profileNodeGroups = [[], [aProfileNode]]; + var visitedProfileNodesForCallUID = {}; + + this._remainingNodeInfos = []; + + for (var profileNodeGroupIndex = 0; profileNodeGroupIndex < profileNodeGroups.length; ++profileNodeGroupIndex) { + var parentProfileNodes = profileNodeGroups[profileNodeGroupIndex]; + var profileNodes = profileNodeGroups[++profileNodeGroupIndex]; + var count = profileNodes.length; + + for (var index = 0; index < count; ++index) { + var profileNode = profileNodes[index]; + + if (!profileNode.UID) + profileNode.UID = ++profileNodeUIDs; + + if (profileNode.head && profileNode !== profileNode.head) { + // The total time of this ancestor is accounted for if we're in any form of recursive cycle. + var visitedNodes = visitedProfileNodesForCallUID[profileNode.callUID]; + var totalTimeAccountedFor = false; + + if (!visitedNodes) { + visitedNodes = {} + visitedProfileNodesForCallUID[profileNode.callUID] = visitedNodes; + } else { + // The total time for this node has already been accounted for iff one of it's parents has already been visited. + // We can do this check in this style because we are traversing the tree in pre-order. + var parentCount = parentProfileNodes.length; + for (var parentIndex = 0; parentIndex < parentCount; ++parentIndex) { + if (visitedNodes[parentProfileNodes[parentIndex].UID]) { + totalTimeAccountedFor = true; + break; + } + } + } + + visitedNodes[profileNode.UID] = true; + + this._remainingNodeInfos.push({ ancestor:profileNode, focusNode:profileNode, totalTimeAccountedFor:totalTimeAccountedFor }); + } + + var children = profileNode.children; + if (children.length) { + profileNodeGroups.push(parentProfileNodes.concat([profileNode])) + profileNodeGroups.push(children); + } + } + } + + // Populate the top level nodes. + WebInspector.BottomUpProfileDataGridNode.prototype._populate.call(this); + + return this; +} + +WebInspector.BottomUpProfileDataGridTree.prototype = { + // When focusing, we keep the entire callstack up to this ancestor. + focus: function(/*ProfileDataGridNode*/ profileDataGridNode) + { + if (!profileDataGridNode) + return; + + this._save(); + + var currentNode = profileDataGridNode; + var focusNode = profileDataGridNode; + + while (currentNode.parent && (currentNode instanceof WebInspector.ProfileDataGridNode)) { + currentNode._takePropertiesFromProfileDataGridNode(profileDataGridNode); + + focusNode = currentNode; + currentNode = currentNode.parent; + + if (currentNode instanceof WebInspector.ProfileDataGridNode) + currentNode._keepOnlyChild(focusNode); + } + + this.children = [focusNode]; + this.totalTime = profileDataGridNode.totalTime; + }, + + exclude: function(/*ProfileDataGridNode*/ profileDataGridNode) + { + if (!profileDataGridNode) + return; + + this._save(); + + var excludedCallUID = profileDataGridNode.callUID; + var excludedTopLevelChild = this.childrenByCallUID[excludedCallUID]; + + // If we have a top level node that is excluded, get rid of it completely (not keeping children), + // since bottom up data relies entirely on the root node. + if (excludedTopLevelChild) + this.children.remove(excludedTopLevelChild); + + var children = this.children; + var count = children.length; + + for (var index = 0; index < count; ++index) + children[index]._exclude(excludedCallUID); + + if (this.lastComparator) + this.sort(this.lastComparator, true); + } +} + +WebInspector.BottomUpProfileDataGridTree.prototype.__proto__ = WebInspector.ProfileDataGridTree.prototype; + +WebInspector.BottomUpProfileDataGridNode = function(/*ProfileView*/ profileView, /*ProfileNode*/ profileNode, /*BottomUpProfileDataGridTree*/ owningTree) +{ + // In bottom up mode, our parents are our children since we display an inverted tree. + // However, we don't want to show the very top parent since it is redundant. + var hasChildren = !!(profileNode.parent && profileNode.parent.parent); + + WebInspector.ProfileDataGridNode.call(this, profileView, profileNode, owningTree, hasChildren); + + this._remainingNodeInfos = []; +} + +WebInspector.BottomUpProfileDataGridNode.prototype = { + _takePropertiesFromProfileDataGridNode: function(/*ProfileDataGridNode*/ profileDataGridNode) + { + this._save(); + + this.selfTime = profileDataGridNode.selfTime; + this.totalTime = profileDataGridNode.totalTime; + this.numberOfCalls = profileDataGridNode.numberOfCalls; + }, + + // When focusing, we keep just the members of the callstack. + _keepOnlyChild: function(/*ProfileDataGridNode*/ child) + { + this._save(); + + this.removeChildren(); + this.appendChild(child); + }, + + _exclude: function(aCallUID) + { + if (this._remainingNodeInfos) + this._populate(); + + this._save(); + + var children = this.children; + var index = this.children.length; + + while (index--) + children[index]._exclude(aCallUID); + + var child = this.childrenByCallUID[aCallUID]; + + if (child) + this._merge(child, true); + }, + + _merge: function(/*ProfileDataGridNode*/ child, /*Boolean*/ shouldAbsorb) + { + this.selfTime -= child.selfTime; + + WebInspector.ProfileDataGridNode.prototype._merge.call(this, child, shouldAbsorb); + }, + + _populate: function(event) + { + var remainingNodeInfos = this._remainingNodeInfos; + var count = remainingNodeInfos.length; + + for (var index = 0; index < count; ++index) { + var nodeInfo = remainingNodeInfos[index]; + var ancestor = nodeInfo.ancestor; + var focusNode = nodeInfo.focusNode; + var child = this.findChild(ancestor); + + // If we already have this child, then merge the data together. + if (child) { + var totalTimeAccountedFor = nodeInfo.totalTimeAccountedFor; + + child.selfTime += focusNode.selfTime; + child.numberOfCalls += focusNode.numberOfCalls; + + if (!totalTimeAccountedFor) + child.totalTime += focusNode.totalTime; + } else { + // If not, add it as a true ancestor. + // In heavy mode, we take our visual identity from ancestor node... + var child = new WebInspector.BottomUpProfileDataGridNode(this.profileView, ancestor, this.tree); + + if (ancestor !== focusNode) { + // but the actual statistics from the "root" node (bottom of the callstack). + child.selfTime = focusNode.selfTime; + child.totalTime = focusNode.totalTime; + child.numberOfCalls = focusNode.numberOfCalls; + } + + this.appendChild(child); + } + + var parent = ancestor.parent; + if (parent && parent.parent) { + nodeInfo.ancestor = parent; + child._remainingNodeInfos.push(nodeInfo); + } + } + + delete this._remainingNodeInfos; + + if (this.removeEventListener) + this.removeEventListener("populate", this._populate, this); + } +} + +WebInspector.BottomUpProfileDataGridNode.prototype.__proto__ = WebInspector.ProfileDataGridNode.prototype; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Breakpoint.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Breakpoint.js new file mode 100644 index 0000000..292975a --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Breakpoint.js @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.Breakpoint = function(url, line, sourceID, condition) +{ + this.url = url; + this.line = line; + this.sourceID = sourceID; + this._enabled = true; + this._sourceText = ""; + this._condition = condition || ""; +} + +WebInspector.Breakpoint.prototype = { + get enabled() + { + return this._enabled; + }, + + set enabled(x) + { + if (this._enabled === x) + return; + + this._enabled = x; + + if (this._enabled) + this.dispatchEventToListeners("enabled"); + else + this.dispatchEventToListeners("disabled"); + }, + + get sourceText() + { + return this._sourceText; + }, + + set sourceText(text) + { + this._sourceText = text; + this.dispatchEventToListeners("text-changed"); + }, + + get label() + { + var displayName = (this.url ? WebInspector.displayNameForURL(this.url) : WebInspector.UIString("(program)")); + return displayName + ":" + this.line; + }, + + get id() + { + return this.sourceID + ":" + this.line; + }, + + get condition() + { + return this._condition; + }, + + set condition(c) + { + c = c || ""; + if (this._condition === c) + return; + + this._condition = c; + this.dispatchEventToListeners("condition-changed"); + + if (this.enabled) + InspectorController.updateBreakpoint(this.sourceID, this.line, c); + } +} + +WebInspector.Breakpoint.prototype.__proto__ = WebInspector.Object.prototype; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/BreakpointsSidebarPane.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/BreakpointsSidebarPane.js new file mode 100644 index 0000000..e6edece --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/BreakpointsSidebarPane.js @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.BreakpointsSidebarPane = function() +{ + WebInspector.SidebarPane.call(this, WebInspector.UIString("Breakpoints")); + + this.breakpoints = {}; + + this.listElement = document.createElement("ol"); + this.listElement.className = "breakpoint-list"; + + this.emptyElement = document.createElement("div"); + this.emptyElement.className = "info"; + this.emptyElement.textContent = WebInspector.UIString("No Breakpoints"); + + this.bodyElement.appendChild(this.emptyElement); +} + +WebInspector.BreakpointsSidebarPane.prototype = { + addBreakpoint: function(breakpoint) + { + if (this.breakpoints[breakpoint.id]) + return; + + this.breakpoints[breakpoint.id] = breakpoint; + + breakpoint.addEventListener("enabled", this._breakpointEnableChanged, this); + breakpoint.addEventListener("disabled", this._breakpointEnableChanged, this); + breakpoint.addEventListener("text-changed", this._breakpointTextChanged, this); + + this._appendBreakpointElement(breakpoint); + + if (this.emptyElement.parentElement) { + this.bodyElement.removeChild(this.emptyElement); + this.bodyElement.appendChild(this.listElement); + } + + if (!InspectorController.debuggerEnabled() || !breakpoint.sourceID) + return; + + if (breakpoint.enabled) + InspectorController.addBreakpoint(breakpoint.sourceID, breakpoint.line, breakpoint.condition); + }, + + _appendBreakpointElement: function(breakpoint) + { + function checkboxClicked() + { + breakpoint.enabled = !breakpoint.enabled; + } + + function labelClicked() + { + var script = WebInspector.panels.scripts.scriptOrResourceForID(breakpoint.sourceID); + if (script) + WebInspector.panels.scripts.showScript(script, breakpoint.line); + } + + var breakpointElement = document.createElement("li"); + breakpoint._breakpointListElement = breakpointElement; + breakpointElement._breakpointObject = breakpoint; + + var checkboxElement = document.createElement("input"); + checkboxElement.className = "checkbox-elem"; + checkboxElement.type = "checkbox"; + checkboxElement.checked = breakpoint.enabled; + checkboxElement.addEventListener("click", checkboxClicked, false); + breakpointElement.appendChild(checkboxElement); + + var labelElement = document.createElement("a"); + labelElement.textContent = breakpoint.label; + labelElement.addEventListener("click", labelClicked, false); + breakpointElement.appendChild(labelElement); + + var sourceTextElement = document.createElement("div"); + sourceTextElement.textContent = breakpoint.sourceText; + sourceTextElement.className = "source-text"; + breakpointElement.appendChild(sourceTextElement); + + var currentElement = this.listElement.firstChild; + while (currentElement) { + var currentBreak = currentElement._breakpointObject; + if (currentBreak.url > breakpoint.url) { + this.listElement.insertBefore(breakpointElement, currentElement); + return; + } else if (currentBreak.url == breakpoint.url && currentBreak.line > breakpoint.line) { + this.listElement.insertBefore(breakpointElement, currentElement); + return; + } + currentElement = currentElement.nextSibling; + } + this.listElement.appendChild(breakpointElement); + }, + + removeBreakpoint: function(breakpoint) + { + if (!this.breakpoints[breakpoint.id]) + return; + delete this.breakpoints[breakpoint.id]; + + breakpoint.removeEventListener("enabled", null, this); + breakpoint.removeEventListener("disabled", null, this); + breakpoint.removeEventListener("text-changed", null, this); + + var element = breakpoint._breakpointListElement; + element.parentElement.removeChild(element); + + if (!this.listElement.firstChild) { + this.bodyElement.removeChild(this.listElement); + this.bodyElement.appendChild(this.emptyElement); + } + + if (!InspectorController.debuggerEnabled() || !breakpoint.sourceID) + return; + + InspectorController.removeBreakpoint(breakpoint.sourceID, breakpoint.line); + }, + + _breakpointEnableChanged: function(event) + { + var breakpoint = event.target; + + var checkbox = breakpoint._breakpointListElement.firstChild; + checkbox.checked = breakpoint.enabled; + + if (!InspectorController.debuggerEnabled() || !breakpoint.sourceID) + return; + + if (breakpoint.enabled) + InspectorController.addBreakpoint(breakpoint.sourceID, breakpoint.line, breakpoint.condition); + else + InspectorController.removeBreakpoint(breakpoint.sourceID, breakpoint.line); + }, + + _breakpointTextChanged: function(event) + { + var breakpoint = event.target; + + var sourceTextElement = breakpoint._breakpointListElement.firstChild.nextSibling.nextSibling; + sourceTextElement.textContent = breakpoint.sourceText; + } +} + +WebInspector.BreakpointsSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/CallStackSidebarPane.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/CallStackSidebarPane.js new file mode 100644 index 0000000..2fe4315 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/CallStackSidebarPane.js @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.CallStackSidebarPane = function() +{ + WebInspector.SidebarPane.call(this, WebInspector.UIString("Call Stack")); + + this._shortcuts = {}; + + var shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.Period, + WebInspector.KeyboardShortcut.Modifiers.Ctrl); + this._shortcuts[shortcut] = this._selectNextCallFrameOnStack.bind(this); + + var shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.Comma, + WebInspector.KeyboardShortcut.Modifiers.Ctrl); + this._shortcuts[shortcut] = this._selectPreviousCallFrameOnStack.bind(this); +} + +WebInspector.CallStackSidebarPane.prototype = { + update: function(callFrames, sourceIDMap) + { + this.bodyElement.removeChildren(); + + this.placards = []; + delete this._selectedCallFrame; + + if (!callFrames) { + var infoElement = document.createElement("div"); + infoElement.className = "info"; + infoElement.textContent = WebInspector.UIString("Not Paused"); + this.bodyElement.appendChild(infoElement); + return; + } + + var title; + var subtitle; + var scriptOrResource; + + for (var i = 0; i < callFrames.length; ++i) { + var callFrame = callFrames[i]; + switch (callFrame.type) { + case "function": + title = callFrame.functionName || WebInspector.UIString("(anonymous function)"); + break; + case "program": + title = WebInspector.UIString("(program)"); + break; + } + + scriptOrResource = sourceIDMap[callFrame.sourceID]; + subtitle = WebInspector.displayNameForURL(scriptOrResource.sourceURL || scriptOrResource.url); + + if (callFrame.line > 0) { + if (subtitle) + subtitle += ":" + callFrame.line; + else + subtitle = WebInspector.UIString("line %d", callFrame.line); + } + + var placard = new WebInspector.Placard(title, subtitle); + placard.callFrame = callFrame; + + placard.element.addEventListener("click", this._placardSelected.bind(this), false); + + this.placards.push(placard); + this.bodyElement.appendChild(placard.element); + } + }, + + get selectedCallFrame() + { + return this._selectedCallFrame; + }, + + set selectedCallFrame(x) + { + if (this._selectedCallFrame === x) + return; + + this._selectedCallFrame = x; + + for (var i = 0; i < this.placards.length; ++i) { + var placard = this.placards[i]; + placard.selected = (placard.callFrame === this._selectedCallFrame); + } + + this.dispatchEventToListeners("call frame selected"); + }, + + handleKeyEvent: function(event) + { + var shortcut = WebInspector.KeyboardShortcut.makeKeyFromEvent(event); + var handler = this._shortcuts[shortcut]; + if (handler) { + handler(event); + event.preventDefault(); + event.handled = true; + } + }, + + _selectNextCallFrameOnStack: function() + { + var index = this._selectedCallFrameIndex(); + if (index == -1) + return; + this._selectedPlacardByIndex(index + 1); + }, + + _selectPreviousCallFrameOnStack: function() + { + var index = this._selectedCallFrameIndex(); + if (index == -1) + return; + this._selectedPlacardByIndex(index - 1); + }, + + _selectedPlacardByIndex: function(index) + { + if (index < 0 || index >= this.placards.length) + return; + var placard = this.placards[index]; + this.selectedCallFrame = placard.callFrame + }, + + _selectedCallFrameIndex: function() + { + if (!this._selectedCallFrame) + return -1; + for (var i = 0; i < this.placards.length; ++i) { + var placard = this.placards[i]; + if (placard.callFrame === this._selectedCallFrame) + return i; + } + return -1; + }, + + _placardSelected: function(event) + { + var placardElement = event.target.enclosingNodeOrSelfWithClass("placard"); + this.selectedCallFrame = placardElement.placard.callFrame; + } +} + +WebInspector.CallStackSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Callback.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Callback.js new file mode 100644 index 0000000..8ae7f95 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Callback.js @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2009 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.Callback = function() +{ + this._lastCallbackId = 1; + this._callbacks = {}; +} + +WebInspector.Callback.prototype = { + wrap: function(callback) + { + var callbackId = this._lastCallbackId++; + this._callbacks[callbackId] = callback || function() {}; + return callbackId; + }, + + processCallback: function(callbackId, opt_vararg) + { + var args = Array.prototype.slice.call(arguments, 1); + var callback = this._callbacks[callbackId]; + callback.apply(null, args); + delete this._callbacks[callbackId]; + } +} + +WebInspector.Callback._INSTANCE = new WebInspector.Callback(); +WebInspector.Callback.wrap = WebInspector.Callback._INSTANCE.wrap.bind(WebInspector.Callback._INSTANCE); +WebInspector.Callback.processCallback = WebInspector.Callback._INSTANCE.processCallback.bind(WebInspector.Callback._INSTANCE); diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ChangesView.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ChangesView.js new file mode 100644 index 0000000..802fdba --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ChangesView.js @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2009 Joseph Pecoraro + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.ChangesView = function(drawer) +{ + WebInspector.View.call(this); + this.element.innerHTML = "<div style=\"bottom:25%;color:rgb(192,192,192);font-size:12px;height:65px;left:0px;margin:auto;position:absolute;right:0px;text-align:center;top:0px;\"><h1>Not Implemented Yet</h1></div>"; + + this.drawer = drawer; + + this.clearButton = document.createElement("button"); + this.clearButton.id = "clear-changes-status-bar-item"; + this.clearButton.title = WebInspector.UIString("Clear changes log."); + this.clearButton.className = "status-bar-item"; + this.clearButton.addEventListener("click", this._clearButtonClicked.bind(this), false); + + this.toggleChangesButton = document.getElementById("changes-status-bar-item"); + this.toggleChangesButton.title = WebInspector.UIString("Show changes view."); + this.toggleChangesButton.addEventListener("click", this._toggleChangesButtonClicked.bind(this), false); + var anchoredStatusBar = document.getElementById("anchored-status-bar-items"); + anchoredStatusBar.appendChild(this.toggleChangesButton); +} + +WebInspector.ChangesView.prototype = { + _clearButtonClicked: function() + { + // Not Implemented Yet + }, + + _toggleChangesButtonClicked: function() + { + this.drawer.visibleView = this; + }, + + attach: function(mainElement, statusBarElement) + { + mainElement.appendChild(this.element); + statusBarElement.appendChild(this.clearButton); + }, + + show: function() + { + this.toggleChangesButton.addStyleClass("toggled-on"); + this.toggleChangesButton.title = WebInspector.UIString("Hide changes view."); + }, + + hide: function() + { + this.toggleChangesButton.removeStyleClass("toggled-on"); + this.toggleChangesButton.title = WebInspector.UIString("Show changes view."); + } +} + +WebInspector.ChangesView.prototype.__proto__ = WebInspector.View.prototype; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Color.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Color.js new file mode 100644 index 0000000..9d9cd76 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Color.js @@ -0,0 +1,661 @@ +/* + * Copyright (C) 2009 Apple Inc. All rights reserved. + * Copyright (C) 2009 Joseph Pecoraro + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.Color = function(str) +{ + this.value = str; + this._parse(); +} + +WebInspector.Color.prototype = { + get shorthex() + { + if ("_short" in this) + return this._short; + + if (!this.simple) + return null; + + var hex = this.hex; + if (hex.charAt(0) === hex.charAt(1) && hex.charAt(2) === hex.charAt(3) && hex.charAt(4) === hex.charAt(5)) + this._short = hex.charAt(0) + hex.charAt(2) + hex.charAt(4); + else + this._short = hex; + + return this._short; + }, + + get hex() + { + if (!this.simple) + return null; + + return this._hex; + }, + + set hex(x) + { + this._hex = x; + }, + + get rgb() + { + if ("_rgb" in this) + return this._rgb; + + if (this.simple) + this._rgb = this._hexToRGB(this.hex); + else { + var rgba = this.rgba; + this._rgb = [rgba[0], rgba[1], rgba[2]]; + } + + return this._rgb; + }, + + set rgb(x) + { + this._rgb = x; + }, + + get hsl() + { + if ("_hsl" in this) + return this._hsl; + + this._hsl = this._rgbToHSL(this.rgb); + return this._hsl; + }, + + set hsl(x) + { + this._hsl = x; + }, + + get nickname() + { + if (typeof this._nickname !== "undefined") // would be set on parse if there was a nickname + return this._nickname; + else + return null; + }, + + set nickname(x) + { + this._nickname = x; + }, + + get rgba() + { + return this._rgba; + }, + + set rgba(x) + { + this._rgba = x; + }, + + get hsla() + { + return this._hsla; + }, + + set hsla(x) + { + this._hsla = x; + }, + + hasShortHex: function() + { + var shorthex = this.shorthex; + return (shorthex && shorthex.length === 3); + }, + + toString: function(format) + { + if (!format) + format = this.format; + + switch (format) { + case "rgb": + return "rgb(" + this.rgb.join(", ") + ")"; + case "rgba": + return "rgba(" + this.rgba.join(", ") + ")"; + case "hsl": + var hsl = this.hsl; + return "hsl(" + hsl[0] + ", " + hsl[1] + "%, " + hsl[2] + "%)"; + case "hsla": + var hsla = this.hsla; + return "hsla(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%, " + hsla[3] + ")"; + case "hex": + return "#" + this.hex; + case "shorthex": + return "#" + this.shorthex; + case "nickname": + return this.nickname; + } + + throw "invalid color format"; + }, + + _rgbToHex: function(rgb) + { + var r = parseInt(rgb[0]).toString(16); + var g = parseInt(rgb[1]).toString(16); + var b = parseInt(rgb[2]).toString(16); + if (r.length === 1) + r = "0" + r; + if (g.length === 1) + g = "0" + g; + if (b.length === 1) + b = "0" + b; + + return (r + g + b).toUpperCase(); + }, + + _hexToRGB: function(hex) + { + var r = parseInt(hex.substring(0,2), 16); + var g = parseInt(hex.substring(2,4), 16); + var b = parseInt(hex.substring(4,6), 16); + + return [r, g, b]; + }, + + _rgbToHSL: function(rgb) + { + var r = parseInt(rgb[0]) / 255; + var g = parseInt(rgb[1]) / 255; + var b = parseInt(rgb[2]) / 255; + var max = Math.max(r, g, b); + var min = Math.min(r, g, b); + var diff = max - min; + var add = max + min; + + if (min === max) + var h = 0; + else if (r === max) + var h = ((60 * (g - b) / diff) + 360) % 360; + else if (g === max) + var h = (60 * (b - r) / diff) + 120; + else + var h = (60 * (r - g) / diff) + 240; + + var l = 0.5 * add; + + if (l === 0) + var s = 0; + else if (l === 1) + var s = 1; + else if (l <= 0.5) + var s = diff / add; + else + var s = diff / (2 - add); + + h = Math.round(h); + s = Math.round(s*100); + l = Math.round(l*100); + + return [h, s, l]; + }, + + _hslToRGB: function(hsl) + { + var h = parseFloat(hsl[0]) / 360; + var s = parseFloat(hsl[1]) / 100; + var l = parseFloat(hsl[2]) / 100; + + if (l <= 0.5) + var q = l * (1 + s); + else + var q = l + s - (l * s); + + var p = 2 * l - q; + + var tr = h + (1 / 3); + var tg = h; + var tb = h - (1 / 3); + + var r = Math.round(hueToRGB(p, q, tr) * 255); + var g = Math.round(hueToRGB(p, q, tg) * 255); + var b = Math.round(hueToRGB(p, q, tb) * 255); + return [r, g, b]; + + function hueToRGB(p, q, h) { + if (h < 0) + h += 1; + else if (h > 1) + h -= 1; + + if ((h * 6) < 1) + return p + (q - p) * h * 6; + else if ((h * 2) < 1) + return q; + else if ((h * 3) < 2) + return p + (q - p) * ((2 / 3) - h) * 6; + else + return p; + } + }, + + _rgbaToHSLA: function(rgba) + { + var alpha = rgba[3]; + var hsl = this._rgbToHSL(rgba) + hsl.push(alpha); + return hsl; + }, + + _hslaToRGBA: function(hsla) + { + var alpha = hsla[3]; + var rgb = this._hslToRGB(hsla); + rgb.push(alpha); + return rgb; + }, + + _parse: function() + { + // Special Values - Advanced but Must Be Parsed First - transparent + var value = this.value.toLowerCase().replace(/%|\s+/g, ""); + if (value in WebInspector.Color.AdvancedNickNames) { + this.format = "nickname"; + var set = WebInspector.Color.AdvancedNickNames[value]; + this.simple = false; + this.rgba = set[0]; + this.hsla = set[1]; + this.nickname = set[2]; + this.alpha = set[0][3]; + return; + } + + // Simple - #hex, rgb(), nickname, hsl() + var simple = /^(?:#([0-9a-f]{3,6})|rgb\(([^)]+)\)|(\w+)|hsl\(([^)]+)\))$/i; + var match = this.value.match(simple); + if (match) { + this.simple = true; + + if (match[1]) { // hex + var hex = match[1].toUpperCase(); + if (hex.length === 3) { + this.format = "shorthex"; + this.hex = hex.charAt(0) + hex.charAt(0) + hex.charAt(1) + hex.charAt(1) + hex.charAt(2) + hex.charAt(2); + } else { + this.format = "hex"; + this.hex = hex; + } + } else if (match[2]) { // rgb + this.format = "rgb"; + var rgb = match[2].split(/\s*,\s*/); + this.rgb = rgb; + this.hex = this._rgbToHex(rgb); + } else if (match[3]) { // nickname + var nickname = match[3].toLowerCase(); + if (nickname in WebInspector.Color.Nicknames) { + this.format = "nickname"; + this.hex = WebInspector.Color.Nicknames[nickname]; + } else // unknown name + throw "unknown color name"; + } else if (match[4]) { // hsl + this.format = "hsl"; + var hsl = match[4].replace(/%g/, "").split(/\s*,\s*/); + this.hsl = hsl; + this.rgb = this._hslToRGB(hsl); + this.hex = this._rgbToHex(this.rgb); + } + + // Fill in the values if this is a known hex color + var hex = this.hex; + if (hex && hex in WebInspector.Color.HexTable) { + var set = WebInspector.Color.HexTable[hex]; + this.rgb = set[0]; + this.hsl = set[1]; + this.nickname = set[2]; + } + + return; + } + + // Advanced - rgba(), hsla() + var advanced = /^(?:rgba\(([^)]+)\)|hsla\(([^)]+)\))$/; + match = this.value.match(advanced); + if (match) { + this.simple = false; + if (match[1]) { // rgba + this.format = "rgba"; + this.rgba = match[1].split(/\s*,\s*/); + this.hsla = this._rgbaToHSLA(this.rgba); + this.alpha = this.rgba[3]; + } else if (match[2]) { // hsla + this.format = "hsla"; + this.hsla = match[2].replace(/%/g, "").split(/\s*,\s*/); + this.rgba = this._hslaToRGBA(this.hsla); + this.alpha = this.hsla[3]; + } + + return; + } + + // Could not parse as a valid color + throw "could not parse color"; + } +} + +// Simple Values: [rgb, hsl, nickname] +WebInspector.Color.HexTable = { + "000000": [[0, 0, 0], [0, 0, 0], "black"], + "000080": [[0, 0, 128], [240, 100, 25], "navy"], + "00008B": [[0, 0, 139], [240, 100, 27], "darkBlue"], + "0000CD": [[0, 0, 205], [240, 100, 40], "mediumBlue"], + "0000FF": [[0, 0, 255], [240, 100, 50], "blue"], + "006400": [[0, 100, 0], [120, 100, 20], "darkGreen"], + "008000": [[0, 128, 0], [120, 100, 25], "green"], + "008080": [[0, 128, 128], [180, 100, 25], "teal"], + "008B8B": [[0, 139, 139], [180, 100, 27], "darkCyan"], + "00BFFF": [[0, 191, 255], [195, 100, 50], "deepSkyBlue"], + "00CED1": [[0, 206, 209], [181, 100, 41], "darkTurquoise"], + "00FA9A": [[0, 250, 154], [157, 100, 49], "mediumSpringGreen"], + "00FF00": [[0, 255, 0], [120, 100, 50], "lime"], + "00FF7F": [[0, 255, 127], [150, 100, 50], "springGreen"], + "00FFFF": [[0, 255, 255], [180, 100, 50], "cyan"], + "191970": [[25, 25, 112], [240, 64, 27], "midnightBlue"], + "1E90FF": [[30, 144, 255], [210, 100, 56], "dodgerBlue"], + "20B2AA": [[32, 178, 170], [177, 70, 41], "lightSeaGreen"], + "228B22": [[34, 139, 34], [120, 61, 34], "forestGreen"], + "2E8B57": [[46, 139, 87], [146, 50, 36], "seaGreen"], + "2F4F4F": [[47, 79, 79], [180, 25, 25], "darkSlateGray"], + "32CD32": [[50, 205, 50], [120, 61, 50], "limeGreen"], + "3CB371": [[60, 179, 113], [147, 50, 47], "mediumSeaGreen"], + "40E0D0": [[64, 224, 208], [174, 72, 56], "turquoise"], + "4169E1": [[65, 105, 225], [225, 73, 57], "royalBlue"], + "4682B4": [[70, 130, 180], [207, 44, 49], "steelBlue"], + "483D8B": [[72, 61, 139], [248, 39, 39], "darkSlateBlue"], + "48D1CC": [[72, 209, 204], [178, 60, 55], "mediumTurquoise"], + "4B0082": [[75, 0, 130], [275, 100, 25], "indigo"], + "556B2F": [[85, 107, 47], [82, 39, 30], "darkOliveGreen"], + "5F9EA0": [[95, 158, 160], [182, 25, 50], "cadetBlue"], + "6495ED": [[100, 149, 237], [219, 79, 66], "cornflowerBlue"], + "66CDAA": [[102, 205, 170], [160, 51, 60], "mediumAquaMarine"], + "696969": [[105, 105, 105], [0, 0, 41], "dimGray"], + "6A5ACD": [[106, 90, 205], [248, 53, 58], "slateBlue"], + "6B8E23": [[107, 142, 35], [80, 60, 35], "oliveDrab"], + "708090": [[112, 128, 144], [210, 13, 50], "slateGray"], + "778899": [[119, 136, 153], [210, 14, 53], "lightSlateGray"], + "7B68EE": [[123, 104, 238], [249, 80, 67], "mediumSlateBlue"], + "7CFC00": [[124, 252, 0], [90, 100, 49], "lawnGreen"], + "7FFF00": [[127, 255, 0], [90, 100, 50], "chartreuse"], + "7FFFD4": [[127, 255, 212], [160, 100, 75], "aquamarine"], + "800000": [[128, 0, 0], [0, 100, 25], "maroon"], + "800080": [[128, 0, 128], [300, 100, 25], "purple"], + "808000": [[128, 128, 0], [60, 100, 25], "olive"], + "808080": [[128, 128, 128], [0, 0, 50], "gray"], + "87CEEB": [[135, 206, 235], [197, 71, 73], "skyBlue"], + "87CEFA": [[135, 206, 250], [203, 92, 75], "lightSkyBlue"], + "8A2BE2": [[138, 43, 226], [271, 76, 53], "blueViolet"], + "8B0000": [[139, 0, 0], [0, 100, 27], "darkRed"], + "8B008B": [[139, 0, 139], [300, 100, 27], "darkMagenta"], + "8B4513": [[139, 69, 19], [25, 76, 31], "saddleBrown"], + "8FBC8F": [[143, 188, 143], [120, 25, 65], "darkSeaGreen"], + "90EE90": [[144, 238, 144], [120, 73, 75], "lightGreen"], + "9370D8": [[147, 112, 219], [260, 60, 65], "mediumPurple"], + "9400D3": [[148, 0, 211], [282, 100, 41], "darkViolet"], + "98FB98": [[152, 251, 152], [120, 93, 79], "paleGreen"], + "9932CC": [[153, 50, 204], [280, 61, 50], "darkOrchid"], + "9ACD32": [[154, 205, 50], [80, 61, 50], "yellowGreen"], + "A0522D": [[160, 82, 45], [19, 56, 40], "sienna"], + "A52A2A": [[165, 42, 42], [0, 59, 41], "brown"], + "A9A9A9": [[169, 169, 169], [0, 0, 66], "darkGray"], + "ADD8E6": [[173, 216, 230], [195, 53, 79], "lightBlue"], + "ADFF2F": [[173, 255, 47], [84, 100, 59], "greenYellow"], + "AFEEEE": [[175, 238, 238], [180, 65, 81], "paleTurquoise"], + "B0C4DE": [[176, 196, 222], [214, 41, 78], "lightSteelBlue"], + "B0E0E6": [[176, 224, 230], [187, 52, 80], "powderBlue"], + "B22222": [[178, 34, 34], [0, 68, 42], "fireBrick"], + "B8860B": [[184, 134, 11], [43, 89, 38], "darkGoldenRod"], + "BA55D3": [[186, 85, 211], [288, 59, 58], "mediumOrchid"], + "BC8F8F": [[188, 143, 143], [0, 25, 65], "rosyBrown"], + "BDB76B": [[189, 183, 107], [56, 38, 58], "darkKhaki"], + "C0C0C0": [[192, 192, 192], [0, 0, 75], "silver"], + "C71585": [[199, 21, 133], [322, 81, 43], "mediumVioletRed"], + "CD5C5C": [[205, 92, 92], [0, 53, 58], "indianRed"], + "CD853F": [[205, 133, 63], [30, 59, 53], "peru"], + "D2691E": [[210, 105, 30], [25, 75, 47], "chocolate"], + "D2B48C": [[210, 180, 140], [34, 44, 69], "tan"], + "D3D3D3": [[211, 211, 211], [0, 0, 83], "lightGrey"], + "D87093": [[219, 112, 147], [340, 60, 65], "paleVioletRed"], + "D8BFD8": [[216, 191, 216], [300, 24, 80], "thistle"], + "DA70D6": [[218, 112, 214], [302, 59, 65], "orchid"], + "DAA520": [[218, 165, 32], [43, 74, 49], "goldenRod"], + "DC143C": [[237, 164, 61], [35, 83, 58], "crimson"], + "DCDCDC": [[220, 220, 220], [0, 0, 86], "gainsboro"], + "DDA0DD": [[221, 160, 221], [300, 47, 75], "plum"], + "DEB887": [[222, 184, 135], [34, 57, 70], "burlyWood"], + "E0FFFF": [[224, 255, 255], [180, 100, 94], "lightCyan"], + "E6E6FA": [[230, 230, 250], [240, 67, 94], "lavender"], + "E9967A": [[233, 150, 122], [15, 72, 70], "darkSalmon"], + "EE82EE": [[238, 130, 238], [300, 76, 72], "violet"], + "EEE8AA": [[238, 232, 170], [55, 67, 80], "paleGoldenRod"], + "F08080": [[240, 128, 128], [0, 79, 72], "lightCoral"], + "F0E68C": [[240, 230, 140], [54, 77, 75], "khaki"], + "F0F8FF": [[240, 248, 255], [208, 100, 97], "aliceBlue"], + "F0FFF0": [[240, 255, 240], [120, 100, 97], "honeyDew"], + "F0FFFF": [[240, 255, 255], [180, 100, 97], "azure"], + "F4A460": [[244, 164, 96], [28, 87, 67], "sandyBrown"], + "F5DEB3": [[245, 222, 179], [39, 77, 83], "wheat"], + "F5F5DC": [[245, 245, 220], [60, 56, 91], "beige"], + "F5F5F5": [[245, 245, 245], [0, 0, 96], "whiteSmoke"], + "F5FFFA": [[245, 255, 250], [150, 100, 98], "mintCream"], + "F8F8FF": [[248, 248, 255], [240, 100, 99], "ghostWhite"], + "FA8072": [[250, 128, 114], [6, 93, 71], "salmon"], + "FAEBD7": [[250, 235, 215], [34, 78, 91], "antiqueWhite"], + "FAF0E6": [[250, 240, 230], [30, 67, 94], "linen"], + "FAFAD2": [[250, 250, 210], [60, 80, 90], "lightGoldenRodYellow"], + "FDF5E6": [[253, 245, 230], [39, 85, 95], "oldLace"], + "FF0000": [[255, 0, 0], [0, 100, 50], "red"], + "FF00FF": [[255, 0, 255], [300, 100, 50], "magenta"], + "FF1493": [[255, 20, 147], [328, 100, 54], "deepPink"], + "FF4500": [[255, 69, 0], [16, 100, 50], "orangeRed"], + "FF6347": [[255, 99, 71], [9, 100, 64], "tomato"], + "FF69B4": [[255, 105, 180], [330, 100, 71], "hotPink"], + "FF7F50": [[255, 127, 80], [16, 100, 66], "coral"], + "FF8C00": [[255, 140, 0], [33, 100, 50], "darkOrange"], + "FFA07A": [[255, 160, 122], [17, 100, 74], "lightSalmon"], + "FFA500": [[255, 165, 0], [39, 100, 50], "orange"], + "FFB6C1": [[255, 182, 193], [351, 100, 86], "lightPink"], + "FFC0CB": [[255, 192, 203], [350, 100, 88], "pink"], + "FFD700": [[255, 215, 0], [51, 100, 50], "gold"], + "FFDAB9": [[255, 218, 185], [28, 100, 86], "peachPuff"], + "FFDEAD": [[255, 222, 173], [36, 100, 84], "navajoWhite"], + "FFE4B5": [[255, 228, 181], [38, 100, 85], "moccasin"], + "FFE4C4": [[255, 228, 196], [33, 100, 88], "bisque"], + "FFE4E1": [[255, 228, 225], [6, 100, 94], "mistyRose"], + "FFEBCD": [[255, 235, 205], [36, 100, 90], "blanchedAlmond"], + "FFEFD5": [[255, 239, 213], [37, 100, 92], "papayaWhip"], + "FFF0F5": [[255, 240, 245], [340, 100, 97], "lavenderBlush"], + "FFF5EE": [[255, 245, 238], [25, 100, 97], "seaShell"], + "FFF8DC": [[255, 248, 220], [48, 100, 93], "cornsilk"], + "FFFACD": [[255, 250, 205], [54, 100, 90], "lemonChiffon"], + "FFFAF0": [[255, 250, 240], [40, 100, 97], "floralWhite"], + "FFFAFA": [[255, 250, 250], [0, 100, 99], "snow"], + "FFFF00": [[255, 255, 0], [60, 100, 50], "yellow"], + "FFFFE0": [[255, 255, 224], [60, 100, 94], "lightYellow"], + "FFFFF0": [[255, 255, 240], [60, 100, 97], "ivory"], + "FFFFFF": [[255, 255, 255], [0, 100, 100], "white"] +}; + +// Simple Values +WebInspector.Color.Nicknames = { + "aliceblue": "F0F8FF", + "antiquewhite": "FAEBD7", + "aqua": "00FFFF", + "aquamarine": "7FFFD4", + "azure": "F0FFFF", + "beige": "F5F5DC", + "bisque": "FFE4C4", + "black": "000000", + "blanchedalmond": "FFEBCD", + "blue": "0000FF", + "blueviolet": "8A2BE2", + "brown": "A52A2A", + "burlywood": "DEB887", + "cadetblue": "5F9EA0", + "chartreuse": "7FFF00", + "chocolate": "D2691E", + "coral": "FF7F50", + "cornflowerblue": "6495ED", + "cornsilk": "FFF8DC", + "crimson": "DC143C", + "cyan": "00FFFF", + "darkblue": "00008B", + "darkcyan": "008B8B", + "darkgoldenrod": "B8860B", + "darkgray": "A9A9A9", + "darkgreen": "006400", + "darkkhaki": "BDB76B", + "darkmagenta": "8B008B", + "darkolivegreen": "556B2F", + "darkorange": "FF8C00", + "darkorchid": "9932CC", + "darkred": "8B0000", + "darksalmon": "E9967A", + "darkseagreen": "8FBC8F", + "darkslateblue": "483D8B", + "darkslategray": "2F4F4F", + "darkturquoise": "00CED1", + "darkviolet": "9400D3", + "deeppink": "FF1493", + "deepskyblue": "00BFFF", + "dimgray": "696969", + "dodgerblue": "1E90FF", + "firebrick": "B22222", + "floralwhite": "FFFAF0", + "forestgreen": "228B22", + "fuchsia": "FF00FF", + "gainsboro": "DCDCDC", + "ghostwhite": "F8F8FF", + "gold": "FFD700", + "goldenrod": "DAA520", + "gray": "808080", + "green": "008000", + "greenyellow": "ADFF2F", + "honeydew": "F0FFF0", + "hotpink": "FF69B4", + "indianred": "CD5C5C", + "indigo": "4B0082", + "ivory": "FFFFF0", + "khaki": "F0E68C", + "lavender": "E6E6FA", + "lavenderblush": "FFF0F5", + "lawngreen": "7CFC00", + "lemonchiffon": "FFFACD", + "lightblue": "ADD8E6", + "lightcoral": "F08080", + "lightcyan": "E0FFFF", + "lightgoldenrodyellow": "FAFAD2", + "lightgreen": "90EE90", + "lightgrey": "D3D3D3", + "lightpink": "FFB6C1", + "lightsalmon": "FFA07A", + "lightseagreen": "20B2AA", + "lightskyblue": "87CEFA", + "lightslategray": "778899", + "lightsteelblue": "B0C4DE", + "lightyellow": "FFFFE0", + "lime": "00FF00", + "limegreen": "32CD32", + "linen": "FAF0E6", + "magenta": "FF00FF", + "maroon": "800000", + "mediumaquamarine": "66CDAA", + "mediumblue": "0000CD", + "mediumorchid": "BA55D3", + "mediumpurple": "9370D8", + "mediumseagreen": "3CB371", + "mediumslateblue": "7B68EE", + "mediumspringgreen": "00FA9A", + "mediumturquoise": "48D1CC", + "mediumvioletred": "C71585", + "midnightblue": "191970", + "mintcream": "F5FFFA", + "mistyrose": "FFE4E1", + "moccasin": "FFE4B5", + "navajowhite": "FFDEAD", + "navy": "000080", + "oldlace": "FDF5E6", + "olive": "808000", + "olivedrab": "6B8E23", + "orange": "FFA500", + "orangered": "FF4500", + "orchid": "DA70D6", + "palegoldenrod": "EEE8AA", + "palegreen": "98FB98", + "paleturquoise": "AFEEEE", + "palevioletred": "D87093", + "papayawhip": "FFEFD5", + "peachpuff": "FFDAB9", + "peru": "CD853F", + "pink": "FFC0CB", + "plum": "DDA0DD", + "powderblue": "B0E0E6", + "purple": "800080", + "red": "FF0000", + "rosybrown": "BC8F8F", + "royalblue": "4169E1", + "saddlebrown": "8B4513", + "salmon": "FA8072", + "sandybrown": "F4A460", + "seagreen": "2E8B57", + "seashell": "FFF5EE", + "sienna": "A0522D", + "silver": "C0C0C0", + "skyblue": "87CEEB", + "slateblue": "6A5ACD", + "slategray": "708090", + "snow": "FFFAFA", + "springgreen": "00FF7F", + "steelblue": "4682B4", + "tan": "D2B48C", + "teal": "008080", + "thistle": "D8BFD8", + "tomato": "FF6347", + "turquoise": "40E0D0", + "violet": "EE82EE", + "wheat": "F5DEB3", + "white": "FFFFFF", + "whitesmoke": "F5F5F5", + "yellow": "FFFF00", + "yellowgreen": "9ACD32" +}; + +// Advanced Values [rgba, hsla, nickname] +WebInspector.Color.AdvancedNickNames = { + "transparent": [[0, 0, 0, 0], [0, 0, 0, 0], "transparent"], + "rgba(0,0,0,0)": [[0, 0, 0, 0], [0, 0, 0, 0], "transparent"], + "hsla(0,0,0,0)": [[0, 0, 0, 0], [0, 0, 0, 0], "transparent"], +}; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ConsoleView.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ConsoleView.js new file mode 100644 index 0000000..fa9a363 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ConsoleView.js @@ -0,0 +1,970 @@ +/* + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2009 Joseph Pecoraro + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.ConsoleView = function(drawer) +{ + WebInspector.View.call(this, document.getElementById("console-view")); + + this.messages = []; + this.drawer = drawer; + + this.clearButton = document.getElementById("clear-console-status-bar-item"); + this.clearButton.title = WebInspector.UIString("Clear console log."); + this.clearButton.addEventListener("click", this._clearButtonClicked.bind(this), false); + + this.messagesElement = document.getElementById("console-messages"); + this.messagesElement.addEventListener("selectstart", this._messagesSelectStart.bind(this), false); + this.messagesElement.addEventListener("click", this._messagesClicked.bind(this), true); + + this.promptElement = document.getElementById("console-prompt"); + this.promptElement.handleKeyEvent = this._promptKeyDown.bind(this); + this.prompt = new WebInspector.TextPrompt(this.promptElement, this.completions.bind(this), " .=:[({;"); + + this.topGroup = new WebInspector.ConsoleGroup(null, 0); + this.messagesElement.insertBefore(this.topGroup.element, this.promptElement); + this.groupLevel = 0; + this.currentGroup = this.topGroup; + + this.toggleConsoleButton = document.getElementById("console-status-bar-item"); + this.toggleConsoleButton.title = WebInspector.UIString("Show console."); + this.toggleConsoleButton.addEventListener("click", this._toggleConsoleButtonClicked.bind(this), false); + + var anchoredStatusBar = document.getElementById("anchored-status-bar-items"); + anchoredStatusBar.appendChild(this.toggleConsoleButton); + + // Will hold the list of filter elements + this.filterBarElement = document.getElementById("console-filter"); + + function createFilterElement(category) { + var categoryElement = document.createElement("li"); + categoryElement.category = category; + + categoryElement.addStyleClass(categoryElement.category); + + var label = category.toString(); + categoryElement.appendChild(document.createTextNode(label)); + + categoryElement.addEventListener("click", this._updateFilter.bind(this), false); + + this.filterBarElement.appendChild(categoryElement); + return categoryElement; + } + + this.allElement = createFilterElement.call(this, "All"); + this.errorElement = createFilterElement.call(this, "Errors"); + this.warningElement = createFilterElement.call(this, "Warnings"); + this.logElement = createFilterElement.call(this, "Logs"); + + this.filter(this.allElement); +} + +WebInspector.ConsoleView.prototype = { + + _updateFilter: function(e) + { + this.filter(e.target); + }, + + filter: function(target) + { + if (target.category == "All") { + if (target.hasStyleClass("selected")) { + // We can't unselect all, so we break early here + return; + } + + this.errorElement.removeStyleClass("selected"); + this.warningElement.removeStyleClass("selected"); + this.logElement.removeStyleClass("selected"); + + document.getElementById("console-messages").removeStyleClass("filter-errors"); + document.getElementById("console-messages").removeStyleClass("filter-warnings"); + document.getElementById("console-messages").removeStyleClass("filter-logs"); + } else { + // Something other than all is being selected, so we want to unselect all + if (this.allElement.hasStyleClass("selected")) { + this.allElement.removeStyleClass("selected"); + document.getElementById("console-messages").removeStyleClass("filter-all"); + } + } + + if (target.hasStyleClass("selected")) { + target.removeStyleClass("selected"); + var newClass = "filter-" + target.category.toLowerCase(); + var filterElement = document.getElementById("console-messages"); + filterElement.removeStyleClass(newClass); + } else { + target.addStyleClass("selected"); + var newClass = "filter-" + target.category.toLowerCase(); + var filterElement = document.getElementById("console-messages"); + filterElement.addStyleClass(newClass); + } + }, + + _toggleConsoleButtonClicked: function() + { + this.drawer.visibleView = this; + }, + + attach: function(mainElement, statusBarElement) + { + mainElement.appendChild(this.element); + statusBarElement.appendChild(this.clearButton); + statusBarElement.appendChild(this.filterBarElement); + }, + + show: function() + { + this.toggleConsoleButton.addStyleClass("toggled-on"); + this.toggleConsoleButton.title = WebInspector.UIString("Hide console."); + if (!this.prompt.isCaretInsidePrompt()) + this.prompt.moveCaretToEndOfPrompt(); + }, + + afterShow: function() + { + WebInspector.currentFocusElement = this.promptElement; + }, + + hide: function() + { + this.toggleConsoleButton.removeStyleClass("toggled-on"); + this.toggleConsoleButton.title = WebInspector.UIString("Show console."); + }, + + addMessage: function(msg) + { + if (msg instanceof WebInspector.ConsoleMessage && !(msg instanceof WebInspector.ConsoleCommandResult)) { + msg.totalRepeatCount = msg.repeatCount; + msg.repeatDelta = msg.repeatCount; + + var messageRepeated = false; + + if (msg.isEqual && msg.isEqual(this.previousMessage)) { + // Because sometimes we get a large number of repeated messages and sometimes + // we get them one at a time, we need to know the difference between how many + // repeats we used to have and how many we have now. + msg.repeatDelta -= this.previousMessage.totalRepeatCount; + + if (!isNaN(this.repeatCountBeforeCommand)) + msg.repeatCount -= this.repeatCountBeforeCommand; + + if (!this.commandSincePreviousMessage) { + // Recreate the previous message element to reset the repeat count. + var messagesElement = this.currentGroup.messagesElement; + messagesElement.removeChild(messagesElement.lastChild); + messagesElement.appendChild(msg.toMessageElement()); + + messageRepeated = true; + } + } else + delete this.repeatCountBeforeCommand; + + // Increment the error or warning count + switch (msg.level) { + case WebInspector.ConsoleMessage.MessageLevel.Warning: + WebInspector.warnings += msg.repeatDelta; + break; + case WebInspector.ConsoleMessage.MessageLevel.Error: + WebInspector.errors += msg.repeatDelta; + break; + } + + // Add message to the resource panel + if (msg.url in WebInspector.resourceURLMap) { + msg.resource = WebInspector.resourceURLMap[msg.url]; + if (WebInspector.panels.resources) + WebInspector.panels.resources.addMessageToResource(msg.resource, msg); + } + + this.commandSincePreviousMessage = false; + this.previousMessage = msg; + + if (messageRepeated) + return; + } else if (msg instanceof WebInspector.ConsoleCommand) { + if (this.previousMessage) { + this.commandSincePreviousMessage = true; + this.repeatCountBeforeCommand = this.previousMessage.totalRepeatCount; + } + } + + this.messages.push(msg); + + if (msg.type === WebInspector.ConsoleMessage.MessageType.EndGroup) { + if (this.groupLevel < 1) + return; + + this.groupLevel--; + + this.currentGroup = this.currentGroup.parentGroup; + } else { + if (msg.type === WebInspector.ConsoleMessage.MessageType.StartGroup) { + this.groupLevel++; + + var group = new WebInspector.ConsoleGroup(this.currentGroup, this.groupLevel); + this.currentGroup.messagesElement.appendChild(group.element); + this.currentGroup = group; + } + + this.currentGroup.addMessage(msg); + } + + this.promptElement.scrollIntoView(false); + }, + + clearMessages: function(clearInspectorController) + { + if (clearInspectorController) + InspectorController.clearMessages(false); + if (WebInspector.panels.resources) + WebInspector.panels.resources.clearMessages(); + + this.messages = []; + + this.groupLevel = 0; + this.currentGroup = this.topGroup; + this.topGroup.messagesElement.removeChildren(); + + WebInspector.errors = 0; + WebInspector.warnings = 0; + + delete this.commandSincePreviousMessage; + delete this.repeatCountBeforeCommand; + delete this.previousMessage; + }, + + completions: function(wordRange, bestMatchOnly, completionsReadyCallback) + { + // Pass less stop characters to rangeOfWord so the range will be a more complete expression. + const expressionStopCharacters = " =:{;"; + var expressionRange = wordRange.startContainer.rangeOfWord(wordRange.startOffset, expressionStopCharacters, this.promptElement, "backward"); + var expressionString = expressionRange.toString(); + var lastIndex = expressionString.length - 1; + + var dotNotation = (expressionString[lastIndex] === "."); + var bracketNotation = (expressionString[lastIndex] === "["); + + if (dotNotation || bracketNotation) + expressionString = expressionString.substr(0, lastIndex); + + var prefix = wordRange.toString(); + if (!expressionString && !prefix) + return; + + var reportCompletions = this._reportCompletions.bind(this, bestMatchOnly, completionsReadyCallback, dotNotation, bracketNotation, prefix); + // Collect comma separated object properties for the completion. + + if (!expressionString) { + if (WebInspector.panels.scripts && WebInspector.panels.scripts.paused) { + // Evaluate into properties in scope of the selected call frame. + reportCompletions(WebInspector.panels.scripts.variablesInSelectedCallFrame()); + return; + } else { + expressionString = "this"; + } + } + + var includeInspectorCommandLineAPI = (!dotNotation && !bracketNotation); + InjectedScriptAccess.getCompletions(expressionString, includeInspectorCommandLineAPI, reportCompletions); + }, + + _reportCompletions: function(bestMatchOnly, completionsReadyCallback, dotNotation, bracketNotation, prefix, result, isException) { + if (isException) + return; + + if (bracketNotation) { + if (prefix.length && prefix[0] === "'") + var quoteUsed = "'"; + else + var quoteUsed = "\""; + } + + var results = []; + var properties = Object.sortedProperties(result); + + for (var i = 0; i < properties.length; ++i) { + var property = properties[i]; + + if (dotNotation && !/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(property)) + continue; + + if (bracketNotation) { + if (!/^[0-9]+$/.test(property)) + property = quoteUsed + property.escapeCharacters(quoteUsed + "\\") + quoteUsed; + property += "]"; + } + + if (property.length < prefix.length) + continue; + if (property.indexOf(prefix) !== 0) + continue; + + results.push(property); + if (bestMatchOnly) + break; + } + setTimeout(completionsReadyCallback, 0, results); + }, + + _clearButtonClicked: function() + { + this.clearMessages(true); + }, + + _messagesSelectStart: function(event) + { + if (this._selectionTimeout) + clearTimeout(this._selectionTimeout); + + this.prompt.clearAutoComplete(); + + function moveBackIfOutside() + { + delete this._selectionTimeout; + if (!this.prompt.isCaretInsidePrompt() && window.getSelection().isCollapsed) + this.prompt.moveCaretToEndOfPrompt(); + this.prompt.autoCompleteSoon(); + } + + this._selectionTimeout = setTimeout(moveBackIfOutside.bind(this), 100); + }, + + _messagesClicked: function(event) + { + var link = event.target.enclosingNodeOrSelfWithNodeName("a"); + if (!link || !link.representedNode) + return; + + WebInspector.updateFocusedNode(link.representedNode.id); + event.stopPropagation(); + event.preventDefault(); + }, + + _promptKeyDown: function(event) + { + switch (event.keyIdentifier) { + case "Enter": + this._enterKeyPressed(event); + return; + } + + this.prompt.handleKeyEvent(event); + }, + + evalInInspectedWindow: function(expression, callback) + { + if (WebInspector.panels.scripts && WebInspector.panels.scripts.paused) { + WebInspector.panels.scripts.evaluateInSelectedCallFrame(expression, false, callback); + return; + } + this.doEvalInWindow(expression, callback); + }, + + doEvalInWindow: function(expression, callback) + { + if (!expression) { + // There is no expression, so the completion should happen against global properties. + expression = "this"; + } + + function evalCallback(result) + { + callback(result.value, result.isException); + }; + InjectedScriptAccess.evaluate(expression, evalCallback); + }, + + _enterKeyPressed: function(event) + { + if (event.altKey) + return; + + event.preventDefault(); + event.stopPropagation(); + + this.prompt.clearAutoComplete(true); + + var str = this.prompt.text; + if (!str.length) + return; + + var commandMessage = new WebInspector.ConsoleCommand(str); + this.addMessage(commandMessage); + + var self = this; + function printResult(result, exception) + { + self.prompt.history.push(str); + self.prompt.historyOffset = 0; + self.prompt.text = ""; + self.addMessage(new WebInspector.ConsoleCommandResult(result, exception, commandMessage)); + } + this.evalInInspectedWindow(str, printResult); + }, + + _format: function(output, forceObjectFormat) + { + var isProxy = (output != null && typeof output === "object"); + + if (forceObjectFormat) + var type = "object"; + else + var type = Object.proxyType(output); + + if (isProxy && type !== "object" && type !== "function" && type !== "array" && type !== "node") { + // Unwrap primitive value, skip decoration. + output = output.description; + type = "undecorated" + } + + // We don't perform any special formatting on these types, so we just + // pass them through the simple _formatvalue function. + var undecoratedTypes = { + "undefined": 1, + "null": 1, + "boolean": 1, + "number": 1, + "undecorated": 1 + }; + + var formatter; + if (forceObjectFormat) + formatter = "_formatobject"; + else if (type in undecoratedTypes) + formatter = "_formatvalue"; + else { + formatter = "_format" + type; + if (!(formatter in this)) { + formatter = "_formatobject"; + type = "object"; + } + } + + var span = document.createElement("span"); + span.addStyleClass("console-formatted-" + type); + this[formatter](output, span); + return span; + }, + + _formatvalue: function(val, elem) + { + elem.appendChild(document.createTextNode(val)); + }, + + _formatfunction: function(func, elem) + { + elem.appendChild(document.createTextNode(func.description)); + }, + + _formatdate: function(date, elem) + { + elem.appendChild(document.createTextNode(date)); + }, + + _formatstring: function(str, elem) + { + elem.appendChild(document.createTextNode("\"" + str + "\"")); + }, + + _formatregexp: function(re, elem) + { + var formatted = String(re.description).replace(/([\\\/])/g, "\\$1").replace(/\\(\/[gim]*)$/, "$1").substring(1); + elem.appendChild(document.createTextNode(formatted)); + }, + + _formatarray: function(arr, elem) + { + var self = this; + function printResult(properties) + { + if (!properties) + return; + elem.appendChild(document.createTextNode("[")); + for (var i = 0; i < properties.length; ++i) { + var property = properties[i].value; + elem.appendChild(self._format(property)); + if (i < properties.length - 1) + elem.appendChild(document.createTextNode(", ")); + } + elem.appendChild(document.createTextNode("]")); + } + InjectedScriptAccess.getProperties(arr, false, printResult); + }, + + _formatnode: function(object, elem) + { + function printNode(nodeId) + { + if (!nodeId) + return; + var treeOutline = new WebInspector.ElementsTreeOutline(); + treeOutline.rootDOMNode = WebInspector.domAgent.nodeForId(nodeId); + treeOutline.element.addStyleClass("outline-disclosure"); + if (!treeOutline.children[0].hasChildren) + treeOutline.element.addStyleClass("single-node"); + elem.appendChild(treeOutline.element); + } + InjectedScriptAccess.pushNodeToFrontend(object, printNode); + }, + + _formatobject: function(obj, elem) + { + elem.appendChild(new WebInspector.ObjectPropertiesSection(obj, obj.description, null, true).element); + }, + + _formaterror: function(obj, elem) + { + var messageElement = document.createElement("span"); + messageElement.className = "error-message"; + messageElement.textContent = obj.name + ": " + obj.message; + elem.appendChild(messageElement); + + if (obj.sourceURL) { + var urlElement = document.createElement("a"); + urlElement.className = "webkit-html-resource-link"; + urlElement.href = obj.sourceURL; + urlElement.lineNumber = obj.line; + urlElement.preferredPanel = "scripts"; + + if (obj.line > 0) + urlElement.textContent = WebInspector.displayNameForURL(obj.sourceURL) + ":" + obj.line; + else + urlElement.textContent = WebInspector.displayNameForURL(obj.sourceURL); + + elem.appendChild(document.createTextNode(" (")); + elem.appendChild(urlElement); + elem.appendChild(document.createTextNode(")")); + } + } +} + +WebInspector.ConsoleView.prototype.__proto__ = WebInspector.View.prototype; + +WebInspector.ConsoleMessage = function(source, type, level, line, url, groupLevel, repeatCount) +{ + this.source = source; + this.type = type; + this.level = level; + this.line = line; + this.url = url; + this.groupLevel = groupLevel; + this.repeatCount = repeatCount; + if (arguments.length > 7) + this.setMessageBody(Array.prototype.slice.call(arguments, 7)); +} + +WebInspector.ConsoleMessage.prototype = { + setMessageBody: function(args) + { + switch (this.type) { + case WebInspector.ConsoleMessage.MessageType.Trace: + var span = document.createElement("span"); + span.addStyleClass("console-formatted-trace"); + var stack = Array.prototype.slice.call(args); + var funcNames = stack.map(function(f) { + return f || WebInspector.UIString("(anonymous function)"); + }); + span.appendChild(document.createTextNode(funcNames.join("\n"))); + this.formattedMessage = span; + break; + case WebInspector.ConsoleMessage.MessageType.Object: + this.formattedMessage = this._format(["%O", args[0]]); + break; + default: + this.formattedMessage = this._format(args); + break; + } + + // This is used for inline message bubbles in SourceFrames, or other plain-text representations. + this.message = this.formattedMessage.textContent; + }, + + isErrorOrWarning: function() + { + return (this.level === WebInspector.ConsoleMessage.MessageLevel.Warning || this.level === WebInspector.ConsoleMessage.MessageLevel.Error); + }, + + _format: function(parameters) + { + var formattedResult = document.createElement("span"); + + if (!parameters.length) + return formattedResult; + + function formatForConsole(obj) + { + return WebInspector.console._format(obj); + } + + function formatAsObjectForConsole(obj) + { + return WebInspector.console._format(obj, true); + } + + if (typeof parameters[0] === "string") { + var formatters = {} + for (var i in String.standardFormatters) + formatters[i] = String.standardFormatters[i]; + + // Firebug uses %o for formatting objects. + formatters.o = formatForConsole; + // Firebug allows both %i and %d for formatting integers. + formatters.i = formatters.d; + // Support %O to force object formating, instead of the type-based %o formatting. + formatters.O = formatAsObjectForConsole; + + function append(a, b) + { + if (!(b instanceof Node)) + a.appendChild(WebInspector.linkifyStringAsFragment(b.toString())); + else + a.appendChild(b); + return a; + } + + var result = String.format(parameters[0], parameters.slice(1), formatters, formattedResult, append); + formattedResult = result.formattedResult; + parameters = result.unusedSubstitutions; + if (parameters.length) + formattedResult.appendChild(document.createTextNode(" ")); + } + + for (var i = 0; i < parameters.length; ++i) { + if (typeof parameters[i] === "string") + formattedResult.appendChild(WebInspector.linkifyStringAsFragment(parameters[i])); + else + formattedResult.appendChild(formatForConsole(parameters[i])); + + if (i < parameters.length - 1) + formattedResult.appendChild(document.createTextNode(" ")); + } + + return formattedResult; + }, + + toMessageElement: function() + { + if (this.propertiesSection) + return this.propertiesSection.element; + + var element = document.createElement("div"); + element.message = this; + element.className = "console-message"; + + switch (this.source) { + case WebInspector.ConsoleMessage.MessageSource.HTML: + element.addStyleClass("console-html-source"); + break; + case WebInspector.ConsoleMessage.MessageSource.WML: + element.addStyleClass("console-wml-source"); + break; + case WebInspector.ConsoleMessage.MessageSource.XML: + element.addStyleClass("console-xml-source"); + break; + case WebInspector.ConsoleMessage.MessageSource.JS: + element.addStyleClass("console-js-source"); + break; + case WebInspector.ConsoleMessage.MessageSource.CSS: + element.addStyleClass("console-css-source"); + break; + case WebInspector.ConsoleMessage.MessageSource.Other: + element.addStyleClass("console-other-source"); + break; + } + + switch (this.level) { + case WebInspector.ConsoleMessage.MessageLevel.Tip: + element.addStyleClass("console-tip-level"); + break; + case WebInspector.ConsoleMessage.MessageLevel.Log: + element.addStyleClass("console-log-level"); + break; + case WebInspector.ConsoleMessage.MessageLevel.Debug: + element.addStyleClass("console-debug-level"); + break; + case WebInspector.ConsoleMessage.MessageLevel.Warning: + element.addStyleClass("console-warning-level"); + break; + case WebInspector.ConsoleMessage.MessageLevel.Error: + element.addStyleClass("console-error-level"); + break; + } + + if (this.type === WebInspector.ConsoleMessage.MessageType.StartGroup) { + element.addStyleClass("console-group-title"); + } + + if (this.elementsTreeOutline) { + element.addStyleClass("outline-disclosure"); + element.appendChild(this.elementsTreeOutline.element); + return element; + } + + if (this.repeatCount > 1) { + var messageRepeatCountElement = document.createElement("span"); + messageRepeatCountElement.className = "bubble"; + messageRepeatCountElement.textContent = this.repeatCount; + + element.appendChild(messageRepeatCountElement); + element.addStyleClass("repeated-message"); + } + + if (this.url && this.url !== "undefined") { + var urlElement = document.createElement("a"); + urlElement.className = "console-message-url webkit-html-resource-link"; + urlElement.href = this.url; + urlElement.lineNumber = this.line; + + if (this.source === WebInspector.ConsoleMessage.MessageSource.JS) + urlElement.preferredPanel = "scripts"; + + if (this.line > 0) + urlElement.textContent = WebInspector.displayNameForURL(this.url) + ":" + this.line; + else + urlElement.textContent = WebInspector.displayNameForURL(this.url); + + element.appendChild(urlElement); + } + + var messageTextElement = document.createElement("span"); + messageTextElement.className = "console-message-text"; + messageTextElement.appendChild(this.formattedMessage); + element.appendChild(messageTextElement); + + return element; + }, + + toString: function() + { + var sourceString; + switch (this.source) { + case WebInspector.ConsoleMessage.MessageSource.HTML: + sourceString = "HTML"; + break; + case WebInspector.ConsoleMessage.MessageSource.WML: + sourceString = "WML"; + break; + case WebInspector.ConsoleMessage.MessageSource.XML: + sourceString = "XML"; + break; + case WebInspector.ConsoleMessage.MessageSource.JS: + sourceString = "JS"; + break; + case WebInspector.ConsoleMessage.MessageSource.CSS: + sourceString = "CSS"; + break; + case WebInspector.ConsoleMessage.MessageSource.Other: + sourceString = "Other"; + break; + } + + var typeString; + switch (this.type) { + case WebInspector.ConsoleMessage.MessageType.Log: + typeString = "Log"; + break; + case WebInspector.ConsoleMessage.MessageType.Object: + typeString = "Object"; + break; + case WebInspector.ConsoleMessage.MessageType.Trace: + typeString = "Trace"; + break; + case WebInspector.ConsoleMessage.MessageType.StartGroup: + typeString = "Start Group"; + break; + case WebInspector.ConsoleMessage.MessageType.EndGroup: + typeString = "End Group"; + break; + } + + var levelString; + switch (this.level) { + case WebInspector.ConsoleMessage.MessageLevel.Tip: + levelString = "Tip"; + break; + case WebInspector.ConsoleMessage.MessageLevel.Log: + levelString = "Log"; + break; + case WebInspector.ConsoleMessage.MessageLevel.Warning: + levelString = "Warning"; + break; + case WebInspector.ConsoleMessage.MessageLevel.Debug: + levelString = "Debug"; + break; + case WebInspector.ConsoleMessage.MessageLevel.Error: + levelString = "Error"; + break; + } + + return sourceString + " " + typeString + " " + levelString + ": " + this.formattedMessage.textContent + "\n" + this.url + " line " + this.line; + }, + + isEqual: function(msg, disreguardGroup) + { + if (!msg) + return false; + + var ret = (this.source == msg.source) + && (this.type == msg.type) + && (this.level == msg.level) + && (this.line == msg.line) + && (this.url == msg.url) + && (this.message == msg.message); + + return (disreguardGroup ? ret : (ret && (this.groupLevel == msg.groupLevel))); + } +} + +// Note: Keep these constants in sync with the ones in Console.h +WebInspector.ConsoleMessage.MessageSource = { + HTML: 0, + WML: 1, + XML: 2, + JS: 3, + CSS: 4, + Other: 5 +} + +WebInspector.ConsoleMessage.MessageType = { + Log: 0, + Object: 1, + Trace: 2, + StartGroup: 3, + EndGroup: 4 +} + +WebInspector.ConsoleMessage.MessageLevel = { + Tip: 0, + Log: 1, + Warning: 2, + Error: 3, + Debug: 4 +} + +WebInspector.ConsoleCommand = function(command) +{ + this.command = command; +} + +WebInspector.ConsoleCommand.prototype = { + toMessageElement: function() + { + var element = document.createElement("div"); + element.command = this; + element.className = "console-user-command"; + + var commandTextElement = document.createElement("span"); + commandTextElement.className = "console-message-text"; + commandTextElement.textContent = this.command; + element.appendChild(commandTextElement); + + return element; + } +} + +WebInspector.ConsoleTextMessage = function(text, level) +{ + level = level || WebInspector.ConsoleMessage.MessageLevel.Log; + WebInspector.ConsoleMessage.call(this, WebInspector.ConsoleMessage.MessageSource.JS, WebInspector.ConsoleMessage.MessageType.Log, level, 0, null, null, 1, text); +} + +WebInspector.ConsoleTextMessage.prototype.__proto__ = WebInspector.ConsoleMessage.prototype; + +WebInspector.ConsoleCommandResult = function(result, exception, originatingCommand) +{ + var level = (exception ? WebInspector.ConsoleMessage.MessageLevel.Error : WebInspector.ConsoleMessage.MessageLevel.Log); + var message = (exception ? String(result) : result); + var line = (exception ? result.line : -1); + var url = (exception ? result.sourceURL : null); + + WebInspector.ConsoleMessage.call(this, WebInspector.ConsoleMessage.MessageSource.JS, WebInspector.ConsoleMessage.MessageType.Log, level, line, url, null, 1, message); + + this.originatingCommand = originatingCommand; +} + +WebInspector.ConsoleCommandResult.prototype = { + toMessageElement: function() + { + var element = WebInspector.ConsoleMessage.prototype.toMessageElement.call(this); + element.addStyleClass("console-user-command-result"); + return element; + } +} + +WebInspector.ConsoleCommandResult.prototype.__proto__ = WebInspector.ConsoleMessage.prototype; + +WebInspector.ConsoleGroup = function(parentGroup, level) +{ + this.parentGroup = parentGroup; + this.level = level; + + var element = document.createElement("div"); + element.className = "console-group"; + element.group = this; + this.element = element; + + var messagesElement = document.createElement("div"); + messagesElement.className = "console-group-messages"; + element.appendChild(messagesElement); + this.messagesElement = messagesElement; +} + +WebInspector.ConsoleGroup.prototype = { + addMessage: function(msg) + { + var element = msg.toMessageElement(); + + if (msg.type === WebInspector.ConsoleMessage.MessageType.StartGroup) { + this.messagesElement.parentNode.insertBefore(element, this.messagesElement); + element.addEventListener("click", this._titleClicked.bind(this), true); + } else + this.messagesElement.appendChild(element); + + if (element.previousSibling && msg.originatingCommand && element.previousSibling.command === msg.originatingCommand) + element.previousSibling.addStyleClass("console-adjacent-user-command-result"); + }, + + _titleClicked: function(event) + { + var groupTitleElement = event.target.enclosingNodeOrSelfWithClass("console-group-title"); + if (groupTitleElement) { + var groupElement = groupTitleElement.enclosingNodeOrSelfWithClass("console-group"); + if (groupElement) + if (groupElement.hasStyleClass("collapsed")) + groupElement.removeStyleClass("collapsed"); + else + groupElement.addStyleClass("collapsed"); + groupTitleElement.scrollIntoViewIfNeeded(true); + } + + event.stopPropagation(); + event.preventDefault(); + } +} diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/CookieItemsView.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/CookieItemsView.js new file mode 100644 index 0000000..f9604a4 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/CookieItemsView.js @@ -0,0 +1,266 @@ +/* + * Copyright (C) 2009 Apple Inc. All rights reserved. + * Copyright (C) 2009 Joseph Pecoraro + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.CookieItemsView = function() +{ + WebInspector.View.call(this); + + this.element.addStyleClass("storage-view"); + this.element.addStyleClass("table"); + + this.deleteButton = new WebInspector.StatusBarButton(WebInspector.UIString("Delete"), "delete-storage-status-bar-item"); + this.deleteButton.visible = false; + this.deleteButton.addEventListener("click", this._deleteButtonClicked.bind(this), false); + + this.refreshButton = new WebInspector.StatusBarButton(WebInspector.UIString("Refresh"), "refresh-storage-status-bar-item"); + this.refreshButton.addEventListener("click", this._refreshButtonClicked.bind(this), false); +} + +WebInspector.CookieItemsView.prototype = { + get statusBarItems() + { + return [this.refreshButton.element, this.deleteButton.element]; + }, + + show: function(parentElement) + { + WebInspector.View.prototype.show.call(this, parentElement); + this.update(); + }, + + hide: function() + { + WebInspector.View.prototype.hide.call(this); + this.deleteButton.visible = false; + }, + + update: function() + { + this.element.removeChildren(); + + var self = this; + function callback(cookies, isAdvanced) { + var dataGrid = (isAdvanced ? self.dataGridForCookies(cookies) : self.simpleDataGridForCookies(cookies)); + if (dataGrid) { + self._dataGrid = dataGrid; + self.element.appendChild(dataGrid.element); + if (isAdvanced) + self.deleteButton.visible = true; + } else { + var emptyMsgElement = document.createElement("div"); + emptyMsgElement.className = "storage-table-empty"; + emptyMsgElement.textContent = WebInspector.UIString("This site has no cookies."); + self.element.appendChild(emptyMsgElement); + self._dataGrid = null; + self.deleteButton.visible = false; + } + } + + WebInspector.Cookies.getCookiesAsync(callback); + }, + + dataGridForCookies: function(cookies) + { + if (!cookies.length) + return null; + + for (var i = 0; i < cookies.length; ++i) + cookies[i].expires = new Date(cookies[i].expires); + + var columns = { 0: {}, 1: {}, 2: {}, 3: {}, 4: {}, 5: {}, 6: {}, 7: {} }; + columns[0].title = WebInspector.UIString("Name"); + columns[0].width = columns[0].title.length; + columns[1].title = WebInspector.UIString("Value"); + columns[1].width = columns[1].title.length; + columns[2].title = WebInspector.UIString("Domain"); + columns[2].width = columns[2].title.length; + columns[3].title = WebInspector.UIString("Path"); + columns[3].width = columns[3].title.length; + columns[4].title = WebInspector.UIString("Expires"); + columns[4].width = columns[4].title.length; + columns[5].title = WebInspector.UIString("Size"); + columns[5].width = columns[5].title.length; + columns[5].aligned = "right"; + columns[6].title = WebInspector.UIString("HTTP"); + columns[6].width = columns[6].title.length; + columns[6].aligned = "centered"; + columns[7].title = WebInspector.UIString("Secure"); + columns[7].width = columns[7].title.length; + columns[7].aligned = "centered"; + + function updateDataAndColumn(index, value) { + data[index] = value; + if (value.length > columns[index].width) + columns[index].width = value.length; + } + + var data; + var nodes = []; + for (var i = 0; i < cookies.length; ++i) { + var cookie = cookies[i]; + data = {}; + + updateDataAndColumn(0, cookie.name); + updateDataAndColumn(1, cookie.value); + updateDataAndColumn(2, cookie.domain); + updateDataAndColumn(3, cookie.path); + updateDataAndColumn(4, (cookie.session ? WebInspector.UIString("Session") : cookie.expires.toGMTString())); + updateDataAndColumn(5, Number.bytesToString(cookie.size, WebInspector.UIString)); + updateDataAndColumn(6, (cookie.httpOnly ? "\u2713" : "")); // Checkmark + updateDataAndColumn(7, (cookie.secure ? "\u2713" : "")); // Checkmark + + var node = new WebInspector.DataGridNode(data, false); + node.cookie = cookie; + node.selectable = true; + nodes.push(node); + } + + var totalColumnWidths = 0; + for (var columnIdentifier in columns) + totalColumnWidths += columns[columnIdentifier].width; + + // Enforce the Value column (the 2nd column) to be a max of 33% + // tweaking the raw total width because may massively outshadow the others + var valueColumnWidth = columns[1].width; + if (valueColumnWidth / totalColumnWidths > 0.33) { + totalColumnWidths -= valueColumnWidth; + totalColumnWidths *= 1.33; + columns[1].width = totalColumnWidths * 0.33; + } + + // Calculate the percentage width for the columns. + const minimumPrecent = 6; + var recoupPercent = 0; + for (var columnIdentifier in columns) { + var width = columns[columnIdentifier].width; + width = Math.round((width / totalColumnWidths) * 100); + if (width < minimumPrecent) { + recoupPercent += (minimumPrecent - width); + width = minimumPrecent; + } + columns[columnIdentifier].width = width; + } + + // Enforce the minimum percentage width. (need to narrow total percentage due to earlier additions) + while (recoupPercent > 0) { + for (var columnIdentifier in columns) { + if (columns[columnIdentifier].width > minimumPrecent) { + --columns[columnIdentifier].width; + --recoupPercent; + if (!recoupPercent) + break; + } + } + } + + for (var columnIdentifier in columns) + columns[columnIdentifier].width += "%"; + + var dataGrid = new WebInspector.DataGrid(columns); + var length = nodes.length; + for (var i = 0; i < length; ++i) + dataGrid.appendChild(nodes[i]); + if (length > 0) + nodes[0].selected = true; + + return dataGrid; + }, + + simpleDataGridForCookies: function(cookies) + { + if (!cookies.length) + return null; + + var columns = {}; + columns[0] = {}; + columns[1] = {}; + columns[0].title = WebInspector.UIString("Name"); + columns[0].width = columns[0].title.length; + columns[1].title = WebInspector.UIString("Value"); + columns[1].width = columns[1].title.length; + + var nodes = []; + for (var i = 0; i < cookies.length; ++i) { + var cookie = cookies[i]; + var data = {}; + + var name = cookie.name; + data[0] = name; + if (name.length > columns[0].width) + columns[0].width = name.length; + + var value = cookie.value; + data[1] = value; + if (value.length > columns[1].width) + columns[1].width = value.length; + + var node = new WebInspector.DataGridNode(data, false); + node.selectable = true; + nodes.push(node); + } + + var totalColumnWidths = columns[0].width + columns[1].width; + var width = Math.round((columns[0].width * 100) / totalColumnWidths); + const minimumPrecent = 20; + if (width < minimumPrecent) + width = minimumPrecent; + if (width > 100 - minimumPrecent) + width = 100 - minimumPrecent; + columns[0].width = width; + columns[1].width = 100 - width; + columns[0].width += "%"; + columns[1].width += "%"; + + var dataGrid = new WebInspector.DataGrid(columns); + var length = nodes.length; + for (var i = 0; i < length; ++i) + dataGrid.appendChild(nodes[i]); + if (length > 0) + nodes[0].selected = true; + + return dataGrid; + }, + + _deleteButtonClicked: function(event) + { + if (!this._dataGrid) + return; + + var cookie = this._dataGrid.selectedNode.cookie; + InspectorController.deleteCookie(cookie.name); + this.update(); + }, + + _refreshButtonClicked: function(event) + { + this.update(); + } +} + +WebInspector.CookieItemsView.prototype.__proto__ = WebInspector.View.prototype; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/DOMAgent.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/DOMAgent.js new file mode 100644 index 0000000..47c8041 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/DOMAgent.js @@ -0,0 +1,650 @@ +/* + * Copyright (C) 2009 Google Inc. All rights reserved. + * Copyright (C) 2009 Joseph Pecoraro + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.DOMNode = function(doc, payload) { + this.ownerDocument = doc; + + this.id = payload.id; + this.nodeType = payload.nodeType; + this.nodeName = payload.nodeName; + this._nodeValue = payload.nodeValue; + this.textContent = this.nodeValue; + + this.attributes = []; + this._attributesMap = {}; + if (payload.attributes) + this._setAttributesPayload(payload.attributes); + + this._childNodeCount = payload.childNodeCount; + this.children = null; + + this.nextSibling = null; + this.prevSibling = null; + this.firstChild = null; + this.lastChild = null; + this.parentNode = null; + + if (payload.children) + this._setChildrenPayload(payload.children); + + this._computedStyle = null; + this.style = null; + this._matchedCSSRules = []; + + if (this.nodeType == Node.ELEMENT_NODE) { + if (this.nodeName == "HTML") + this.ownerDocument.documentElement = this; + if (this.nodeName == "BODY") + this.ownerDocument.body = this; + } +} + +WebInspector.DOMNode.prototype = { + hasAttributes: function() + { + return this.attributes.length > 0; + }, + + hasChildNodes: function() { + return this._childNodeCount > 0; + }, + + get nodeValue() { + return this._nodeValue; + }, + + set nodeValue(value) { + if (this.nodeType != Node.TEXT_NODE) + return; + var self = this; + var callback = function() + { + self._nodeValue = value; + self.textContent = value; + }; + this.ownerDocument._domAgent.setTextNodeValueAsync(this, value, callback); + }, + + getAttribute: function(name) + { + var attr = this._attributesMap[name]; + return attr ? attr.value : undefined; + }, + + setAttribute: function(name, value) + { + var self = this; + var callback = function() + { + var attr = self._attributesMap[name]; + if (attr) + attr.value = value; + else + attr = self._addAttribute(name, value); + }; + this.ownerDocument._domAgent.setAttributeAsync(this, name, value, callback); + }, + + removeAttribute: function(name) + { + var self = this; + var callback = function() + { + delete self._attributesMap[name]; + for (var i = 0; i < self.attributes.length; ++i) { + if (self.attributes[i].name == name) { + self.attributes.splice(i, 1); + break; + } + } + }; + this.ownerDocument._domAgent.removeAttributeAsync(this, name, callback); + }, + + _setAttributesPayload: function(attrs) + { + for (var i = 0; i < attrs.length; i += 2) + this._addAttribute(attrs[i], attrs[i + 1]); + }, + + _insertChild: function(prev, payload) + { + var node = new WebInspector.DOMNode(this.ownerDocument, payload); + if (!prev) + // First node + this.children = [ node ]; + else + this.children.splice(this.children.indexOf(prev) + 1, 0, node); + this._renumber(); + return node; + }, + + removeChild_: function(node) + { + this.children.splice(this.children.indexOf(node), 1); + node.parentNode = null; + this._renumber(); + }, + + _setChildrenPayload: function(payloads) + { + this.children = []; + for (var i = 0; i < payloads.length; ++i) { + var payload = payloads[i]; + var node = new WebInspector.DOMNode(this.ownerDocument, payload); + this.children.push(node); + } + this._renumber(); + }, + + _renumber: function() + { + this._childNodeCount = this.children.length; + if (this._childNodeCount == 0) { + this.firstChild = null; + this.lastChild = null; + return; + } + this.firstChild = this.children[0]; + this.lastChild = this.children[this._childNodeCount - 1]; + for (var i = 0; i < this._childNodeCount; ++i) { + var child = this.children[i]; + child.nextSibling = i + 1 < this._childNodeCount ? this.children[i + 1] : null; + child.prevSibling = i - 1 >= 0 ? this.children[i - 1] : null; + child.parentNode = this; + } + }, + + _addAttribute: function(name, value) + { + var attr = { + "name": name, + "value": value, + "_node": this + }; + this._attributesMap[name] = attr; + this.attributes.push(attr); + }, + + _setStyles: function(computedStyle, inlineStyle, styleAttributes, matchedCSSRules) + { + this._computedStyle = new WebInspector.CSSStyleDeclaration(computedStyle); + this.style = new WebInspector.CSSStyleDeclaration(inlineStyle); + + for (var name in styleAttributes) { + if (this._attributesMap[name]) + this._attributesMap[name].style = new WebInspector.CSSStyleDeclaration(styleAttributes[name]); + } + + this._matchedCSSRules = []; + for (var i = 0; i < matchedCSSRules.length; i++) + this._matchedCSSRules.push(WebInspector.CSSStyleDeclaration.parseRule(matchedCSSRules[i])); + }, + + _clearStyles: function() + { + this.computedStyle = null; + this.style = null; + for (var name in this._attributesMap) + this._attributesMap[name].style = null; + this._matchedCSSRules = null; + } +} + +WebInspector.DOMDocument = function(domAgent, defaultView, payload) +{ + WebInspector.DOMNode.call(this, this, payload); + this._listeners = {}; + this._domAgent = domAgent; + this.defaultView = defaultView; +} + +WebInspector.DOMDocument.prototype = { + + addEventListener: function(name, callback) + { + var listeners = this._listeners[name]; + if (!listeners) { + listeners = []; + this._listeners[name] = listeners; + } + listeners.push(callback); + }, + + removeEventListener: function(name, callback) + { + var listeners = this._listeners[name]; + if (!listeners) + return; + + var index = listeners.indexOf(callback); + if (index != -1) + listeners.splice(index, 1); + }, + + _fireDomEvent: function(name, event) + { + var listeners = this._listeners[name]; + if (!listeners) + return; + + for (var i = 0; i < listeners.length; ++i) { + var listener = listeners[i]; + listener.call(this, event); + } + } +} + +WebInspector.DOMDocument.prototype.__proto__ = WebInspector.DOMNode.prototype; + + +WebInspector.DOMWindow = function(domAgent) +{ + this._domAgent = domAgent; +} + +WebInspector.DOMWindow.prototype = { + get document() + { + return this._domAgent.document; + }, + + get Node() + { + return WebInspector.DOMNode; + }, + + get Element() + { + return WebInspector.DOMNode; + }, + + Object: function() + { + }, + + getComputedStyle: function(node) + { + return node._computedStyle; + }, + + getMatchedCSSRules: function(node, pseudoElement, authorOnly) + { + return node._matchedCSSRules; + } +} + +WebInspector.DOMAgent = function() { + this._window = new WebInspector.DOMWindow(this); + this._idToDOMNode = null; + this.document = null; + + // TODO: update ElementsPanel to not track embedded iframes - it is already being handled + // in the agent backend. + + // Whitespace is ignored in InspectorDOMAgent already -> no need to filter. + // TODO: Either remove all of its usages or push value into the agent backend. + Preferences.ignoreWhitespace = false; +} + +WebInspector.DOMAgent.prototype = { + get domWindow() + { + return this._window; + }, + + getChildNodesAsync: function(parent, callback) + { + var children = parent.children; + if (children) { + callback(children); + return; + } + function mycallback() { + callback(parent.children); + } + var callId = WebInspector.Callback.wrap(mycallback); + InspectorController.getChildNodes(callId, parent.id); + }, + + setAttributeAsync: function(node, name, value, callback) + { + var mycallback = this._didApplyDomChange.bind(this, node, callback); + InspectorController.setAttribute(WebInspector.Callback.wrap(mycallback), node.id, name, value); + }, + + removeAttributeAsync: function(node, name, callback) + { + var mycallback = this._didApplyDomChange.bind(this, node, callback); + InspectorController.removeAttribute(WebInspector.Callback.wrap(mycallback), node.id, name); + }, + + setTextNodeValueAsync: function(node, text, callback) + { + var mycallback = this._didApplyDomChange.bind(this, node, callback); + InspectorController.setTextNodeValue(WebInspector.Callback.wrap(mycallback), node.id, text); + }, + + _didApplyDomChange: function(node, callback, success) + { + if (!success) + return; + callback(); + // TODO(pfeldman): Fix this hack. + var elem = WebInspector.panels.elements.treeOutline.findTreeElement(node); + if (elem) { + elem._updateTitle(); + } + }, + + _attributesUpdated: function(nodeId, attrsArray) + { + var node = this._idToDOMNode[nodeId]; + node._setAttributesPayload(attrsArray); + }, + + nodeForId: function(nodeId) { + return this._idToDOMNode[nodeId]; + }, + + _setDocument: function(payload) + { + this.document = new WebInspector.DOMDocument(this, this._window, payload); + this._idToDOMNode = {}; + this._idToDOMNode[payload.id] = this.document; + this._bindNodes(this.document.children); + WebInspector.panels.elements.reset(); + }, + + _setDetachedRoot: function(payload) + { + var root = new WebInspector.DOMNode(this.document, payload); + this._idToDOMNode[payload.id] = root; + }, + + _setChildNodes: function(parentId, payloads) + { + var parent = this._idToDOMNode[parentId]; + parent._setChildrenPayload(payloads); + this._bindNodes(parent.children); + }, + + _bindNodes: function(children) + { + for (var i = 0; i < children.length; ++i) { + var child = children[i]; + this._idToDOMNode[child.id] = child; + if (child.children) + this._bindNodes(child.children); + } + }, + + _childNodeCountUpdated: function(nodeId, newValue) + { + var node = this._idToDOMNode[nodeId]; + node._childNodeCount = newValue; + var outline = WebInspector.panels.elements.treeOutline; + var treeElement = outline.findTreeElement(node); + if (treeElement) { + treeElement.hasChildren = newValue; + treeElement.whitespaceIgnored = Preferences.ignoreWhitespace; + } + }, + + _childNodeInserted: function(parentId, prevId, payload) + { + var parent = this._idToDOMNode[parentId]; + var prev = this._idToDOMNode[prevId]; + var node = parent._insertChild(prev, payload); + this._idToDOMNode[node.id] = node; + var event = { target : node, relatedNode : parent }; + this.document._fireDomEvent("DOMNodeInserted", event); + }, + + _childNodeRemoved: function(parentId, nodeId) + { + var parent = this._idToDOMNode[parentId]; + var node = this._idToDOMNode[nodeId]; + parent.removeChild_(node); + var event = { target : node, relatedNode : parent }; + this.document._fireDomEvent("DOMNodeRemoved", event); + delete this._idToDOMNode[nodeId]; + } +} + +WebInspector.Cookies = {} + +WebInspector.Cookies.getCookiesAsync = function(callback) +{ + function mycallback(cookies, cookiesString) { + if (cookiesString) + callback(WebInspector.Cookies.buildCookiesFromString(cookiesString), false); + else + callback(cookies, true); + } + var callId = WebInspector.Callback.wrap(mycallback); + InspectorController.getCookies(callId); +} + +WebInspector.Cookies.buildCookiesFromString = function(rawCookieString) +{ + var rawCookies = rawCookieString.split(/;\s*/); + var cookies = []; + + if (!(/^\s*$/.test(rawCookieString))) { + for (var i = 0; i < rawCookies.length; ++i) { + var cookie = rawCookies[i]; + var delimIndex = cookie.indexOf("="); + var name = cookie.substring(0, delimIndex); + var value = cookie.substring(delimIndex + 1); + var size = name.length + value.length; + cookies.push({ name: name, value: value, size: size }); + } + } + + return cookies; +} + +WebInspector.CSSStyleDeclaration = function(payload) +{ + this.id = payload.id; + this.width = payload.width; + this.height = payload.height; + this.__disabledProperties = payload.__disabledProperties; + this.__disabledPropertyValues = payload.__disabledPropertyValues; + this.__disabledPropertyPriorities = payload.__disabledPropertyPriorities; + this.uniqueStyleProperties = payload.uniqueStyleProperties; + this._shorthandValues = payload.shorthandValues; + this._propertyMap = {}; + this._longhandProperties = {}; + this.length = payload.properties.length; + + for (var i = 0; i < this.length; ++i) { + var property = payload.properties[i]; + var name = property.name; + this[i] = name; + this._propertyMap[name] = property; + } + + // Index longhand properties. + for (var i = 0; i < this.uniqueStyleProperties.length; ++i) { + var name = this.uniqueStyleProperties[i]; + var property = this._propertyMap[name]; + if (property.shorthand) { + var longhands = this._longhandProperties[property.shorthand]; + if (!longhands) { + longhands = []; + this._longhandProperties[property.shorthand] = longhands; + } + longhands.push(name); + } + } +} + +WebInspector.CSSStyleDeclaration.parseStyle = function(payload) +{ + return new WebInspector.CSSStyleDeclaration(payload); +} + +WebInspector.CSSStyleDeclaration.parseRule = function(payload) +{ + var rule = {}; + rule.id = payload.id; + rule.selectorText = payload.selectorText; + rule.style = new WebInspector.CSSStyleDeclaration(payload.style); + rule.style.parentRule = rule; + rule.isUserAgent = payload.isUserAgent; + rule.isUser = payload.isUser; + rule.isViaInspector = payload.isViaInspector; + if (payload.parentStyleSheet) + rule.parentStyleSheet = { href: payload.parentStyleSheet.href }; + + return rule; +} + +WebInspector.CSSStyleDeclaration.prototype = { + getPropertyValue: function(name) + { + var property = this._propertyMap[name]; + return property ? property.value : ""; + }, + + getPropertyPriority: function(name) + { + var property = this._propertyMap[name]; + return property ? property.priority : ""; + }, + + getPropertyShorthand: function(name) + { + var property = this._propertyMap[name]; + return property ? property.shorthand : ""; + }, + + isPropertyImplicit: function(name) + { + var property = this._propertyMap[name]; + return property ? property.implicit : ""; + }, + + styleTextWithShorthands: function() + { + var cssText = ""; + var foundProperties = {}; + for (var i = 0; i < this.length; ++i) { + var individualProperty = this[i]; + var shorthandProperty = this.getPropertyShorthand(individualProperty); + var propertyName = (shorthandProperty || individualProperty); + + if (propertyName in foundProperties) + continue; + + if (shorthandProperty) { + var value = this.getPropertyValue(shorthandProperty); + var priority = this.getShorthandPriority(shorthandProperty); + } else { + var value = this.getPropertyValue(individualProperty); + var priority = this.getPropertyPriority(individualProperty); + } + + foundProperties[propertyName] = true; + + cssText += propertyName + ": " + value; + if (priority) + cssText += " !" + priority; + cssText += "; "; + } + + return cssText; + }, + + getLonghandProperties: function(name) + { + return this._longhandProperties[name] || []; + }, + + getShorthandValue: function(shorthandProperty) + { + return this._shorthandValues[shorthandProperty]; + }, + + getShorthandPriority: function(shorthandProperty) + { + var priority = this.getPropertyPriority(shorthandProperty); + if (priority) + return priority; + + var longhands = this._longhandProperties[shorthandProperty]; + return longhands ? this.getPropertyPriority(longhands[0]) : null; + } +} + +WebInspector.attributesUpdated = function() +{ + this.domAgent._attributesUpdated.apply(this.domAgent, arguments); +} + +WebInspector.setDocument = function() +{ + this.domAgent._setDocument.apply(this.domAgent, arguments); +} + +WebInspector.setDetachedRoot = function() +{ + this.domAgent._setDetachedRoot.apply(this.domAgent, arguments); +} + +WebInspector.setChildNodes = function() +{ + this.domAgent._setChildNodes.apply(this.domAgent, arguments); +} + +WebInspector.childNodeCountUpdated = function() +{ + this.domAgent._childNodeCountUpdated.apply(this.domAgent, arguments); +} + +WebInspector.childNodeInserted = function() +{ + this.domAgent._childNodeInserted.apply(this.domAgent, arguments); +} + +WebInspector.childNodeRemoved = function() +{ + this.domAgent._childNodeRemoved.apply(this.domAgent, arguments); +} + +WebInspector.didGetCookies = WebInspector.Callback.processCallback; +WebInspector.didGetChildNodes = WebInspector.Callback.processCallback; +WebInspector.didPerformSearch = WebInspector.Callback.processCallback; +WebInspector.didApplyDomChange = WebInspector.Callback.processCallback; +WebInspector.didRemoveAttribute = WebInspector.Callback.processCallback; +WebInspector.didSetTextNodeValue = WebInspector.Callback.processCallback; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/DOMStorage.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/DOMStorage.js new file mode 100644 index 0000000..5207b69 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/DOMStorage.js @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2008 Nokia Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.DOMStorage = function(domStorage, domain, isLocalStorage) +{ + this.domStorage = domStorage; + this.domain = domain; + this.isLocalStorage = isLocalStorage; +} + +WebInspector.DOMStorage.prototype = { + get domStorage() + { + return this._domStorage; + }, + + set domStorage(x) + { + if (this._domStorage === x) + return; + this._domStorage = x; + }, + + get domain() + { + return this._domain; + }, + + set domain(x) + { + if (this._domain === x) + return; + this._domain = x; + }, + + get isLocalStorage() + { + return this._isLocalStorage; + }, + + set isLocalStorage(x) + { + if (this._isLocalStorage === x) + return; + this._isLocalStorage = x; + } +} diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/DOMStorageDataGrid.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/DOMStorageDataGrid.js new file mode 100644 index 0000000..efdd090 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/DOMStorageDataGrid.js @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2009 Nokia Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.DOMStorageDataGrid = function(columns) +{ + WebInspector.DataGrid.call(this, columns); + this.dataTableBody.addEventListener("dblclick", this._ondblclick.bind(this), false); +} + +WebInspector.DOMStorageDataGrid.prototype = { + _ondblclick: function(event) + { + if (this._editing) + return; + if (this._editingNode) + return; + this._startEditing(event); + }, + + _startEditingColumnOfDataGridNode: function(node, column) + { + this._editing = true; + this._editingNode = node; + this._editingNode.select(); + WebInspector.panels.storage._unregisterStorageEventListener(); + + var element = this._editingNode._element.children[column]; + WebInspector.startEditing(element, this._editingCommitted.bind(this), this._editingCancelled.bind(this), element.textContent); + window.getSelection().setBaseAndExtent(element, 0, element, 1); + }, + + _startEditing: function(event) + { + var element = event.target.enclosingNodeOrSelfWithNodeName("td"); + if (!element) + return; + + this._editingNode = this.dataGridNodeFromEvent(event); + if (!this._editingNode) { + if (!this.creationNode) + return; + this._editingNode = this.creationNode; + } + + // Force editing the "Key" column when editing the creation node + if (this._editingNode.isCreationNode) + return this._startEditingColumnOfDataGridNode(this._editingNode, 0); + + this._editing = true; + WebInspector.panels.storage._unregisterStorageEventListener(); + WebInspector.startEditing(element, this._editingCommitted.bind(this), this._editingCancelled.bind(this), element.textContent); + window.getSelection().setBaseAndExtent(element, 0, element, 1); + }, + + _editingCommitted: function(element, newText, oldText, context, moveDirection) + { + var columnIdentifier = (element.hasStyleClass("0-column") ? 0 : 1); + var textBeforeEditing = this._editingNode.data[columnIdentifier]; + var currentEditingNode = this._editingNode; + + function moveToNextIfNeeded(wasChange) { + if (!moveDirection) + return; + + if (moveDirection === "forward") { + if (currentEditingNode.isCreationNode && columnIdentifier === 0 && !wasChange) + return; + + if (columnIdentifier === 0) + return this._startEditingColumnOfDataGridNode(currentEditingNode, 1); + + var nextDataGridNode = currentEditingNode.traverseNextNode(true, null, true); + if (nextDataGridNode) + return this._startEditingColumnOfDataGridNode(nextDataGridNode, 0); + if (currentEditingNode.isCreationNode && wasChange) { + addCreationNode(false); + return this._startEditingColumnOfDataGridNode(this.creationNode, 0); + } + return; + } + + if (moveDirection === "backward") { + if (columnIdentifier === 1) + return this._startEditingColumnOfDataGridNode(currentEditingNode, 0); + var nextDataGridNode = currentEditingNode.traversePreviousNode(true, null, true); + + if (nextDataGridNode) + return this._startEditingColumnOfDataGridNode(nextDataGridNode, 1); + return; + } + } + + if (textBeforeEditing == newText) { + this._editingCancelled(element); + moveToNextIfNeeded.call(this, false); + return; + } + + var domStorage = WebInspector.panels.storage.visibleView.domStorage.domStorage; + if (domStorage) { + if (columnIdentifier == 0) { + if (domStorage.getItem(newText) != null) { + element.textContent = this._editingNode.data[0]; + this._editingCancelled(element); + moveToNextIfNeeded.call(this, false); + return; + } + domStorage.removeItem(this._editingNode.data[0]); + domStorage.setItem(newText, this._editingNode.data[1]); + this._editingNode.data[0] = newText; + } else { + domStorage.setItem(this._editingNode.data[0], newText); + this._editingNode.data[1] = newText; + } + } + + if (this._editingNode.isCreationNode) + this.addCreationNode(false); + + this._editingCancelled(element); + moveToNextIfNeeded.call(this, true); + }, + + _editingCancelled: function(element, context) + { + delete this._editing; + this._editingNode = null; + WebInspector.panels.storage._registerStorageEventListener(); + }, + + deleteSelectedRow: function() + { + var node = this.selectedNode; + if (this.selectedNode.isCreationNode) + return; + + var domStorage = WebInspector.panels.storage.visibleView.domStorage.domStorage; + if (node && domStorage) + domStorage.removeItem(node.data[0]); + } +} + +WebInspector.DOMStorageDataGrid.prototype.__proto__ = WebInspector.DataGrid.prototype; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/DOMStorageItemsView.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/DOMStorageItemsView.js new file mode 100644 index 0000000..8617d60 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/DOMStorageItemsView.js @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2008 Nokia Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.DOMStorageItemsView = function(domStorage) +{ + WebInspector.View.call(this); + + this.domStorage = domStorage; + + this.element.addStyleClass("storage-view"); + this.element.addStyleClass("table"); + + this.deleteButton = new WebInspector.StatusBarButton(WebInspector.UIString("Delete"), "delete-storage-status-bar-item"); + this.deleteButton.visible = false; + this.deleteButton.addEventListener("click", this._deleteButtonClicked.bind(this), false); + + this.refreshButton = new WebInspector.StatusBarButton(WebInspector.UIString("Refresh"), "refresh-storage-status-bar-item"); + this.refreshButton.addEventListener("click", this._refreshButtonClicked.bind(this), false); +} + +WebInspector.DOMStorageItemsView.prototype = { + get statusBarItems() + { + return [this.refreshButton.element, this.deleteButton.element]; + }, + + show: function(parentElement) + { + WebInspector.View.prototype.show.call(this, parentElement); + this.update(); + }, + + hide: function() + { + WebInspector.View.prototype.hide.call(this); + this.deleteButton.visible = false; + }, + + update: function() + { + this.element.removeChildren(); + var hasDOMStorage = this.domStorage; + if (hasDOMStorage) + hasDOMStorage = this.domStorage.domStorage; + + if (hasDOMStorage) { + var dataGrid = WebInspector.panels.storage.dataGridForDOMStorage(this.domStorage.domStorage); + if (!dataGrid) + hasDOMStorage = 0; + else { + this._dataGrid = dataGrid; + this.element.appendChild(dataGrid.element); + this._dataGrid.updateWidths(); + this.deleteButton.visible = true; + } + } + + if (!hasDOMStorage) { + var emptyMsgElement = document.createElement("div"); + emptyMsgElement.className = "storage-table-empty"; + if (this.domStorage) + emptyMsgElement.textContent = WebInspector.UIString("This storage is empty."); + this.element.appendChild(emptyMsgElement); + this._dataGrid = null; + this.deleteButton.visible = false; + } + }, + + resize: function() + { + if (this._dataGrid) + this._dataGrid.updateWidths(); + }, + + _deleteButtonClicked: function(event) + { + if (this._dataGrid) { + this._dataGrid.deleteSelectedRow(); + + this.show(); + } + }, + + _refreshButtonClicked: function(event) + { + this.update(); + } +} + +WebInspector.DOMStorageItemsView.prototype.__proto__ = WebInspector.View.prototype; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/DataGrid.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/DataGrid.js new file mode 100644 index 0000000..ce61548 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/DataGrid.js @@ -0,0 +1,1041 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.DataGrid = function(columns) +{ + this.element = document.createElement("div"); + this.element.className = "data-grid"; + this.element.tabIndex = 0; + this.element.addEventListener("keydown", this._keyDown.bind(this), false); + + this._headerTable = document.createElement("table"); + this._headerTable.className = "header"; + + this._dataTable = document.createElement("table"); + this._dataTable.className = "data"; + + this._dataTable.addEventListener("mousedown", this._mouseDownInDataTable.bind(this), true); + this._dataTable.addEventListener("click", this._clickInDataTable.bind(this), true); + + this.aligned = {}; + + var scrollContainer = document.createElement("div"); + scrollContainer.className = "data-container"; + scrollContainer.appendChild(this._dataTable); + + this.element.appendChild(this._headerTable); + this.element.appendChild(scrollContainer); + + var headerRow = document.createElement("tr"); + var columnGroup = document.createElement("colgroup"); + var columnCount = 0; + + for (var columnIdentifier in columns) { + var column = columns[columnIdentifier]; + if (column.disclosure) + this.disclosureColumnIdentifier = columnIdentifier; + + var col = document.createElement("col"); + if (column.width) + col.style.width = column.width; + columnGroup.appendChild(col); + + var cell = document.createElement("th"); + cell.className = columnIdentifier + "-column"; + cell.columnIdentifier = columnIdentifier; + + var div = document.createElement("div"); + div.textContent = column.title; + cell.appendChild(div); + + if (column.sort) { + cell.addStyleClass("sort-" + column.sort); + this._sortColumnCell = cell; + } + + if (column.sortable) { + cell.addEventListener("click", this._clickInHeaderCell.bind(this), false); + cell.addStyleClass("sortable"); + } + + if (column.aligned) { + cell.addStyleClass(column.aligned); + this.aligned[columnIdentifier] = column.aligned; + } + + headerRow.appendChild(cell); + + ++columnCount; + } + + columnGroup.span = columnCount; + + var cell = document.createElement("th"); + cell.className = "corner"; + headerRow.appendChild(cell); + + this._headerTableColumnGroup = columnGroup; + this._headerTable.appendChild(this._headerTableColumnGroup); + this.headerTableBody.appendChild(headerRow); + + var fillerRow = document.createElement("tr"); + fillerRow.className = "filler"; + + for (var i = 0; i < columnCount; ++i) { + var cell = document.createElement("td"); + fillerRow.appendChild(cell); + } + + this._dataTableColumnGroup = columnGroup.cloneNode(true); + this._dataTable.appendChild(this._dataTableColumnGroup); + this.dataTableBody.appendChild(fillerRow); + + this.columns = columns || {}; + this.children = []; + this.selectedNode = null; + this.expandNodesWhenArrowing = false; + this.root = true; + this.hasChildren = false; + this.expanded = true; + this.revealed = true; + this.selected = false; + this.dataGrid = this; + this.indentWidth = 15; + this.resizers = []; + this.columnWidthsInitialized = false; +} + +WebInspector.DataGrid.prototype = { + get sortColumnIdentifier() + { + if (!this._sortColumnCell) + return null; + return this._sortColumnCell.columnIdentifier; + }, + + get sortOrder() + { + if (!this._sortColumnCell || this._sortColumnCell.hasStyleClass("sort-ascending")) + return "ascending"; + if (this._sortColumnCell.hasStyleClass("sort-descending")) + return "descending"; + return null; + }, + + get headerTableBody() + { + if ("_headerTableBody" in this) + return this._headerTableBody; + + this._headerTableBody = this._headerTable.getElementsByTagName("tbody")[0]; + if (!this._headerTableBody) { + this._headerTableBody = this.element.ownerDocument.createElement("tbody"); + this._headerTable.insertBefore(this._headerTableBody, this._headerTable.tFoot); + } + + return this._headerTableBody; + }, + + get dataTableBody() + { + if ("_dataTableBody" in this) + return this._dataTableBody; + + this._dataTableBody = this._dataTable.getElementsByTagName("tbody")[0]; + if (!this._dataTableBody) { + this._dataTableBody = this.element.ownerDocument.createElement("tbody"); + this._dataTable.insertBefore(this._dataTableBody, this._dataTable.tFoot); + } + + return this._dataTableBody; + }, + + // Updates the widths of the table, including the positions of the column + // resizers. + // + // IMPORTANT: This function MUST be called once after the element of the + // DataGrid is attached to its parent element and every subsequent time the + // width of the parent element is changed in order to make it possible to + // resize the columns. + // + // If this function is not called after the DataGrid is attached to its + // parent element, then the DataGrid's columns will not be resizable. + updateWidths: function() + { + var headerTableColumns = this._headerTableColumnGroup.children; + + var left = 0; + var tableWidth = this._dataTable.offsetWidth; + var numColumns = headerTableColumns.length; + + if (!this.columnWidthsInitialized) { + // Give all the columns initial widths now so that during a resize, + // when the two columns that get resized get a percent value for + // their widths, all the other columns already have percent values + // for their widths. + for (var i = 0; i < numColumns; i++) { + var columnWidth = this.headerTableBody.rows[0].cells[i].offsetWidth; + var percentWidth = ((columnWidth / tableWidth) * 100) + "%"; + this._headerTableColumnGroup.children[i].style.width = percentWidth; + this._dataTableColumnGroup.children[i].style.width = percentWidth; + } + this.columnWidthsInitialized = true; + } + + // Make n - 1 resizers for n columns. + for (var i = 0; i < numColumns - 1; i++) { + var resizer = this.resizers[i]; + + if (!resizer) { + // This is the first call to updateWidth, so the resizers need + // to be created. + resizer = document.createElement("div"); + resizer.addStyleClass("data-grid-resizer"); + // This resizer is associated with the column to its right. + resizer.rightNeighboringColumnID = i + 1; + resizer.addEventListener("mousedown", this._startResizerDragging.bind(this), false); + this.element.appendChild(resizer); + this.resizers[i] = resizer; + } + + // Get the width of the cell in the first (and only) row of the + // header table in order to determine the width of the column, since + // it is not possible to query a column for its width. + left += this.headerTableBody.rows[0].cells[i].offsetWidth; + + resizer.style.left = left + "px"; + } + }, + + addCreationNode: function(hasChildren) + { + if (this.creationNode) + this.creationNode.makeNormal(); + + var emptyData = {}; + for (var column in this.columns) + emptyData[column] = ''; + this.creationNode = new WebInspector.CreationDataGridNode(emptyData, hasChildren); + this.appendChild(this.creationNode); + }, + + appendChild: function(child) + { + this.insertChild(child, this.children.length); + }, + + insertChild: function(child, index) + { + if (!child) + throw("insertChild: Node can't be undefined or null."); + if (child.parent === this) + throw("insertChild: Node is already a child of this node."); + + if (child.parent) + child.parent.removeChild(child); + + this.children.splice(index, 0, child); + this.hasChildren = true; + + child.parent = this; + child.dataGrid = this.dataGrid; + child._recalculateSiblings(index); + + delete child._depth; + delete child._revealed; + delete child._attached; + + var current = child.children[0]; + while (current) { + current.dataGrid = this.dataGrid; + delete current._depth; + delete current._revealed; + delete current._attached; + current = current.traverseNextNode(false, child, true); + } + + if (this.expanded) + child._attach(); + }, + + removeChild: function(child) + { + if (!child) + throw("removeChild: Node can't be undefined or null."); + if (child.parent !== this) + throw("removeChild: Node is not a child of this node."); + + child.deselect(); + + this.children.remove(child, true); + + if (child.previousSibling) + child.previousSibling.nextSibling = child.nextSibling; + if (child.nextSibling) + child.nextSibling.previousSibling = child.previousSibling; + + child.dataGrid = null; + child.parent = null; + child.nextSibling = null; + child.previousSibling = null; + + if (this.children.length <= 0) + this.hasChildren = false; + }, + + removeChildren: function() + { + for (var i = 0; i < this.children.length; ++i) { + var child = this.children[i]; + child.deselect(); + child._detach(); + + child.dataGrid = null; + child.parent = null; + child.nextSibling = null; + child.previousSibling = null; + } + + this.children = []; + this.hasChildren = false; + }, + + removeChildrenRecursive: function() + { + var childrenToRemove = this.children; + + var child = this.children[0]; + while (child) { + if (child.children.length) + childrenToRemove = childrenToRemove.concat(child.children); + child = child.traverseNextNode(false, this, true); + } + + for (var i = 0; i < childrenToRemove.length; ++i) { + var child = childrenToRemove[i]; + child.deselect(); + child._detach(); + + child.children = []; + child.dataGrid = null; + child.parent = null; + child.nextSibling = null; + child.previousSibling = null; + } + + this.children = []; + }, + + handleKeyEvent: function(event) + { + if (!this.selectedNode || event.shiftKey || event.metaKey || event.ctrlKey) + return false; + + var handled = false; + var nextSelectedNode; + if (event.keyIdentifier === "Up" && !event.altKey) { + nextSelectedNode = this.selectedNode.traversePreviousNode(true); + while (nextSelectedNode && !nextSelectedNode.selectable) + nextSelectedNode = nextSelectedNode.traversePreviousNode(!this.expandTreeNodesWhenArrowing); + handled = nextSelectedNode ? true : false; + } else if (event.keyIdentifier === "Down" && !event.altKey) { + nextSelectedNode = this.selectedNode.traverseNextNode(true); + while (nextSelectedNode && !nextSelectedNode.selectable) + nextSelectedNode = nextSelectedNode.traverseNextNode(!this.expandTreeNodesWhenArrowing); + handled = nextSelectedNode ? true : false; + } else if (event.keyIdentifier === "Left") { + if (this.selectedNode.expanded) { + if (event.altKey) + this.selectedNode.collapseRecursively(); + else + this.selectedNode.collapse(); + handled = true; + } else if (this.selectedNode.parent && !this.selectedNode.parent.root) { + handled = true; + if (this.selectedNode.parent.selectable) { + nextSelectedNode = this.selectedNode.parent; + handled = nextSelectedNode ? true : false; + } else if (this.selectedNode.parent) + this.selectedNode.parent.collapse(); + } + } else if (event.keyIdentifier === "Right") { + if (!this.selectedNode.revealed) { + this.selectedNode.reveal(); + handled = true; + } else if (this.selectedNode.hasChildren) { + handled = true; + if (this.selectedNode.expanded) { + nextSelectedNode = this.selectedNode.children[0]; + handled = nextSelectedNode ? true : false; + } else { + if (event.altKey) + this.selectedNode.expandRecursively(); + else + this.selectedNode.expand(); + } + } + } + + if (nextSelectedNode) { + nextSelectedNode.reveal(); + nextSelectedNode.select(); + } + + if (handled) { + event.preventDefault(); + event.stopPropagation(); + } + + return handled; + }, + + expand: function() + { + // This is the root, do nothing. + }, + + collapse: function() + { + // This is the root, do nothing. + }, + + reveal: function() + { + // This is the root, do nothing. + }, + + dataGridNodeFromEvent: function(event) + { + var rowElement = event.target.enclosingNodeOrSelfWithNodeName("tr"); + return rowElement._dataGridNode; + }, + + dataGridNodeFromPoint: function(x, y) + { + var node = this._dataTable.ownerDocument.elementFromPoint(x, y); + var rowElement = node.enclosingNodeOrSelfWithNodeName("tr"); + return rowElement._dataGridNode; + }, + + _keyDown: function(event) + { + this.handleKeyEvent(event); + }, + + _clickInHeaderCell: function(event) + { + var cell = event.target.enclosingNodeOrSelfWithNodeName("th"); + if (!cell || !cell.columnIdentifier || !cell.hasStyleClass("sortable")) + return; + + var sortOrder = this.sortOrder; + + if (this._sortColumnCell) { + this._sortColumnCell.removeStyleClass("sort-ascending"); + this._sortColumnCell.removeStyleClass("sort-descending"); + } + + if (cell == this._sortColumnCell) { + if (sortOrder == "ascending") + sortOrder = "descending"; + else + sortOrder = "ascending"; + } + + this._sortColumnCell = cell; + + cell.addStyleClass("sort-" + sortOrder); + + this.dispatchEventToListeners("sorting changed"); + }, + + _mouseDownInDataTable: function(event) + { + var gridNode = this.dataGridNodeFromEvent(event); + if (!gridNode || !gridNode.selectable) + return; + + if (gridNode.isEventWithinDisclosureTriangle(event)) + return; + + if (event.metaKey) { + if (gridNode.selected) + gridNode.deselect(); + else + gridNode.select(); + } else + gridNode.select(); + }, + + _clickInDataTable: function(event) + { + var gridNode = this.dataGridNodeFromEvent(event); + if (!gridNode || !gridNode.hasChildren) + return; + + if (!gridNode.isEventWithinDisclosureTriangle(event)) + return; + + if (gridNode.expanded) { + if (event.altKey) + gridNode.collapseRecursively(); + else + gridNode.collapse(); + } else { + if (event.altKey) + gridNode.expandRecursively(); + else + gridNode.expand(); + } + }, + + _startResizerDragging: function(event) + { + this.currentResizer = event.target; + if (!this.currentResizer.rightNeighboringColumnID) + return; + WebInspector.elementDragStart(this.lastResizer, this._resizerDragging.bind(this), + this._endResizerDragging.bind(this), event, "col-resize"); + }, + + _resizerDragging: function(event) + { + var resizer = this.currentResizer; + if (!resizer) + return; + + // Constrain the dragpoint to be within the containing div of the + // datagrid. + var dragPoint = event.clientX - this.element.totalOffsetLeft; + // Constrain the dragpoint to be within the space made up by the + // column directly to the left and the column directly to the right. + var leftEdgeOfPreviousColumn = 0; + var firstRowCells = this.headerTableBody.rows[0].cells; + for (var i = 0; i < resizer.rightNeighboringColumnID - 1; i++) + leftEdgeOfPreviousColumn += firstRowCells[i].offsetWidth; + + var rightEdgeOfNextColumn = leftEdgeOfPreviousColumn + firstRowCells[resizer.rightNeighboringColumnID - 1].offsetWidth + firstRowCells[resizer.rightNeighboringColumnID].offsetWidth; + + // Give each column some padding so that they don't disappear. + var leftMinimum = leftEdgeOfPreviousColumn + this.ColumnResizePadding; + var rightMaximum = rightEdgeOfNextColumn - this.ColumnResizePadding; + + dragPoint = Number.constrain(dragPoint, leftMinimum, rightMaximum); + + resizer.style.left = (dragPoint - this.CenterResizerOverBorderAdjustment) + "px"; + + var percentLeftColumn = (((dragPoint - leftEdgeOfPreviousColumn) / this._dataTable.offsetWidth) * 100) + "%"; + this._headerTableColumnGroup.children[resizer.rightNeighboringColumnID - 1].style.width = percentLeftColumn; + this._dataTableColumnGroup.children[resizer.rightNeighboringColumnID - 1].style.width = percentLeftColumn; + + var percentRightColumn = (((rightEdgeOfNextColumn - dragPoint) / this._dataTable.offsetWidth) * 100) + "%"; + this._headerTableColumnGroup.children[resizer.rightNeighboringColumnID].style.width = percentRightColumn; + this._dataTableColumnGroup.children[resizer.rightNeighboringColumnID].style.width = percentRightColumn; + + event.preventDefault(); + }, + + _endResizerDragging: function(event) + { + WebInspector.elementDragEnd(event); + this.currentResizer = null; + }, + + ColumnResizePadding: 10, + + CenterResizerOverBorderAdjustment: 3, +} + +WebInspector.DataGrid.prototype.__proto__ = WebInspector.Object.prototype; + +WebInspector.DataGridNode = function(data, hasChildren) +{ + this._expanded = false; + this._selected = false; + this._shouldRefreshChildren = true; + this._data = data || {}; + this.hasChildren = hasChildren || false; + this.children = []; + this.dataGrid = null; + this.parent = null; + this.previousSibling = null; + this.nextSibling = null; + this.disclosureToggleWidth = 10; +} + +WebInspector.DataGridNode.prototype = { + selectable: true, + + get element() + { + if (this._element) + return this._element; + + if (!this.dataGrid) + return null; + + this._element = document.createElement("tr"); + this._element._dataGridNode = this; + + if (this.hasChildren) + this._element.addStyleClass("parent"); + if (this.expanded) + this._element.addStyleClass("expanded"); + if (this.selected) + this._element.addStyleClass("selected"); + if (this.revealed) + this._element.addStyleClass("revealed"); + + for (var columnIdentifier in this.dataGrid.columns) { + var cell = this.createCell(columnIdentifier); + this._element.appendChild(cell); + } + + return this._element; + }, + + get data() + { + return this._data; + }, + + set data(x) + { + this._data = x || {}; + this.refresh(); + }, + + get revealed() + { + if ("_revealed" in this) + return this._revealed; + + var currentAncestor = this.parent; + while (currentAncestor && !currentAncestor.root) { + if (!currentAncestor.expanded) { + this._revealed = false; + return false; + } + + currentAncestor = currentAncestor.parent; + } + + this._revealed = true; + return true; + }, + + set hasChildren(x) + { + if (this._hasChildren === x) + return; + + this._hasChildren = x; + + if (!this._element) + return; + + if (this._hasChildren) + { + this._element.addStyleClass("parent"); + if (this.expanded) + this._element.addStyleClass("expanded"); + } + else + { + this._element.removeStyleClass("parent"); + this._element.removeStyleClass("expanded"); + } + }, + + get hasChildren() + { + return this._hasChildren; + }, + + set revealed(x) + { + if (this._revealed === x) + return; + + this._revealed = x; + + if (this._element) { + if (this._revealed) + this._element.addStyleClass("revealed"); + else + this._element.removeStyleClass("revealed"); + } + + for (var i = 0; i < this.children.length; ++i) + this.children[i].revealed = x && this.expanded; + }, + + get depth() + { + if ("_depth" in this) + return this._depth; + if (this.parent && !this.parent.root) + this._depth = this.parent.depth + 1; + else + this._depth = 0; + return this._depth; + }, + + get shouldRefreshChildren() + { + return this._shouldRefreshChildren; + }, + + set shouldRefreshChildren(x) + { + this._shouldRefreshChildren = x; + if (x && this.expanded) + this.expand(); + }, + + get selected() + { + return this._selected; + }, + + set selected(x) + { + if (x) + this.select(); + else + this.deselect(); + }, + + get expanded() + { + return this._expanded; + }, + + set expanded(x) + { + if (x) + this.expand(); + else + this.collapse(); + }, + + refresh: function() + { + if (!this._element || !this.dataGrid) + return; + + this._element.removeChildren(); + + for (var columnIdentifier in this.dataGrid.columns) { + var cell = this.createCell(columnIdentifier); + this._element.appendChild(cell); + } + }, + + createCell: function(columnIdentifier) + { + var cell = document.createElement("td"); + cell.className = columnIdentifier + "-column"; + + var alignment = this.dataGrid.aligned[columnIdentifier]; + if (alignment) + cell.addStyleClass(alignment); + + var div = document.createElement("div"); + div.textContent = this.data[columnIdentifier]; + cell.appendChild(div); + + if (columnIdentifier === this.dataGrid.disclosureColumnIdentifier) { + cell.addStyleClass("disclosure"); + if (this.depth) + cell.style.setProperty("padding-left", (this.depth * this.dataGrid.indentWidth) + "px"); + } + + return cell; + }, + + // Share these functions with DataGrid. They are written to work with a DataGridNode this object. + appendChild: WebInspector.DataGrid.prototype.appendChild, + insertChild: WebInspector.DataGrid.prototype.insertChild, + removeChild: WebInspector.DataGrid.prototype.removeChild, + removeChildren: WebInspector.DataGrid.prototype.removeChildren, + removeChildrenRecursive: WebInspector.DataGrid.prototype.removeChildrenRecursive, + + _recalculateSiblings: function(myIndex) + { + if (!this.parent) + return; + + var previousChild = (myIndex > 0 ? this.parent.children[myIndex - 1] : null); + + if (previousChild) { + previousChild.nextSibling = this; + this.previousSibling = previousChild; + } else + this.previousSibling = null; + + var nextChild = this.parent.children[myIndex + 1]; + + if (nextChild) { + nextChild.previousSibling = this; + this.nextSibling = nextChild; + } else + this.nextSibling = null; + }, + + collapse: function() + { + if (this._element) + this._element.removeStyleClass("expanded"); + + this._expanded = false; + + for (var i = 0; i < this.children.length; ++i) + this.children[i].revealed = false; + + this.dispatchEventToListeners("collapsed"); + }, + + collapseRecursively: function() + { + var item = this; + while (item) { + if (item.expanded) + item.collapse(); + item = item.traverseNextNode(false, this, true); + } + }, + + expand: function() + { + if (!this.hasChildren || this.expanded) + return; + + if (this.revealed && !this._shouldRefreshChildren) + for (var i = 0; i < this.children.length; ++i) + this.children[i].revealed = true; + + if (this._shouldRefreshChildren) { + for (var i = 0; i < this.children.length; ++i) + this.children[i]._detach(); + + this.dispatchEventToListeners("populate"); + + if (this._attached) { + for (var i = 0; i < this.children.length; ++i) { + var child = this.children[i]; + if (this.revealed) + child.revealed = true; + child._attach(); + } + } + + delete this._shouldRefreshChildren; + } + + if (this._element) + this._element.addStyleClass("expanded"); + + this._expanded = true; + + this.dispatchEventToListeners("expanded"); + }, + + expandRecursively: function() + { + var item = this; + while (item) { + item.expand(); + item = item.traverseNextNode(false, this); + } + }, + + reveal: function() + { + var currentAncestor = this.parent; + while (currentAncestor && !currentAncestor.root) { + if (!currentAncestor.expanded) + currentAncestor.expand(); + currentAncestor = currentAncestor.parent; + } + + this.element.scrollIntoViewIfNeeded(false); + + this.dispatchEventToListeners("revealed"); + }, + + select: function(supressSelectedEvent) + { + if (!this.dataGrid || !this.selectable || this.selected) + return; + + if (this.dataGrid.selectedNode) + this.dataGrid.selectedNode.deselect(); + + this._selected = true; + this.dataGrid.selectedNode = this; + + if (this._element) + this._element.addStyleClass("selected"); + + if (!supressSelectedEvent) + this.dispatchEventToListeners("selected"); + }, + + deselect: function(supressDeselectedEvent) + { + if (!this.dataGrid || this.dataGrid.selectedNode !== this || !this.selected) + return; + + this._selected = false; + this.dataGrid.selectedNode = null; + + if (this._element) + this._element.removeStyleClass("selected"); + + if (!supressDeselectedEvent) + this.dispatchEventToListeners("deselected"); + }, + + traverseNextNode: function(skipHidden, stayWithin, dontPopulate, info) + { + if (!dontPopulate && this.hasChildren) + this.dispatchEventToListeners("populate"); + + if (info) + info.depthChange = 0; + + var node = (!skipHidden || this.revealed) ? this.children[0] : null; + if (node && (!skipHidden || this.expanded)) { + if (info) + info.depthChange = 1; + return node; + } + + if (this === stayWithin) + return null; + + node = (!skipHidden || this.revealed) ? this.nextSibling : null; + if (node) + return node; + + node = this; + while (node && !node.root && !((!skipHidden || node.revealed) ? node.nextSibling : null) && node.parent !== stayWithin) { + if (info) + info.depthChange -= 1; + node = node.parent; + } + + if (!node) + return null; + + return (!skipHidden || node.revealed) ? node.nextSibling : null; + }, + + traversePreviousNode: function(skipHidden, dontPopulate) + { + var node = (!skipHidden || this.revealed) ? this.previousSibling : null; + if (!dontPopulate && node && node.hasChildren) + node.dispatchEventToListeners("populate"); + + while (node && ((!skipHidden || (node.revealed && node.expanded)) ? node.children[node.children.length - 1] : null)) { + if (!dontPopulate && node.hasChildren) + node.dispatchEventToListeners("populate"); + node = ((!skipHidden || (node.revealed && node.expanded)) ? node.children[node.children.length - 1] : null); + } + + if (node) + return node; + + if (!this.parent || this.parent.root) + return null; + + return this.parent; + }, + + isEventWithinDisclosureTriangle: function(event) + { + if (!this.hasChildren) + return false; + var cell = event.target.enclosingNodeOrSelfWithNodeName("td"); + if (!cell.hasStyleClass("disclosure")) + return false; + var computedLeftPadding = window.getComputedStyle(cell).getPropertyCSSValue("padding-left").getFloatValue(CSSPrimitiveValue.CSS_PX); + var left = cell.totalOffsetLeft + computedLeftPadding; + return event.pageX >= left && event.pageX <= left + this.disclosureToggleWidth; + }, + + _attach: function() + { + if (!this.dataGrid || this._attached) + return; + + this._attached = true; + + var nextNode = null; + var previousNode = this.traversePreviousNode(true, true); + if (previousNode && previousNode.element.parentNode && previousNode.element.nextSibling) + var nextNode = previousNode.element.nextSibling; + if (!nextNode) + nextNode = this.dataGrid.dataTableBody.lastChild; + this.dataGrid.dataTableBody.insertBefore(this.element, nextNode); + + if (this.expanded) + for (var i = 0; i < this.children.length; ++i) + this.children[i]._attach(); + }, + + _detach: function() + { + if (!this._attached) + return; + + this._attached = false; + + if (this._element && this._element.parentNode) + this._element.parentNode.removeChild(this._element); + + for (var i = 0; i < this.children.length; ++i) + this.children[i]._detach(); + } +} + +WebInspector.DataGridNode.prototype.__proto__ = WebInspector.Object.prototype; + +WebInspector.CreationDataGridNode = function(data, hasChildren) +{ + WebInspector.DataGridNode.call(this, data, hasChildren); + this.isCreationNode = true; +} + +WebInspector.CreationDataGridNode.prototype = { + makeNormal: function() + { + delete this.isCreationNode; + delete this.makeNormal; + } +} + +WebInspector.CreationDataGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Database.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Database.js new file mode 100644 index 0000000..dcab2ab --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Database.js @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.Database = function(database, domain, name, version) +{ + this._database = database; + this.domain = domain; + this.name = name; + this.version = version; +} + +WebInspector.Database.prototype = { + isDatabase: function(db) + { + return this._database === db; + }, + + get name() + { + return this._name; + }, + + set name(x) + { + if (this._name === x) + return; + this._name = x; + }, + + get version() + { + return this._version; + }, + + set version(x) + { + if (this._version === x) + return; + this._version = x; + }, + + get domain() + { + return this._domain; + }, + + set domain(x) + { + if (this._domain === x) + return; + this._domain = x; + }, + + get displayDomain() + { + return WebInspector.Resource.prototype.__lookupGetter__("displayDomain").call(this); + }, + + getTableNames: function(callback) + { + var names = InspectorController.databaseTableNames(this._database); + function sortingCallback() + { + callback(names.sort()); + } + setTimeout(sortingCallback, 0); + }, + + executeSql: function(query, onSuccess, onError) + { + function successCallback(tx, result) + { + onSuccess(result); + } + + function errorCallback(tx, error) + { + onError(error); + } + + var self = this; + function queryTransaction(tx) + { + tx.executeSql(query, null, InspectorController.wrapCallback(successCallback.bind(self)), InspectorController.wrapCallback(errorCallback.bind(self))); + } + this._database.transaction(InspectorController.wrapCallback(queryTransaction.bind(this)), InspectorController.wrapCallback(errorCallback.bind(this))); + } +} diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/DatabaseQueryView.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/DatabaseQueryView.js new file mode 100644 index 0000000..6c5fa02 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/DatabaseQueryView.js @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.DatabaseQueryView = function(database) +{ + WebInspector.View.call(this); + + this.database = database; + + this.element.addStyleClass("storage-view"); + this.element.addStyleClass("query"); + this.element.tabIndex = 0; + + this.element.addEventListener("selectstart", this._selectStart.bind(this), false); + + this.promptElement = document.createElement("div"); + this.promptElement.className = "database-query-prompt"; + this.promptElement.appendChild(document.createElement("br")); + this.promptElement.handleKeyEvent = this._promptKeyDown.bind(this); + this.element.appendChild(this.promptElement); + + this.prompt = new WebInspector.TextPrompt(this.promptElement, this.completions.bind(this), " "); +} + +WebInspector.DatabaseQueryView.prototype = { + show: function(parentElement) + { + WebInspector.View.prototype.show.call(this, parentElement); + + function moveBackIfOutside() + { + if (!this.prompt.isCaretInsidePrompt() && window.getSelection().isCollapsed) + this.prompt.moveCaretToEndOfPrompt(); + } + + setTimeout(moveBackIfOutside.bind(this), 0); + }, + + completions: function(wordRange, bestMatchOnly, completionsReadyCallback) + { + var prefix = wordRange.toString().toLowerCase(); + if (!prefix.length) + return; + + var results = []; + + function accumulateMatches(textArray) + { + if (bestMatchOnly && results.length) + return; + for (var i = 0; i < textArray.length; ++i) { + var text = textArray[i].toLowerCase(); + if (text.length < prefix.length) + continue; + if (text.indexOf(prefix) !== 0) + continue; + results.push(textArray[i]); + if (bestMatchOnly) + return; + } + } + + function tableNamesCallback(tableNames) + { + accumulateMatches(tableNames.map(function(name) { return name + " " })); + accumulateMatches(["SELECT ", "FROM ", "WHERE ", "LIMIT ", "DELETE FROM ", "CREATE ", "DROP ", "TABLE ", "INDEX ", "UPDATE ", "INSERT INTO ", "VALUES ("]); + + completionsReadyCallback(results); + } + this.database.getTableNames(tableNamesCallback); + }, + + _promptKeyDown: function(event) + { + switch (event.keyIdentifier) { + case "Enter": + this._enterKeyPressed(event); + return; + } + + this.prompt.handleKeyEvent(event); + }, + + _selectStart: function(event) + { + if (this._selectionTimeout) + clearTimeout(this._selectionTimeout); + + this.prompt.clearAutoComplete(); + + function moveBackIfOutside() + { + delete this._selectionTimeout; + if (!this.prompt.isCaretInsidePrompt() && window.getSelection().isCollapsed) + this.prompt.moveCaretToEndOfPrompt(); + this.prompt.autoCompleteSoon(); + } + + this._selectionTimeout = setTimeout(moveBackIfOutside.bind(this), 100); + }, + + _enterKeyPressed: function(event) + { + event.preventDefault(); + event.stopPropagation(); + + this.prompt.clearAutoComplete(true); + + var query = this.prompt.text; + if (!query.length) + return; + + this.prompt.history.push(query); + this.prompt.historyOffset = 0; + this.prompt.text = ""; + + this.database.executeSql(query, this._queryFinished.bind(this, query), this._queryError.bind(this, query)); + }, + + _queryFinished: function(query, result) + { + var dataGrid = WebInspector.panels.storage.dataGridForResult(result); + if (!dataGrid) + return; + dataGrid.element.addStyleClass("inline"); + this._appendQueryResult(query, dataGrid.element); + + if (query.match(/^create /i) || query.match(/^drop table /i)) + WebInspector.panels.storage.updateDatabaseTables(this.database); + }, + + _queryError: function(query, error) + { + if (error.code == 1) + var message = error.message; + else if (error.code == 2) + var message = WebInspector.UIString("Database no longer has expected version."); + else + var message = WebInspector.UIString("An unexpected error %s occurred.", error.code); + + this._appendQueryResult(query, message, "error"); + }, + + _appendQueryResult: function(query, result, resultClassName) + { + var element = document.createElement("div"); + element.className = "database-user-query"; + + var commandTextElement = document.createElement("span"); + commandTextElement.className = "database-query-text"; + commandTextElement.textContent = query; + element.appendChild(commandTextElement); + + var resultElement = document.createElement("div"); + resultElement.className = "database-query-result"; + + if (resultClassName) + resultElement.addStyleClass(resultClassName); + + if (typeof result === "string" || result instanceof String) + resultElement.textContent = result; + else if (result && result.nodeName) + resultElement.appendChild(result); + + if (resultElement.childNodes.length) + element.appendChild(resultElement); + + this.element.insertBefore(element, this.promptElement); + this.promptElement.scrollIntoView(false); + } +} + +WebInspector.DatabaseQueryView.prototype.__proto__ = WebInspector.View.prototype; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/DatabaseTableView.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/DatabaseTableView.js new file mode 100644 index 0000000..aa76794f --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/DatabaseTableView.js @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.DatabaseTableView = function(database, tableName) +{ + WebInspector.View.call(this); + + this.database = database; + this.tableName = tableName; + + this.element.addStyleClass("storage-view"); + this.element.addStyleClass("table"); + + this.refreshButton = new WebInspector.StatusBarButton(WebInspector.UIString("Refresh"), "refresh-storage-status-bar-item"); + this.refreshButton.addEventListener("click", this._refreshButtonClicked.bind(this), false); +} + +WebInspector.DatabaseTableView.prototype = { + show: function(parentElement) + { + WebInspector.View.prototype.show.call(this, parentElement); + this.update(); + }, + + get statusBarItems() + { + return [this.refreshButton]; + }, + + update: function() + { + this.database.executeSql("SELECT * FROM " + this.tableName, this._queryFinished.bind(this), this._queryError.bind(this)); + }, + + _queryFinished: function(result) + { + this.element.removeChildren(); + + var dataGrid = WebInspector.panels.storage.dataGridForResult(result); + if (!dataGrid) { + var emptyMsgElement = document.createElement("div"); + emptyMsgElement.className = "storage-table-empty"; + emptyMsgElement.textContent = WebInspector.UIString("The “%sâ€\ntable is empty.", this.tableName); + this.element.appendChild(emptyMsgElement); + return; + } + + this.element.appendChild(dataGrid.element); + }, + + _queryError: function(error) + { + this.element.removeChildren(); + + var errorMsgElement = document.createElement("div"); + errorMsgElement.className = "storage-table-error"; + errorMsgElement.textContent = WebInspector.UIString("An error occurred trying to\nread the “%s†table.", this.tableName); + this.element.appendChild(errorMsgElement); + }, + + _refreshButtonClicked: function(event) + { + this.update(); + } +} + +WebInspector.DatabaseTableView.prototype.__proto__ = WebInspector.View.prototype; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Drawer.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Drawer.js new file mode 100644 index 0000000..1b50f91 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Drawer.js @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2009 Joseph Pecoraro + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.Drawer = function() +{ + WebInspector.View.call(this, document.getElementById("drawer")); + + this.mainStatusBar = document.getElementById("main-status-bar"); + this.mainStatusBar.addEventListener("mousedown", this._startStatusBarDragging.bind(this), true); + this.viewStatusBar = document.getElementById("other-drawer-status-bar-items"); +} + +WebInspector.Drawer.prototype = { + get visibleView() + { + return this._visibleView; + }, + + set visibleView(x) + { + if (this._visibleView === x) { + this.visible = !this.visible; + return; + } + + var firstTime = !this._visibleView; + if (this._visibleView) + this._visibleView.hide(); + + this._visibleView = x; + + if (x && !firstTime) { + this._safelyRemoveChildren(); + this.viewStatusBar.removeChildren(); // optimize this? call old.detach() + x.attach(this.element, this.viewStatusBar); + x.show(); + this.visible = true; + } + }, + + showView: function(view) + { + if (!this.visible || this.visibleView !== view) + this.visibleView = view; + }, + + show: function() + { + if (this._animating || this.visible) + return; + + if (this.visibleView) + this.visibleView.show(); + + WebInspector.View.prototype.show.call(this); + + this._animating = true; + + document.body.addStyleClass("drawer-visible"); + + var anchoredItems = document.getElementById("anchored-status-bar-items"); + + var animations = [ + {element: document.getElementById("main"), end: {bottom: this.element.offsetHeight}}, + {element: document.getElementById("main-status-bar"), start: {"padding-left": anchoredItems.offsetWidth - 1}, end: {"padding-left": 0}}, + {element: document.getElementById("other-drawer-status-bar-items"), start: {opacity: 0}, end: {opacity: 1}} + ]; + + var consoleStatusBar = document.getElementById("drawer-status-bar"); + consoleStatusBar.insertBefore(anchoredItems, consoleStatusBar.firstChild); + + function animationFinished() + { + if ("updateStatusBarItems" in WebInspector.currentPanel) + WebInspector.currentPanel.updateStatusBarItems(); + if (this.visibleView.afterShow) + this.visibleView.afterShow(); + delete this._animating; + } + + WebInspector.animateStyle(animations, window.event && window.event.shiftKey ? 2000 : 250, animationFinished.bind(this)); + }, + + hide: function() + { + if (this._animating || !this.visible) + return; + + WebInspector.View.prototype.hide.call(this); + + if (this.visibleView) + this.visibleView.hide(); + + this._animating = true; + + if (this.element === WebInspector.currentFocusElement || this.element.isAncestor(WebInspector.currentFocusElement)) + WebInspector.currentFocusElement = WebInspector.previousFocusElement; + + var anchoredItems = document.getElementById("anchored-status-bar-items"); + + // Temporally set properties and classes to mimic the post-animation values so panels + // like Elements in their updateStatusBarItems call will size things to fit the final location. + document.getElementById("main-status-bar").style.setProperty("padding-left", (anchoredItems.offsetWidth - 1) + "px"); + document.body.removeStyleClass("drawer-visible"); + if ("updateStatusBarItems" in WebInspector.currentPanel) + WebInspector.currentPanel.updateStatusBarItems(); + document.body.addStyleClass("drawer-visible"); + + var animations = [ + {element: document.getElementById("main"), end: {bottom: 0}}, + {element: document.getElementById("main-status-bar"), start: {"padding-left": 0}, end: {"padding-left": anchoredItems.offsetWidth - 1}}, + {element: document.getElementById("other-drawer-status-bar-items"), start: {opacity: 1}, end: {opacity: 0}} + ]; + + function animationFinished() + { + var mainStatusBar = document.getElementById("main-status-bar"); + mainStatusBar.insertBefore(anchoredItems, mainStatusBar.firstChild); + mainStatusBar.style.removeProperty("padding-left"); + document.body.removeStyleClass("drawer-visible"); + delete this._animating; + } + + WebInspector.animateStyle(animations, window.event && window.event.shiftKey ? 2000 : 250, animationFinished.bind(this)); + }, + + _safelyRemoveChildren: function() + { + var child = this.element.firstChild; + while (child) { + if (child.id !== "drawer-status-bar") { + var moveTo = child.nextSibling; + this.element.removeChild(child); + child = moveTo; + } else + child = child.nextSibling; + } + }, + + _startStatusBarDragging: function(event) + { + if (!this.visible || event.target !== document.getElementById("main-status-bar")) + return; + + WebInspector.elementDragStart(document.getElementById("main-status-bar"), this._statusBarDragging.bind(this), this._endStatusBarDragging.bind(this), event, "row-resize"); + + this._statusBarDragOffset = event.pageY - this.element.totalOffsetTop; + + event.stopPropagation(); + }, + + _statusBarDragging: function(event) + { + var mainElement = document.getElementById("main"); + + var height = window.innerHeight - event.pageY + this._statusBarDragOffset; + height = Number.constrain(height, Preferences.minConsoleHeight, window.innerHeight - mainElement.totalOffsetTop - Preferences.minConsoleHeight); + + mainElement.style.bottom = height + "px"; + this.element.style.height = height + "px"; + + event.preventDefault(); + event.stopPropagation(); + }, + + _endStatusBarDragging: function(event) + { + WebInspector.elementDragEnd(event); + + delete this._statusBarDragOffset; + + event.stopPropagation(); + } +} + +WebInspector.Drawer.prototype.__proto__ = WebInspector.View.prototype; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ElementsPanel.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ElementsPanel.js new file mode 100644 index 0000000..49a1188 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ElementsPanel.js @@ -0,0 +1,1045 @@ +/* + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2008 Matt Lilek <webkit@mattlilek.com> + * Copyright (C) 2009 Joseph Pecoraro + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.ElementsPanel = function() +{ + WebInspector.Panel.call(this); + + this.element.addStyleClass("elements"); + + this.contentElement = document.createElement("div"); + this.contentElement.id = "elements-content"; + this.contentElement.className = "outline-disclosure"; + + this.treeOutline = new WebInspector.ElementsTreeOutline(); + this.treeOutline.panel = this; + this.treeOutline.includeRootDOMNode = false; + this.treeOutline.selectEnabled = true; + + this.treeOutline.focusedNodeChanged = function(forceUpdate) + { + if (this.panel.visible && WebInspector.currentFocusElement !== document.getElementById("search")) + WebInspector.currentFocusElement = document.getElementById("main-panels"); + + this.panel.updateBreadcrumb(forceUpdate); + + for (var pane in this.panel.sidebarPanes) + this.panel.sidebarPanes[pane].needsUpdate = true; + + this.panel.updateStyles(true); + this.panel.updateMetrics(); + this.panel.updateProperties(); + + if (InspectorController.searchingForNode()) { + InspectorController.toggleNodeSearch(); + this.panel.nodeSearchButton.removeStyleClass("toggled-on"); + } + if (this._focusedDOMNode) + InjectedScriptAccess.addInspectedNode(this._focusedDOMNode.id, function() {}); + }; + + this.contentElement.appendChild(this.treeOutline.element); + + this.crumbsElement = document.createElement("div"); + this.crumbsElement.className = "crumbs"; + this.crumbsElement.addEventListener("mousemove", this._mouseMovedInCrumbs.bind(this), false); + this.crumbsElement.addEventListener("mouseout", this._mouseMovedOutOfCrumbs.bind(this), false); + + this.sidebarPanes = {}; + this.sidebarPanes.styles = new WebInspector.StylesSidebarPane(); + this.sidebarPanes.metrics = new WebInspector.MetricsSidebarPane(); + this.sidebarPanes.properties = new WebInspector.PropertiesSidebarPane(); + + this.sidebarPanes.styles.onexpand = this.updateStyles.bind(this); + this.sidebarPanes.metrics.onexpand = this.updateMetrics.bind(this); + this.sidebarPanes.properties.onexpand = this.updateProperties.bind(this); + + this.sidebarPanes.styles.expanded = true; + + this.sidebarPanes.styles.addEventListener("style edited", this._stylesPaneEdited, this); + this.sidebarPanes.styles.addEventListener("style property toggled", this._stylesPaneEdited, this); + this.sidebarPanes.metrics.addEventListener("metrics edited", this._metricsPaneEdited, this); + + this.sidebarElement = document.createElement("div"); + this.sidebarElement.id = "elements-sidebar"; + + this.sidebarElement.appendChild(this.sidebarPanes.styles.element); + this.sidebarElement.appendChild(this.sidebarPanes.metrics.element); + this.sidebarElement.appendChild(this.sidebarPanes.properties.element); + + this.sidebarResizeElement = document.createElement("div"); + this.sidebarResizeElement.className = "sidebar-resizer-vertical"; + this.sidebarResizeElement.addEventListener("mousedown", this.rightSidebarResizerDragStart.bind(this), false); + + this.nodeSearchButton = new WebInspector.StatusBarButton(WebInspector.UIString("Select an element in the page to inspect it."), "node-search-status-bar-item"); + this.nodeSearchButton.addEventListener("click", this._nodeSearchButtonClicked.bind(this), false); + + this.searchingForNode = false; + + this.element.appendChild(this.contentElement); + this.element.appendChild(this.sidebarElement); + this.element.appendChild(this.sidebarResizeElement); + + this._changedStyles = {}; + + this.reset(); +} + +WebInspector.ElementsPanel.prototype = { + toolbarItemClass: "elements", + + get toolbarItemLabel() + { + return WebInspector.UIString("Elements"); + }, + + get statusBarItems() + { + return [this.nodeSearchButton.element, this.crumbsElement]; + }, + + updateStatusBarItems: function() + { + this.updateBreadcrumbSizes(); + }, + + show: function() + { + WebInspector.Panel.prototype.show.call(this); + this.sidebarResizeElement.style.right = (this.sidebarElement.offsetWidth - 3) + "px"; + this.updateBreadcrumb(); + this.treeOutline.updateSelection(); + if (this.recentlyModifiedNodes.length) + this._updateModifiedNodes(); + }, + + hide: function() + { + WebInspector.Panel.prototype.hide.call(this); + + WebInspector.hoveredDOMNode = null; + + if (InspectorController.searchingForNode()) { + InspectorController.toggleNodeSearch(); + this.nodeSearchButton.toggled = false; + } + }, + + resize: function() + { + this.treeOutline.updateSelection(); + this.updateBreadcrumbSizes(); + }, + + reset: function() + { + this.rootDOMNode = null; + this.focusedDOMNode = null; + + WebInspector.hoveredDOMNode = null; + + if (InspectorController.searchingForNode()) { + InspectorController.toggleNodeSearch(); + this.nodeSearchButton.toggled = false; + } + + this.recentlyModifiedNodes = []; + + delete this.currentQuery; + this.searchCanceled(); + + var domWindow = WebInspector.domAgent.domWindow; + if (!domWindow || !domWindow.document || !domWindow.document.firstChild) + return; + + // If the window isn't visible, return early so the DOM tree isn't built + // and mutation event listeners are not added. + if (!InspectorController.isWindowVisible()) + return; + + var inspectedRootDocument = domWindow.document; + inspectedRootDocument.addEventListener("DOMNodeInserted", this._nodeInserted.bind(this)); + inspectedRootDocument.addEventListener("DOMNodeRemoved", this._nodeRemoved.bind(this)); + + this.rootDOMNode = inspectedRootDocument; + + var canidateFocusNode = inspectedRootDocument.body || inspectedRootDocument.documentElement; + if (canidateFocusNode) { + this.treeOutline.suppressSelectHighlight = true; + this.focusedDOMNode = canidateFocusNode; + this.treeOutline.suppressSelectHighlight = false; + + if (this.treeOutline.selectedTreeElement) + this.treeOutline.selectedTreeElement.expand(); + } + }, + + searchCanceled: function() + { + if (this._searchResults) { + for (var i = 0; i < this._searchResults.length; ++i) { + var treeElement = this.treeOutline.findTreeElement(this._searchResults[i]); + if (treeElement) + treeElement.highlighted = false; + } + } + + WebInspector.updateSearchMatchesCount(0, this); + + this._currentSearchResultIndex = 0; + this._searchResults = []; + InjectedScriptAccess.searchCanceled(function() {}); + }, + + performSearch: function(query) + { + // Call searchCanceled since it will reset everything we need before doing a new search. + this.searchCanceled(); + + const whitespaceTrimmedQuery = query.trimWhitespace(); + if (!whitespaceTrimmedQuery.length) + return; + + this._updatedMatchCountOnce = false; + this._matchesCountUpdateTimeout = null; + + InjectedScriptAccess.performSearch(whitespaceTrimmedQuery, function() {}); + }, + + _updateMatchesCount: function() + { + WebInspector.updateSearchMatchesCount(this._searchResults.length, this); + this._matchesCountUpdateTimeout = null; + this._updatedMatchCountOnce = true; + }, + + _updateMatchesCountSoon: function() + { + if (!this._updatedMatchCountOnce) + return this._updateMatchesCount(); + if (this._matchesCountUpdateTimeout) + return; + // Update the matches count every half-second so it doesn't feel twitchy. + this._matchesCountUpdateTimeout = setTimeout(this._updateMatchesCount.bind(this), 500); + }, + + addNodesToSearchResult: function(nodeIds) + { + if (!nodeIds) + return; + + var nodeIdsArray = nodeIds.split(","); + for (var i = 0; i < nodeIdsArray.length; ++i) { + var nodeId = nodeIdsArray[i]; + var node = WebInspector.domAgent.nodeForId(nodeId); + if (!node) + continue; + + if (!this._searchResults.length) { + this._currentSearchResultIndex = 0; + this.focusedDOMNode = node; + } + + this._searchResults.push(node); + + // Highlight the tree element to show it matched the search. + // FIXME: highlight the substrings in text nodes and attributes. + var treeElement = this.treeOutline.findTreeElement(node); + if (treeElement) + treeElement.highlighted = true; + } + + this._updateMatchesCountSoon(); + }, + + jumpToNextSearchResult: function() + { + if (!this._searchResults || !this._searchResults.length) + return; + if (++this._currentSearchResultIndex >= this._searchResults.length) + this._currentSearchResultIndex = 0; + this.focusedDOMNode = this._searchResults[this._currentSearchResultIndex]; + }, + + jumpToPreviousSearchResult: function() + { + if (!this._searchResults || !this._searchResults.length) + return; + if (--this._currentSearchResultIndex < 0) + this._currentSearchResultIndex = (this._searchResults.length - 1); + this.focusedDOMNode = this._searchResults[this._currentSearchResultIndex]; + }, + + renameSelector: function(oldIdentifier, newIdentifier, oldSelector, newSelector) + { + // TODO: Implement Shifting the oldSelector, and its contents to a newSelector + }, + + addStyleChange: function(identifier, style, property) + { + if (!style.parentRule) + return; + + var selector = style.parentRule.selectorText; + if (!this._changedStyles[identifier]) + this._changedStyles[identifier] = {}; + + if (!this._changedStyles[identifier][selector]) + this._changedStyles[identifier][selector] = {}; + + if (!this._changedStyles[identifier][selector][property]) + WebInspector.styleChanges += 1; + + this._changedStyles[identifier][selector][property] = style.getPropertyValue(property); + }, + + removeStyleChange: function(identifier, style, property) + { + if (!style.parentRule) + return; + + var selector = style.parentRule.selectorText; + if (!this._changedStyles[identifier] || !this._changedStyles[identifier][selector]) + return; + + if (this._changedStyles[identifier][selector][property]) { + delete this._changedStyles[identifier][selector][property]; + WebInspector.styleChanges -= 1; + } + }, + + generateStylesheet: function() + { + if (!WebInspector.styleChanges) + return; + + // Merge Down to Just Selectors + var mergedSelectors = {}; + for (var identifier in this._changedStyles) { + for (var selector in this._changedStyles[identifier]) { + if (!mergedSelectors[selector]) + mergedSelectors[selector] = this._changedStyles[identifier][selector]; + else { // merge on selector + var merge = {}; + for (var property in mergedSelectors[selector]) + merge[property] = mergedSelectors[selector][property]; + for (var property in this._changedStyles[identifier][selector]) { + if (!merge[property]) + merge[property] = this._changedStyles[identifier][selector][property]; + else { // merge on property within a selector, include comment to notify user + var value1 = merge[property]; + var value2 = this._changedStyles[identifier][selector][property]; + + if (value1 === value2) + merge[property] = [value1]; + else if (value1 instanceof Array) + merge[property].push(value2); + else + merge[property] = [value1, value2]; + } + } + mergedSelectors[selector] = merge; + } + } + } + + var builder = []; + builder.push("/**"); + builder.push(" * Inspector Generated Stylesheet"); // UIString? + builder.push(" */\n"); + + var indent = " "; + function displayProperty(property, value, comment) { + if (comment) + return indent + "/* " + property + ": " + value + "; */"; + else + return indent + property + ": " + value + ";"; + } + + for (var selector in mergedSelectors) { + var psuedoStyle = mergedSelectors[selector]; + var properties = Object.properties(psuedoStyle); + if (properties.length) { + builder.push(selector + " {"); + for (var i = 0; i < properties.length; ++i) { + var property = properties[i]; + var value = psuedoStyle[property]; + if (!(value instanceof Array)) + builder.push(displayProperty(property, value)); + else { + if (value.length === 1) + builder.push(displayProperty(property, value) + " /* merged from equivalent edits */"); // UIString? + else { + builder.push(indent + "/* There was a Conflict... There were Multiple Edits for '" + property + "' */"); // UIString? + for (var j = 0; j < value.length; ++j) + builder.push(displayProperty(property, value, true)); + } + } + } + builder.push("}\n"); + } + } + + WebInspector.showConsole(); + WebInspector.console.addMessage(new WebInspector.ConsoleTextMessage(builder.join("\n"))); + }, + + get rootDOMNode() + { + return this.treeOutline.rootDOMNode; + }, + + set rootDOMNode(x) + { + this.treeOutline.rootDOMNode = x; + }, + + get focusedDOMNode() + { + return this.treeOutline.focusedDOMNode; + }, + + set focusedDOMNode(x) + { + this.treeOutline.focusedDOMNode = x; + }, + + _nodeInserted: function(event) + { + this.recentlyModifiedNodes.push({node: event.target, parent: event.relatedNode, inserted: true}); + if (this.visible) + this._updateModifiedNodesSoon(); + }, + + _nodeRemoved: function(event) + { + this.recentlyModifiedNodes.push({node: event.target, parent: event.relatedNode, removed: true}); + if (this.visible) + this._updateModifiedNodesSoon(); + }, + + _updateModifiedNodesSoon: function() + { + if ("_updateModifiedNodesTimeout" in this) + return; + this._updateModifiedNodesTimeout = setTimeout(this._updateModifiedNodes.bind(this), 0); + }, + + _updateModifiedNodes: function() + { + if ("_updateModifiedNodesTimeout" in this) { + clearTimeout(this._updateModifiedNodesTimeout); + delete this._updateModifiedNodesTimeout; + } + + var updatedParentTreeElements = []; + var updateBreadcrumbs = false; + + for (var i = 0; i < this.recentlyModifiedNodes.length; ++i) { + var replaced = this.recentlyModifiedNodes[i].replaced; + var parent = this.recentlyModifiedNodes[i].parent; + if (!parent) + continue; + + var parentNodeItem = this.treeOutline.findTreeElement(parent); + if (parentNodeItem && !parentNodeItem.alreadyUpdatedChildren) { + parentNodeItem.updateChildren(replaced); + parentNodeItem.alreadyUpdatedChildren = true; + updatedParentTreeElements.push(parentNodeItem); + } + + if (!updateBreadcrumbs && (this.focusedDOMNode === parent || isAncestor(this.focusedDOMNode, parent))) + updateBreadcrumbs = true; + } + + for (var i = 0; i < updatedParentTreeElements.length; ++i) + delete updatedParentTreeElements[i].alreadyUpdatedChildren; + + this.recentlyModifiedNodes = []; + + if (updateBreadcrumbs) + this.updateBreadcrumb(true); + }, + + _stylesPaneEdited: function() + { + this.sidebarPanes.metrics.needsUpdate = true; + this.updateMetrics(); + }, + + _metricsPaneEdited: function() + { + this.sidebarPanes.styles.needsUpdate = true; + this.updateStyles(true); + }, + + _mouseMovedInCrumbs: function(event) + { + var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY); + var crumbElement = nodeUnderMouse.enclosingNodeOrSelfWithClass("crumb"); + + WebInspector.hoveredDOMNode = (crumbElement ? crumbElement.representedObject : null); + + if ("_mouseOutOfCrumbsTimeout" in this) { + clearTimeout(this._mouseOutOfCrumbsTimeout); + delete this._mouseOutOfCrumbsTimeout; + } + }, + + _mouseMovedOutOfCrumbs: function(event) + { + var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY); + if (nodeUnderMouse.isDescendant(this.crumbsElement)) + return; + + WebInspector.hoveredDOMNode = null; + + this._mouseOutOfCrumbsTimeout = setTimeout(this.updateBreadcrumbSizes.bind(this), 1000); + }, + + updateBreadcrumb: function(forceUpdate) + { + if (!this.visible) + return; + + var crumbs = this.crumbsElement; + + var handled = false; + var foundRoot = false; + var crumb = crumbs.firstChild; + while (crumb) { + if (crumb.representedObject === this.rootDOMNode) + foundRoot = true; + + if (foundRoot) + crumb.addStyleClass("dimmed"); + else + crumb.removeStyleClass("dimmed"); + + if (crumb.representedObject === this.focusedDOMNode) { + crumb.addStyleClass("selected"); + handled = true; + } else { + crumb.removeStyleClass("selected"); + } + + crumb = crumb.nextSibling; + } + + if (handled && !forceUpdate) { + // We don't need to rebuild the crumbs, but we need to adjust sizes + // to reflect the new focused or root node. + this.updateBreadcrumbSizes(); + return; + } + + crumbs.removeChildren(); + + var panel = this; + + function selectCrumbFunction(event) + { + var crumb = event.currentTarget; + if (crumb.hasStyleClass("collapsed")) { + // Clicking a collapsed crumb will expose the hidden crumbs. + if (crumb === panel.crumbsElement.firstChild) { + // If the focused crumb is the first child, pick the farthest crumb + // that is still hidden. This allows the user to expose every crumb. + var currentCrumb = crumb; + while (currentCrumb) { + var hidden = currentCrumb.hasStyleClass("hidden"); + var collapsed = currentCrumb.hasStyleClass("collapsed"); + if (!hidden && !collapsed) + break; + crumb = currentCrumb; + currentCrumb = currentCrumb.nextSibling; + } + } + + panel.updateBreadcrumbSizes(crumb); + } else { + // Clicking a dimmed crumb or double clicking (event.detail >= 2) + // will change the root node in addition to the focused node. + if (event.detail >= 2 || crumb.hasStyleClass("dimmed")) + panel.rootDOMNode = crumb.representedObject.parentNode; + panel.focusedDOMNode = crumb.representedObject; + } + + event.preventDefault(); + } + + foundRoot = false; + for (var current = this.focusedDOMNode; current; current = current.parentNode) { + if (current.nodeType === Node.DOCUMENT_NODE) + continue; + + if (current === this.rootDOMNode) + foundRoot = true; + + var crumb = document.createElement("span"); + crumb.className = "crumb"; + crumb.representedObject = current; + crumb.addEventListener("mousedown", selectCrumbFunction, false); + + var crumbTitle; + switch (current.nodeType) { + case Node.ELEMENT_NODE: + crumbTitle = current.nodeName.toLowerCase(); + + var nameElement = document.createElement("span"); + nameElement.textContent = crumbTitle; + crumb.appendChild(nameElement); + + var idAttribute = current.getAttribute("id"); + if (idAttribute) { + var idElement = document.createElement("span"); + crumb.appendChild(idElement); + + var part = "#" + idAttribute; + crumbTitle += part; + idElement.appendChild(document.createTextNode(part)); + + // Mark the name as extra, since the ID is more important. + nameElement.className = "extra"; + } + + var classAttribute = current.getAttribute("class"); + if (classAttribute) { + var classes = classAttribute.split(/\s+/); + var foundClasses = {}; + + if (classes.length) { + var classesElement = document.createElement("span"); + classesElement.className = "extra"; + crumb.appendChild(classesElement); + + for (var i = 0; i < classes.length; ++i) { + var className = classes[i]; + if (className && !(className in foundClasses)) { + var part = "." + className; + crumbTitle += part; + classesElement.appendChild(document.createTextNode(part)); + foundClasses[className] = true; + } + } + } + } + + break; + + case Node.TEXT_NODE: + if (isNodeWhitespace.call(current)) + crumbTitle = WebInspector.UIString("(whitespace)"); + else + crumbTitle = WebInspector.UIString("(text)"); + break + + case Node.COMMENT_NODE: + crumbTitle = "<!-->"; + break; + + case Node.DOCUMENT_TYPE_NODE: + crumbTitle = "<!DOCTYPE>"; + break; + + default: + crumbTitle = current.nodeName.toLowerCase(); + } + + if (!crumb.childNodes.length) { + var nameElement = document.createElement("span"); + nameElement.textContent = crumbTitle; + crumb.appendChild(nameElement); + } + + crumb.title = crumbTitle; + + if (foundRoot) + crumb.addStyleClass("dimmed"); + if (current === this.focusedDOMNode) + crumb.addStyleClass("selected"); + if (!crumbs.childNodes.length) + crumb.addStyleClass("end"); + + crumbs.appendChild(crumb); + } + + if (crumbs.hasChildNodes()) + crumbs.lastChild.addStyleClass("start"); + + this.updateBreadcrumbSizes(); + }, + + updateBreadcrumbSizes: function(focusedCrumb) + { + if (!this.visible) + return; + + if (document.body.offsetWidth <= 0) { + // The stylesheet hasn't loaded yet or the window is closed, + // so we can't calculate what is need. Return early. + return; + } + + var crumbs = this.crumbsElement; + if (!crumbs.childNodes.length || crumbs.offsetWidth <= 0) + return; // No crumbs, do nothing. + + // A Zero index is the right most child crumb in the breadcrumb. + var selectedIndex = 0; + var focusedIndex = 0; + var selectedCrumb; + + var i = 0; + var crumb = crumbs.firstChild; + while (crumb) { + // Find the selected crumb and index. + if (!selectedCrumb && crumb.hasStyleClass("selected")) { + selectedCrumb = crumb; + selectedIndex = i; + } + + // Find the focused crumb index. + if (crumb === focusedCrumb) + focusedIndex = i; + + // Remove any styles that affect size before + // deciding to shorten any crumbs. + if (crumb !== crumbs.lastChild) + crumb.removeStyleClass("start"); + if (crumb !== crumbs.firstChild) + crumb.removeStyleClass("end"); + + crumb.removeStyleClass("compact"); + crumb.removeStyleClass("collapsed"); + crumb.removeStyleClass("hidden"); + + crumb = crumb.nextSibling; + ++i; + } + + // Restore the start and end crumb classes in case they got removed in coalesceCollapsedCrumbs(). + // The order of the crumbs in the document is opposite of the visual order. + crumbs.firstChild.addStyleClass("end"); + crumbs.lastChild.addStyleClass("start"); + + function crumbsAreSmallerThanContainer() + { + var rightPadding = 20; + var errorWarningElement = document.getElementById("error-warning-count"); + if (!WebInspector.drawer.visible && errorWarningElement) + rightPadding += errorWarningElement.offsetWidth; + return ((crumbs.totalOffsetLeft + crumbs.offsetWidth + rightPadding) < window.innerWidth); + } + + if (crumbsAreSmallerThanContainer()) + return; // No need to compact the crumbs, they all fit at full size. + + var BothSides = 0; + var AncestorSide = -1; + var ChildSide = 1; + + function makeCrumbsSmaller(shrinkingFunction, direction, significantCrumb) + { + if (!significantCrumb) + significantCrumb = (focusedCrumb || selectedCrumb); + + if (significantCrumb === selectedCrumb) + var significantIndex = selectedIndex; + else if (significantCrumb === focusedCrumb) + var significantIndex = focusedIndex; + else { + var significantIndex = 0; + for (var i = 0; i < crumbs.childNodes.length; ++i) { + if (crumbs.childNodes[i] === significantCrumb) { + significantIndex = i; + break; + } + } + } + + function shrinkCrumbAtIndex(index) + { + var shrinkCrumb = crumbs.childNodes[index]; + if (shrinkCrumb && shrinkCrumb !== significantCrumb) + shrinkingFunction(shrinkCrumb); + if (crumbsAreSmallerThanContainer()) + return true; // No need to compact the crumbs more. + return false; + } + + // Shrink crumbs one at a time by applying the shrinkingFunction until the crumbs + // fit in the container or we run out of crumbs to shrink. + if (direction) { + // Crumbs are shrunk on only one side (based on direction) of the signifcant crumb. + var index = (direction > 0 ? 0 : crumbs.childNodes.length - 1); + while (index !== significantIndex) { + if (shrinkCrumbAtIndex(index)) + return true; + index += (direction > 0 ? 1 : -1); + } + } else { + // Crumbs are shrunk in order of descending distance from the signifcant crumb, + // with a tie going to child crumbs. + var startIndex = 0; + var endIndex = crumbs.childNodes.length - 1; + while (startIndex != significantIndex || endIndex != significantIndex) { + var startDistance = significantIndex - startIndex; + var endDistance = endIndex - significantIndex; + if (startDistance >= endDistance) + var index = startIndex++; + else + var index = endIndex--; + if (shrinkCrumbAtIndex(index)) + return true; + } + } + + // We are not small enough yet, return false so the caller knows. + return false; + } + + function coalesceCollapsedCrumbs() + { + var crumb = crumbs.firstChild; + var collapsedRun = false; + var newStartNeeded = false; + var newEndNeeded = false; + while (crumb) { + var hidden = crumb.hasStyleClass("hidden"); + if (!hidden) { + var collapsed = crumb.hasStyleClass("collapsed"); + if (collapsedRun && collapsed) { + crumb.addStyleClass("hidden"); + crumb.removeStyleClass("compact"); + crumb.removeStyleClass("collapsed"); + + if (crumb.hasStyleClass("start")) { + crumb.removeStyleClass("start"); + newStartNeeded = true; + } + + if (crumb.hasStyleClass("end")) { + crumb.removeStyleClass("end"); + newEndNeeded = true; + } + + continue; + } + + collapsedRun = collapsed; + + if (newEndNeeded) { + newEndNeeded = false; + crumb.addStyleClass("end"); + } + } else + collapsedRun = true; + crumb = crumb.nextSibling; + } + + if (newStartNeeded) { + crumb = crumbs.lastChild; + while (crumb) { + if (!crumb.hasStyleClass("hidden")) { + crumb.addStyleClass("start"); + break; + } + crumb = crumb.previousSibling; + } + } + } + + function compact(crumb) + { + if (crumb.hasStyleClass("hidden")) + return; + crumb.addStyleClass("compact"); + } + + function collapse(crumb, dontCoalesce) + { + if (crumb.hasStyleClass("hidden")) + return; + crumb.addStyleClass("collapsed"); + crumb.removeStyleClass("compact"); + if (!dontCoalesce) + coalesceCollapsedCrumbs(); + } + + function compactDimmed(crumb) + { + if (crumb.hasStyleClass("dimmed")) + compact(crumb); + } + + function collapseDimmed(crumb) + { + if (crumb.hasStyleClass("dimmed")) + collapse(crumb); + } + + if (!focusedCrumb) { + // When not focused on a crumb we can be biased and collapse less important + // crumbs that the user might not care much about. + + // Compact child crumbs. + if (makeCrumbsSmaller(compact, ChildSide)) + return; + + // Collapse child crumbs. + if (makeCrumbsSmaller(collapse, ChildSide)) + return; + + // Compact dimmed ancestor crumbs. + if (makeCrumbsSmaller(compactDimmed, AncestorSide)) + return; + + // Collapse dimmed ancestor crumbs. + if (makeCrumbsSmaller(collapseDimmed, AncestorSide)) + return; + } + + // Compact ancestor crumbs, or from both sides if focused. + if (makeCrumbsSmaller(compact, (focusedCrumb ? BothSides : AncestorSide))) + return; + + // Collapse ancestor crumbs, or from both sides if focused. + if (makeCrumbsSmaller(collapse, (focusedCrumb ? BothSides : AncestorSide))) + return; + + if (!selectedCrumb) + return; + + // Compact the selected crumb. + compact(selectedCrumb); + if (crumbsAreSmallerThanContainer()) + return; + + // Collapse the selected crumb as a last resort. Pass true to prevent coalescing. + collapse(selectedCrumb, true); + }, + + updateStyles: function(forceUpdate) + { + var stylesSidebarPane = this.sidebarPanes.styles; + if (!stylesSidebarPane.expanded || !stylesSidebarPane.needsUpdate) + return; + + stylesSidebarPane.update(this.focusedDOMNode, null, forceUpdate); + stylesSidebarPane.needsUpdate = false; + }, + + updateMetrics: function() + { + var metricsSidebarPane = this.sidebarPanes.metrics; + if (!metricsSidebarPane.expanded || !metricsSidebarPane.needsUpdate) + return; + + metricsSidebarPane.update(this.focusedDOMNode); + metricsSidebarPane.needsUpdate = false; + }, + + updateProperties: function() + { + var propertiesSidebarPane = this.sidebarPanes.properties; + if (!propertiesSidebarPane.expanded || !propertiesSidebarPane.needsUpdate) + return; + + propertiesSidebarPane.update(this.focusedDOMNode); + propertiesSidebarPane.needsUpdate = false; + }, + + handleKeyEvent: function(event) + { + this.treeOutline.handleKeyEvent(event); + }, + + handleCopyEvent: function(event) + { + // Don't prevent the normal copy if the user has a selection. + if (!window.getSelection().isCollapsed) + return; + + switch (this.focusedDOMNode.nodeType) { + case Node.ELEMENT_NODE: + // TODO: Introduce InspectorController.copyEvent that pushes appropriate markup into the clipboard. + var data = null; + break; + + case Node.COMMENT_NODE: + var data = "<!--" + this.focusedDOMNode.nodeValue + "-->"; + break; + + default: + case Node.TEXT_NODE: + var data = this.focusedDOMNode.nodeValue; + } + + event.clipboardData.clearData(); + event.preventDefault(); + + if (data) + event.clipboardData.setData("text/plain", data); + }, + + rightSidebarResizerDragStart: function(event) + { + WebInspector.elementDragStart(this.sidebarElement, this.rightSidebarResizerDrag.bind(this), this.rightSidebarResizerDragEnd.bind(this), event, "col-resize"); + }, + + rightSidebarResizerDragEnd: function(event) + { + WebInspector.elementDragEnd(event); + }, + + rightSidebarResizerDrag: function(event) + { + var x = event.pageX; + var newWidth = Number.constrain(window.innerWidth - x, Preferences.minElementsSidebarWidth, window.innerWidth * 0.66); + + this.sidebarElement.style.width = newWidth + "px"; + this.contentElement.style.right = newWidth + "px"; + this.sidebarResizeElement.style.right = (newWidth - 3) + "px"; + + this.treeOutline.updateSelection(); + + event.preventDefault(); + }, + + _nodeSearchButtonClicked: function(event) + { + InspectorController.toggleNodeSearch(); + + this.nodeSearchButton.toggled = InspectorController.searchingForNode(); + } +} + +WebInspector.ElementsPanel.prototype.__proto__ = WebInspector.Panel.prototype; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ElementsTreeOutline.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ElementsTreeOutline.js new file mode 100644 index 0000000..08ba1c2 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ElementsTreeOutline.js @@ -0,0 +1,742 @@ +/* + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2008 Matt Lilek <webkit@mattlilek.com> + * Copyright (C) 2009 Joseph Pecoraro + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.ElementsTreeOutline = function() { + this.element = document.createElement("ol"); + this.element.addEventListener("mousedown", this._onmousedown.bind(this), false); + this.element.addEventListener("dblclick", this._ondblclick.bind(this), false); + this.element.addEventListener("mousemove", this._onmousemove.bind(this), false); + this.element.addEventListener("mouseout", this._onmouseout.bind(this), false); + + TreeOutline.call(this, this.element); + + this.includeRootDOMNode = true; + this.selectEnabled = false; + this.rootDOMNode = null; + this.focusedDOMNode = null; +} + +WebInspector.ElementsTreeOutline.prototype = { + get rootDOMNode() + { + return this._rootDOMNode; + }, + + set rootDOMNode(x) + { + if (this._rootDOMNode === x) + return; + + this._rootDOMNode = x; + + this.update(); + }, + + get focusedDOMNode() + { + return this._focusedDOMNode; + }, + + set focusedDOMNode(x) + { + if (this._focusedDOMNode === x) { + this.revealAndSelectNode(x); + return; + } + + this._focusedDOMNode = x; + + this.revealAndSelectNode(x); + + // The revealAndSelectNode() method might find a different element if there is inlined text, + // and the select() call would change the focusedDOMNode and reenter this setter. So to + // avoid calling focusedNodeChanged() twice, first check if _focusedDOMNode is the same + // node as the one passed in. + if (this._focusedDOMNode === x) { + this.focusedNodeChanged(); + + if (x && !this.suppressSelectHighlight) { + InspectorController.highlightDOMNode(x.id); + + if ("_restorePreviousHighlightNodeTimeout" in this) + clearTimeout(this._restorePreviousHighlightNodeTimeout); + + function restoreHighlightToHoveredNode() + { + var hoveredNode = WebInspector.hoveredDOMNode; + if (hoveredNode) + InspectorController.highlightDOMNode(hoveredNode.id); + else + InspectorController.hideDOMNodeHighlight(); + } + + this._restorePreviousHighlightNodeTimeout = setTimeout(restoreHighlightToHoveredNode, 2000); + } + } + }, + + update: function() + { + this.removeChildren(); + + if (!this.rootDOMNode) + return; + + var treeElement; + if (this.includeRootDOMNode) { + treeElement = new WebInspector.ElementsTreeElement(this.rootDOMNode); + treeElement.selectable = this.selectEnabled; + this.appendChild(treeElement); + } else { + // FIXME: this could use findTreeElement to reuse a tree element if it already exists + var node = (Preferences.ignoreWhitespace ? firstChildSkippingWhitespace.call(this.rootDOMNode) : this.rootDOMNode.firstChild); + while (node) { + treeElement = new WebInspector.ElementsTreeElement(node); + treeElement.selectable = this.selectEnabled; + this.appendChild(treeElement); + node = Preferences.ignoreWhitespace ? nextSiblingSkippingWhitespace.call(node) : node.nextSibling; + } + } + + this.updateSelection(); + }, + + updateSelection: function() + { + if (!this.selectedTreeElement) + return; + var element = this.treeOutline.selectedTreeElement; + element.updateSelection(); + }, + + focusedNodeChanged: function(forceUpdate) {}, + + findTreeElement: function(node) + { + var treeElement = TreeOutline.prototype.findTreeElement.call(this, node, isAncestorNode, parentNode); + if (!treeElement && node.nodeType === Node.TEXT_NODE) { + // The text node might have been inlined if it was short, so try to find the parent element. + treeElement = TreeOutline.prototype.findTreeElement.call(this, node.parentNode, isAncestorNode, parentNode); + } + + return treeElement; + }, + + revealAndSelectNode: function(node) + { + if (!node) + return; + + var treeElement = this.findTreeElement(node); + if (!treeElement) + return; + + treeElement.reveal(); + treeElement.select(); + }, + + _treeElementFromEvent: function(event) + { + var root = this.element; + + // We choose this X coordinate based on the knowledge that our list + // items extend nearly to the right edge of the outer <ol>. + var x = root.totalOffsetLeft + root.offsetWidth - 20; + + var y = event.pageY; + + // Our list items have 1-pixel cracks between them vertically. We avoid + // the cracks by checking slightly above and slightly below the mouse + // and seeing if we hit the same element each time. + var elementUnderMouse = this.treeElementFromPoint(x, y); + var elementAboveMouse = this.treeElementFromPoint(x, y - 2); + var element; + if (elementUnderMouse === elementAboveMouse) + element = elementUnderMouse; + else + element = this.treeElementFromPoint(x, y + 2); + + return element; + }, + + _ondblclick: function(event) + { + var element = this._treeElementFromEvent(event); + + if (!element || !element.ondblclick) + return; + + element.ondblclick(element, event); + }, + + _onmousedown: function(event) + { + var element = this._treeElementFromEvent(event); + + if (!element || element.isEventWithinDisclosureTriangle(event)) + return; + + element.select(); + }, + + _onmousemove: function(event) + { + if (this._previousHoveredElement) { + this._previousHoveredElement.hovered = false; + delete this._previousHoveredElement; + } + + var element = this._treeElementFromEvent(event); + if (element && !element.elementCloseTag) { + element.hovered = true; + this._previousHoveredElement = element; + } + + WebInspector.hoveredDOMNode = (element && !element.elementCloseTag ? element.representedObject : null); + }, + + _onmouseout: function(event) + { + var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY); + if (nodeUnderMouse && nodeUnderMouse.isDescendant(this.element)) + return; + + if (this._previousHoveredElement) { + this._previousHoveredElement.hovered = false; + delete this._previousHoveredElement; + } + + WebInspector.hoveredDOMNode = null; + } +} + +WebInspector.ElementsTreeOutline.prototype.__proto__ = TreeOutline.prototype; + +WebInspector.ElementsTreeElement = function(node) +{ + var hasChildren = Preferences.ignoreWhitespace ? (firstChildSkippingWhitespace.call(node) ? true : false) : node.hasChildNodes(); + var titleInfo = nodeTitleInfo.call(node, hasChildren, WebInspector.linkifyURL); + + if (titleInfo.hasChildren) + this.whitespaceIgnored = Preferences.ignoreWhitespace; + + // The title will be updated in onattach. + TreeElement.call(this, "", node, titleInfo.hasChildren); + + if (this.representedObject.nodeType == Node.ELEMENT_NODE) + this._canAddAttributes = true; +} + +WebInspector.ElementsTreeElement.prototype = { + get highlighted() + { + return this._highlighted; + }, + + set highlighted(x) + { + if (this._highlighted === x) + return; + + this._highlighted = x; + + if (this.listItemElement) { + if (x) + this.listItemElement.addStyleClass("highlighted"); + else + this.listItemElement.removeStyleClass("highlighted"); + } + }, + + get hovered() + { + return this._hovered; + }, + + set hovered(x) + { + if (this._hovered === x) + return; + + this._hovered = x; + + if (this.listItemElement) { + if (x) { + this.updateSelection(); + this.listItemElement.addStyleClass("hovered"); + } else + this.listItemElement.removeStyleClass("hovered"); + if (this._canAddAttributes) + this.toggleNewAttributeButton(); + } + }, + + toggleNewAttributeButton: function() + { + function removeWhenEditing(event) + { + if (this._addAttributeElement && this._addAttributeElement.parentNode) + this._addAttributeElement.parentNode.removeChild(this._addAttributeElement); + delete this._addAttributeElement; + } + + if (!this._addAttributeElement && this._hovered && !this._editing) { + var span = document.createElement("span"); + span.className = "add-attribute"; + span.textContent = "\u2026"; + span.addEventListener("dblclick", removeWhenEditing.bind(this), false); + this._addAttributeElement = span; + + var tag = this.listItemElement.getElementsByClassName("webkit-html-tag")[0]; + this._insertInLastAttributePosition(tag, span); + } else if (!this._hovered && this._addAttributeElement) { + if (this._addAttributeElement.parentNode) + this._addAttributeElement.parentNode.removeChild(this._addAttributeElement); + delete this._addAttributeElement; + } + }, + + updateSelection: function() + { + var listItemElement = this.listItemElement; + if (!listItemElement) + return; + + if (document.body.offsetWidth <= 0) { + // The stylesheet hasn't loaded yet or the window is closed, + // so we can't calculate what is need. Return early. + return; + } + + if (!this.selectionElement) { + this.selectionElement = document.createElement("div"); + this.selectionElement.className = "selection selected"; + listItemElement.insertBefore(this.selectionElement, listItemElement.firstChild); + } + + this.selectionElement.style.height = listItemElement.offsetHeight + "px"; + }, + + onattach: function() + { + this.listItemElement.addEventListener("mousedown", this.onmousedown.bind(this), false); + + if (this._highlighted) + this.listItemElement.addStyleClass("highlighted"); + + if (this._hovered) { + this.updateSelection(); + this.listItemElement.addStyleClass("hovered"); + } + + this._updateTitle(); + + this._preventFollowingLinksOnDoubleClick(); + }, + + _preventFollowingLinksOnDoubleClick: function() + { + var links = this.listItemElement.querySelectorAll("li > .webkit-html-tag > .webkit-html-attribute > .webkit-html-external-link, li > .webkit-html-tag > .webkit-html-attribute > .webkit-html-resource-link"); + if (!links) + return; + + for (var i = 0; i < links.length; ++i) + links[i].preventFollowOnDoubleClick = true; + }, + + onpopulate: function() + { + if (this.children.length || this.whitespaceIgnored !== Preferences.ignoreWhitespace) + return; + + this.whitespaceIgnored = Preferences.ignoreWhitespace; + + this.updateChildren(); + }, + + updateChildren: function(fullRefresh) + { + WebInspector.domAgent.getChildNodesAsync(this.representedObject, this._updateChildren.bind(this, fullRefresh)); + }, + + _updateChildren: function(fullRefresh) + { + if (fullRefresh) { + var selectedTreeElement = this.treeOutline.selectedTreeElement; + if (selectedTreeElement && selectedTreeElement.hasAncestor(this)) + this.select(); + this.removeChildren(); + } + + var treeElement = this; + var treeChildIndex = 0; + + function updateChildrenOfNode(node) + { + var treeOutline = treeElement.treeOutline; + var child = (Preferences.ignoreWhitespace ? firstChildSkippingWhitespace.call(node) : node.firstChild); + while (child) { + var currentTreeElement = treeElement.children[treeChildIndex]; + if (!currentTreeElement || currentTreeElement.representedObject !== child) { + // Find any existing element that is later in the children list. + var existingTreeElement = null; + for (var i = (treeChildIndex + 1); i < treeElement.children.length; ++i) { + if (treeElement.children[i].representedObject === child) { + existingTreeElement = treeElement.children[i]; + break; + } + } + + if (existingTreeElement && existingTreeElement.parent === treeElement) { + // If an existing element was found and it has the same parent, just move it. + var wasSelected = existingTreeElement.selected; + treeElement.removeChild(existingTreeElement); + treeElement.insertChild(existingTreeElement, treeChildIndex); + if (wasSelected) + existingTreeElement.select(); + } else { + // No existing element found, insert a new element. + var newElement = new WebInspector.ElementsTreeElement(child); + newElement.selectable = treeOutline.selectEnabled; + treeElement.insertChild(newElement, treeChildIndex); + } + } + + child = Preferences.ignoreWhitespace ? nextSiblingSkippingWhitespace.call(child) : child.nextSibling; + ++treeChildIndex; + } + } + + // Remove any tree elements that no longer have this node (or this node's contentDocument) as their parent. + for (var i = (this.children.length - 1); i >= 0; --i) { + if ("elementCloseTag" in this.children[i]) + continue; + + var currentChild = this.children[i]; + var currentNode = currentChild.representedObject; + var currentParentNode = currentNode.parentNode; + + if (currentParentNode === this.representedObject) + continue; + + var selectedTreeElement = this.treeOutline.selectedTreeElement; + if (selectedTreeElement && (selectedTreeElement === currentChild || selectedTreeElement.hasAncestor(currentChild))) + this.select(); + + this.removeChildAtIndex(i); + } + + updateChildrenOfNode(this.representedObject); + + var lastChild = this.children[this.children.length - 1]; + if (this.representedObject.nodeType == Node.ELEMENT_NODE && (!lastChild || !lastChild.elementCloseTag)) { + var title = "<span class=\"webkit-html-tag close\"></" + this.representedObject.nodeName.toLowerCase().escapeHTML() + "></span>"; + var item = new TreeElement(title, null, false); + item.selectable = false; + item.elementCloseTag = true; + this.appendChild(item); + } + }, + + onexpand: function() + { + this.treeOutline.updateSelection(); + }, + + oncollapse: function() + { + this.treeOutline.updateSelection(); + }, + + onreveal: function() + { + if (this.listItemElement) + this.listItemElement.scrollIntoViewIfNeeded(false); + }, + + onselect: function() + { + this.treeOutline.focusedDOMNode = this.representedObject; + this.updateSelection(); + }, + + onmousedown: function(event) + { + if (this._editing) + return; + + // Prevent selecting the nearest word on double click. + if (event.detail >= 2) + event.preventDefault(); + }, + + ondblclick: function(treeElement, event) + { + if (this._editing) + return; + + if (this._startEditing(event, treeElement)) + return; + + if (this.treeOutline.panel) { + this.treeOutline.rootDOMNode = this.representedObject.parentNode; + this.treeOutline.focusedDOMNode = this.representedObject; + } + + if (this.hasChildren && !this.expanded) + this.expand(); + }, + + _insertInLastAttributePosition: function(tag, node) + { + if (tag.getElementsByClassName("webkit-html-attribute").length > 0) + tag.insertBefore(node, tag.lastChild); + else { + var nodeName = tag.textContent.match(/^<(.*?)>$/)[1]; + tag.textContent = ''; + tag.appendChild(document.createTextNode('<'+nodeName)); + tag.appendChild(node); + tag.appendChild(document.createTextNode('>')); + } + }, + + _startEditing: function(event, treeElement) + { + if (this.treeOutline.focusedDOMNode != this.representedObject) + return; + + if (this.representedObject.nodeType != Node.ELEMENT_NODE && this.representedObject.nodeType != Node.TEXT_NODE) + return false; + + var textNode = event.target.enclosingNodeOrSelfWithClass("webkit-html-text-node"); + if (textNode) + return this._startEditingTextNode(textNode); + + var attribute = event.target.enclosingNodeOrSelfWithClass("webkit-html-attribute"); + if (attribute) + return this._startEditingAttribute(attribute, event.target); + + var newAttribute = event.target.enclosingNodeOrSelfWithClass("add-attribute"); + if (newAttribute) + return this._addNewAttribute(treeElement.listItemElement); + + return false; + }, + + _addNewAttribute: function(listItemElement) + { + var attr = document.createElement("span"); + attr.className = "webkit-html-attribute"; + attr.style.marginLeft = "2px"; // overrides the .editing margin rule + attr.style.marginRight = "2px"; // overrides the .editing margin rule + var name = document.createElement("span"); + name.className = "webkit-html-attribute-name new-attribute"; + name.textContent = " "; + var value = document.createElement("span"); + value.className = "webkit-html-attribute-value"; + attr.appendChild(name); + attr.appendChild(value); + + var tag = listItemElement.getElementsByClassName("webkit-html-tag")[0]; + this._insertInLastAttributePosition(tag, attr); + return this._startEditingAttribute(attr, attr); + }, + + _triggerEditAttribute: function(attributeName) + { + var attributeElements = this.listItemElement.getElementsByClassName("webkit-html-attribute-name"); + for (var i = 0, len = attributeElements.length; i < len; ++i) { + if (attributeElements[i].textContent === attributeName) { + for (var elem = attributeElements[i].nextSibling; elem; elem = elem.nextSibling) { + if (elem.nodeType !== Node.ELEMENT_NODE) + continue; + + if (elem.hasStyleClass("webkit-html-attribute-value")) + return this._startEditingAttribute(attributeElements[i].parentNode, elem); + } + } + } + }, + + _startEditingAttribute: function(attribute, elementForSelection) + { + if (WebInspector.isBeingEdited(attribute)) + return true; + + var attributeNameElement = attribute.getElementsByClassName("webkit-html-attribute-name")[0]; + if (!attributeNameElement) + return false; + + var attributeName = attributeNameElement.innerText; + + function removeZeroWidthSpaceRecursive(node) + { + if (node.nodeType === Node.TEXT_NODE) { + node.nodeValue = node.nodeValue.replace(/\u200B/g, ""); + return; + } + + if (node.nodeType !== Node.ELEMENT_NODE) + return; + + for (var child = node.firstChild; child; child = child.nextSibling) + removeZeroWidthSpaceRecursive(child); + } + + // Remove zero-width spaces that were added by nodeTitleInfo. + removeZeroWidthSpaceRecursive(attribute); + + this._editing = true; + + WebInspector.startEditing(attribute, this._attributeEditingCommitted.bind(this), this._editingCancelled.bind(this), attributeName); + window.getSelection().setBaseAndExtent(elementForSelection, 0, elementForSelection, 1); + + return true; + }, + + _startEditingTextNode: function(textNode) + { + if (WebInspector.isBeingEdited(textNode)) + return true; + + this._editing = true; + + WebInspector.startEditing(textNode, this._textNodeEditingCommitted.bind(this), this._editingCancelled.bind(this)); + window.getSelection().setBaseAndExtent(textNode, 0, textNode, 1); + + return true; + }, + + _attributeEditingCommitted: function(element, newText, oldText, attributeName, moveDirection) + { + delete this._editing; + + // Before we do anything, determine where we should move + // next based on the current element's settings + var moveToAttribute; + var newAttribute; + if (moveDirection) { + var found = false; + var attributes = this.representedObject.attributes; + for (var i = 0, len = attributes.length; i < len; ++i) { + if (attributes[i].name === attributeName) { + found = true; + if (moveDirection === "backward" && i > 0) + moveToAttribute = attributes[i - 1].name; + else if (moveDirection === "forward" && i < attributes.length - 1) + moveToAttribute = attributes[i + 1].name; + else if (moveDirection === "forward" && i === attributes.length - 1) + newAttribute = true; + } + } + + if (!found && moveDirection === "backward") + moveToAttribute = attributes[attributes.length - 1].name; + else if (!found && moveDirection === "forward" && !/^\s*$/.test(newText)) + newAttribute = true; + } + + function moveToNextAttributeIfNeeded() { + if (moveToAttribute) + this._triggerEditAttribute(moveToAttribute); + else if (newAttribute) + this._addNewAttribute(this.listItemElement); + } + + var parseContainerElement = document.createElement("span"); + parseContainerElement.innerHTML = "<span " + newText + "></span>"; + var parseElement = parseContainerElement.firstChild; + + if (!parseElement) { + this._editingCancelled(element, attributeName); + moveToNextAttributeIfNeeded.call(this); + return; + } + + if (!parseElement.hasAttributes()) { + this.representedObject.removeAttribute(attributeName); + this._updateTitle(); + moveToNextAttributeIfNeeded.call(this); + return; + } + + var foundOriginalAttribute = false; + for (var i = 0; i < parseElement.attributes.length; ++i) { + var attr = parseElement.attributes[i]; + foundOriginalAttribute = foundOriginalAttribute || attr.name === attributeName; + try { + this.representedObject.setAttribute(attr.name, attr.value); + } catch(e) {} // ignore invalid attribute (innerHTML doesn't throw errors, but this can) + } + + if (!foundOriginalAttribute) + this.representedObject.removeAttribute(attributeName); + + this._updateTitle(); + + this.treeOutline.focusedNodeChanged(true); + + moveToNextAttributeIfNeeded.call(this); + }, + + _textNodeEditingCommitted: function(element, newText) + { + delete this._editing; + + var textNode; + if (this.representedObject.nodeType == Node.ELEMENT_NODE) { + // We only show text nodes inline in elements if the element only + // has a single child, and that child is a text node. + textNode = this.representedObject.firstChild; + } else if (this.representedObject.nodeType == Node.TEXT_NODE) + textNode = this.representedObject; + + textNode.nodeValue = newText; + this._updateTitle(); + }, + + _editingCancelled: function(element, context) + { + delete this._editing; + + this._updateTitle(); + }, + + _updateTitle: function() + { + var title = nodeTitleInfo.call(this.representedObject, this.hasChildren, WebInspector.linkifyURL).title; + this.title = "<span class=\"highlight\">" + title + "</span>"; + delete this.selectionElement; + this.updateSelection(); + this._preventFollowingLinksOnDoubleClick(); + }, +} + +WebInspector.ElementsTreeElement.prototype.__proto__ = TreeElement.prototype; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/FontView.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/FontView.js new file mode 100644 index 0000000..4e1c931 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/FontView.js @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.FontView = function(resource) +{ + WebInspector.ResourceView.call(this, resource); + + this.element.addStyleClass("font"); + + var uniqueFontName = "WebInspectorFontPreview" + this.resource.identifier; + + this.fontStyleElement = document.createElement("style"); + this.fontStyleElement.textContent = "@font-face { font-family: \"" + uniqueFontName + "\"; src: url(" + this.resource.url + "); }"; + document.getElementsByTagName("head").item(0).appendChild(this.fontStyleElement); + + this.fontPreviewElement = document.createElement("div"); + this.fontPreviewElement.className = "preview"; + this.contentElement.appendChild(this.fontPreviewElement); + + this.fontPreviewElement.style.setProperty("font-family", uniqueFontName, null); + this.fontPreviewElement.innerHTML = "ABCDEFGHIJKLM<br>NOPQRSTUVWXYZ<br>abcdefghijklm<br>nopqrstuvwxyz<br>1234567890"; + + this.updateFontPreviewSize(); +} + +WebInspector.FontView.prototype = { + show: function(parentElement) + { + WebInspector.ResourceView.prototype.show.call(this, parentElement); + this.updateFontPreviewSize(); + }, + + resize: function() + { + this.updateFontPreviewSize(); + }, + + updateFontPreviewSize: function () + { + if (!this.fontPreviewElement || !this.visible) + return; + + this.fontPreviewElement.removeStyleClass("preview"); + + var measureFontSize = 50; + this.fontPreviewElement.style.setProperty("position", "absolute", null); + this.fontPreviewElement.style.setProperty("font-size", measureFontSize + "px", null); + this.fontPreviewElement.style.removeProperty("height"); + + var height = this.fontPreviewElement.offsetHeight; + var width = this.fontPreviewElement.offsetWidth; + + var containerWidth = this.contentElement.offsetWidth; + + // Subtract some padding. This should match the padding in the CSS plus room for the scrollbar. + containerWidth -= 40; + + if (!height || !width || !containerWidth) { + this.fontPreviewElement.style.removeProperty("font-size"); + this.fontPreviewElement.style.removeProperty("position"); + this.fontPreviewElement.addStyleClass("preview"); + return; + } + + var lineCount = this.fontPreviewElement.getElementsByTagName("br").length + 1; + var realLineHeight = Math.floor(height / lineCount); + var fontSizeLineRatio = measureFontSize / realLineHeight; + var widthRatio = containerWidth / width; + var finalFontSize = Math.floor(realLineHeight * widthRatio * fontSizeLineRatio) - 1; + + this.fontPreviewElement.style.setProperty("font-size", finalFontSize + "px", null); + this.fontPreviewElement.style.setProperty("height", this.fontPreviewElement.offsetHeight + "px", null); + this.fontPreviewElement.style.removeProperty("position"); + + this.fontPreviewElement.addStyleClass("preview"); + } +} + +WebInspector.FontView.prototype.__proto__ = WebInspector.ResourceView.prototype; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ImageView.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ImageView.js new file mode 100644 index 0000000..001ffdd --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ImageView.js @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.ImageView = function(resource) +{ + WebInspector.ResourceView.call(this, resource); + + this.element.addStyleClass("image"); + + var container = document.createElement("div"); + container.className = "image"; + this.contentElement.appendChild(container); + + this.imagePreviewElement = document.createElement("img"); + this.imagePreviewElement.setAttribute("src", this.resource.url); + + container.appendChild(this.imagePreviewElement); + + container = document.createElement("div"); + container.className = "info"; + this.contentElement.appendChild(container); + + var imageNameElement = document.createElement("h1"); + imageNameElement.className = "title"; + imageNameElement.textContent = this.resource.displayName; + container.appendChild(imageNameElement); + + var infoListElement = document.createElement("dl"); + infoListElement.className = "infoList"; + + var imageProperties = [ + { name: WebInspector.UIString("Dimensions"), value: WebInspector.UIString("%d × %d", this.imagePreviewElement.naturalWidth, this.imagePreviewElement.height) }, + { name: WebInspector.UIString("File size"), value: Number.bytesToString(this.resource.contentLength, WebInspector.UIString.bind(WebInspector)) }, + { name: WebInspector.UIString("MIME type"), value: this.resource.mimeType } + ]; + + var listHTML = ''; + for (var i = 0; i < imageProperties.length; ++i) + listHTML += "<dt>" + imageProperties[i].name + "</dt><dd>" + imageProperties[i].value + "</dd>"; + + infoListElement.innerHTML = listHTML; + container.appendChild(infoListElement); +} + +WebInspector.ImageView.prototype = { + +} + +WebInspector.ImageView.prototype.__proto__ = WebInspector.ResourceView.prototype; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/InjectedScript.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/InjectedScript.js new file mode 100644 index 0000000..b3d9566 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/InjectedScript.js @@ -0,0 +1,1133 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +var InjectedScript = {}; + +// Called from within InspectorController on the 'inspected page' side. +InjectedScript.reset = function() +{ + InjectedScript._styles = {}; + InjectedScript._styleRules = {}; + InjectedScript._lastStyleId = 0; + InjectedScript._lastStyleRuleId = 0; + InjectedScript._searchResults = []; + InjectedScript._includedInSearchResultsPropertyName = "__includedInInspectorSearchResults"; +} + +InjectedScript.reset(); + +InjectedScript.dispatch = function(methodName, args) +{ + var result = InjectedScript[methodName].apply(InjectedScript, JSON.parse(args)); + return JSON.stringify(result); +} + +InjectedScript.getStyles = function(nodeId, authorOnly) +{ + var node = InjectedScript._nodeForId(nodeId); + if (!node) + return false; + var matchedRules = InjectedScript._window().getMatchedCSSRules(node, "", authorOnly); + var matchedCSSRules = []; + for (var i = 0; matchedRules && i < matchedRules.length; ++i) + matchedCSSRules.push(InjectedScript._serializeRule(matchedRules[i])); + + var styleAttributes = {}; + var attributes = node.attributes; + for (var i = 0; attributes && i < attributes.length; ++i) { + if (attributes[i].style) + styleAttributes[attributes[i].name] = InjectedScript._serializeStyle(attributes[i].style, true); + } + var result = {}; + result.inlineStyle = InjectedScript._serializeStyle(node.style, true); + result.computedStyle = InjectedScript._serializeStyle(InjectedScript._window().getComputedStyle(node)); + result.matchedCSSRules = matchedCSSRules; + result.styleAttributes = styleAttributes; + return result; +} + +InjectedScript.getComputedStyle = function(nodeId) +{ + var node = InjectedScript._nodeForId(nodeId); + if (!node) + return false; + return InjectedScript._serializeStyle(InjectedScript._window().getComputedStyle(node)); +} + +InjectedScript.getInlineStyle = function(nodeId) +{ + var node = InjectedScript._nodeForId(nodeId); + if (!node) + return false; + return InjectedScript._serializeStyle(node.style, true); +} + +InjectedScript.applyStyleText = function(styleId, styleText, propertyName) +{ + var style = InjectedScript._styles[styleId]; + if (!style) + return false; + + var styleTextLength = styleText.length; + + // Create a new element to parse the user input CSS. + var parseElement = document.createElement("span"); + parseElement.setAttribute("style", styleText); + + var tempStyle = parseElement.style; + if (tempStyle.length || !styleTextLength) { + // The input was parsable or the user deleted everything, so remove the + // original property from the real style declaration. If this represents + // a shorthand remove all the longhand properties. + if (style.getPropertyShorthand(propertyName)) { + var longhandProperties = InjectedScript._getLonghandProperties(style, propertyName); + for (var i = 0; i < longhandProperties.length; ++i) + style.removeProperty(longhandProperties[i]); + } else + style.removeProperty(propertyName); + } + + // Notify caller that the property was successfully deleted. + if (!styleTextLength) + return [null, [propertyName]]; + + if (!tempStyle.length) + return false; + + // Iterate of the properties on the test element's style declaration and + // add them to the real style declaration. We take care to move shorthands. + var foundShorthands = {}; + var changedProperties = []; + var uniqueProperties = InjectedScript._getUniqueStyleProperties(tempStyle); + for (var i = 0; i < uniqueProperties.length; ++i) { + var name = uniqueProperties[i]; + var shorthand = tempStyle.getPropertyShorthand(name); + + if (shorthand && shorthand in foundShorthands) + continue; + + if (shorthand) { + var value = InjectedScript._getShorthandValue(tempStyle, shorthand); + var priority = InjectedScript._getShorthandPriority(tempStyle, shorthand); + foundShorthands[shorthand] = true; + } else { + var value = tempStyle.getPropertyValue(name); + var priority = tempStyle.getPropertyPriority(name); + } + + // Set the property on the real style declaration. + style.setProperty((shorthand || name), value, priority); + changedProperties.push(shorthand || name); + } + return [InjectedScript._serializeStyle(style, true), changedProperties]; +} + +InjectedScript.setStyleText = function(style, cssText) +{ + style.cssText = cssText; +} + +InjectedScript.toggleStyleEnabled = function(styleId, propertyName, disabled) +{ + var style = InjectedScript._styles[styleId]; + if (!style) + return false; + + if (disabled) { + if (!style.__disabledPropertyValues || !style.__disabledPropertyPriorities) { + var inspectedWindow = InjectedScript._window(); + style.__disabledProperties = new inspectedWindow.Object; + style.__disabledPropertyValues = new inspectedWindow.Object; + style.__disabledPropertyPriorities = new inspectedWindow.Object; + } + + style.__disabledPropertyValues[propertyName] = style.getPropertyValue(propertyName); + style.__disabledPropertyPriorities[propertyName] = style.getPropertyPriority(propertyName); + + if (style.getPropertyShorthand(propertyName)) { + var longhandProperties = InjectedScript._getLonghandProperties(style, propertyName); + for (var i = 0; i < longhandProperties.length; ++i) { + style.__disabledProperties[longhandProperties[i]] = true; + style.removeProperty(longhandProperties[i]); + } + } else { + style.__disabledProperties[propertyName] = true; + style.removeProperty(propertyName); + } + } else if (style.__disabledProperties && style.__disabledProperties[propertyName]) { + var value = style.__disabledPropertyValues[propertyName]; + var priority = style.__disabledPropertyPriorities[propertyName]; + + style.setProperty(propertyName, value, priority); + delete style.__disabledProperties[propertyName]; + delete style.__disabledPropertyValues[propertyName]; + delete style.__disabledPropertyPriorities[propertyName]; + } + return InjectedScript._serializeStyle(style, true); +} + +InjectedScript.applyStyleRuleText = function(ruleId, newContent, selectedNodeId) +{ + var rule = InjectedScript._styleRules[ruleId]; + if (!rule) + return false; + + var selectedNode = InjectedScript._nodeForId(selectedNodeId); + + try { + var stylesheet = rule.parentStyleSheet; + stylesheet.addRule(newContent); + var newRule = stylesheet.cssRules[stylesheet.cssRules.length - 1]; + newRule.style.cssText = rule.style.cssText; + + var parentRules = stylesheet.cssRules; + for (var i = 0; i < parentRules.length; ++i) { + if (parentRules[i] === rule) { + rule.parentStyleSheet.removeRule(i); + break; + } + } + + return [InjectedScript._serializeRule(newRule), InjectedScript._doesSelectorAffectNode(newContent, selectedNode)]; + } catch(e) { + // Report invalid syntax. + return false; + } +} + +InjectedScript.addStyleSelector = function(newContent, selectedNodeId) +{ + var stylesheet = InjectedScript.stylesheet; + if (!stylesheet) { + var inspectedDocument = InjectedScript._window().document; + var head = inspectedDocument.getElementsByTagName("head")[0]; + var styleElement = inspectedDocument.createElement("style"); + styleElement.type = "text/css"; + head.appendChild(styleElement); + stylesheet = inspectedDocument.styleSheets[inspectedDocument.styleSheets.length - 1]; + InjectedScript.stylesheet = stylesheet; + } + + try { + stylesheet.addRule(newContent); + } catch (e) { + // Invalid Syntax for a Selector + return false; + } + + var selectedNode = InjectedScript._nodeForId(selectedNodeId); + var rule = stylesheet.cssRules[stylesheet.cssRules.length - 1]; + rule.__isViaInspector = true; + + return [ InjectedScript._serializeRule(rule), InjectedScript._doesSelectorAffectNode(newContent, selectedNode) ]; +} + +InjectedScript._doesSelectorAffectNode = function(selectorText, node) +{ + if (!node) + return false; + var nodes = node.ownerDocument.querySelectorAll(selectorText); + for (var i = 0; i < nodes.length; ++i) { + if (nodes[i] === node) { + return true; + } + } + return false; +} + +InjectedScript.setStyleProperty = function(styleId, name, value) +{ + var style = InjectedScript._styles[styleId]; + if (!style) + return false; + + style.setProperty(name, value, ""); + return true; +} + +InjectedScript._serializeRule = function(rule) +{ + var parentStyleSheet = rule.parentStyleSheet; + + var ruleValue = {}; + ruleValue.selectorText = rule.selectorText; + if (parentStyleSheet) { + ruleValue.parentStyleSheet = {}; + ruleValue.parentStyleSheet.href = parentStyleSheet.href; + } + ruleValue.isUserAgent = parentStyleSheet && !parentStyleSheet.ownerNode && !parentStyleSheet.href; + ruleValue.isUser = parentStyleSheet && parentStyleSheet.ownerNode && parentStyleSheet.ownerNode.nodeName == "#document"; + ruleValue.isViaInspector = !!rule.__isViaInspector; + + // Bind editable scripts only. + var doBind = !ruleValue.isUserAgent && !ruleValue.isUser; + ruleValue.style = InjectedScript._serializeStyle(rule.style, doBind); + + if (doBind) { + if (!rule.id) { + rule.id = InjectedScript._lastStyleRuleId++; + InjectedScript._styleRules[rule.id] = rule; + } + ruleValue.id = rule.id; + } + return ruleValue; +} + +InjectedScript._serializeStyle = function(style, doBind) +{ + var result = {}; + result.width = style.width; + result.height = style.height; + result.__disabledProperties = style.__disabledProperties; + result.__disabledPropertyValues = style.__disabledPropertyValues; + result.__disabledPropertyPriorities = style.__disabledPropertyPriorities; + result.properties = []; + result.shorthandValues = {}; + var foundShorthands = {}; + for (var i = 0; i < style.length; ++i) { + var property = {}; + var name = style[i]; + property.name = name; + property.priority = style.getPropertyPriority(name); + property.implicit = style.isPropertyImplicit(name); + var shorthand = style.getPropertyShorthand(name); + property.shorthand = shorthand; + if (shorthand && !(shorthand in foundShorthands)) { + foundShorthands[shorthand] = true; + result.shorthandValues[shorthand] = InjectedScript._getShorthandValue(style, shorthand); + } + property.value = style.getPropertyValue(name); + result.properties.push(property); + } + result.uniqueStyleProperties = InjectedScript._getUniqueStyleProperties(style); + + if (doBind) { + if (!style.id) { + style.id = InjectedScript._lastStyleId++; + InjectedScript._styles[style.id] = style; + } + result.id = style.id; + } + return result; +} + +InjectedScript._getUniqueStyleProperties = function(style) +{ + var properties = []; + var foundProperties = {}; + + for (var i = 0; i < style.length; ++i) { + var property = style[i]; + if (property in foundProperties) + continue; + foundProperties[property] = true; + properties.push(property); + } + + return properties; +} + + +InjectedScript._getLonghandProperties = function(style, shorthandProperty) +{ + var properties = []; + var foundProperties = {}; + + for (var i = 0; i < style.length; ++i) { + var individualProperty = style[i]; + if (individualProperty in foundProperties || style.getPropertyShorthand(individualProperty) !== shorthandProperty) + continue; + foundProperties[individualProperty] = true; + properties.push(individualProperty); + } + + return properties; +} + +InjectedScript._getShorthandValue = function(style, shorthandProperty) +{ + var value = style.getPropertyValue(shorthandProperty); + if (!value) { + // Some shorthands (like border) return a null value, so compute a shorthand value. + // FIXME: remove this when http://bugs.webkit.org/show_bug.cgi?id=15823 is fixed. + + var foundProperties = {}; + for (var i = 0; i < style.length; ++i) { + var individualProperty = style[i]; + if (individualProperty in foundProperties || style.getPropertyShorthand(individualProperty) !== shorthandProperty) + continue; + + var individualValue = style.getPropertyValue(individualProperty); + if (style.isPropertyImplicit(individualProperty) || individualValue === "initial") + continue; + + foundProperties[individualProperty] = true; + + if (!value) + value = ""; + else if (value.length) + value += " "; + value += individualValue; + } + } + return value; +} + +InjectedScript._getShorthandPriority = function(style, shorthandProperty) +{ + var priority = style.getPropertyPriority(shorthandProperty); + if (!priority) { + for (var i = 0; i < style.length; ++i) { + var individualProperty = style[i]; + if (style.getPropertyShorthand(individualProperty) !== shorthandProperty) + continue; + priority = style.getPropertyPriority(individualProperty); + break; + } + } + return priority; +} + +InjectedScript.getPrototypes = function(nodeId) +{ + var node = InjectedScript._nodeForId(nodeId); + if (!node) + return false; + + var result = []; + for (var prototype = node; prototype; prototype = prototype.__proto__) { + var title = Object.describe(prototype); + if (title.match(/Prototype$/)) { + title = title.replace(/Prototype$/, ""); + } + result.push(title); + } + return result; +} + +InjectedScript.getProperties = function(objectProxy, ignoreHasOwnProperty) +{ + var object = InjectedScript._resolveObject(objectProxy); + if (!object) + return false; + + var properties = []; + + // Go over properties, prepare results. + for (var propertyName in object) { + if (!ignoreHasOwnProperty && "hasOwnProperty" in object && !object.hasOwnProperty(propertyName)) + continue; + + var property = {}; + property.name = propertyName; + property.parentObjectProxy = objectProxy; + var isGetter = object["__lookupGetter__"] && object.__lookupGetter__(propertyName); + if (!property.isGetter) { + var childObject = object[propertyName]; + var childObjectProxy = new InjectedScript.createProxyObject(childObject, objectProxy.objectId, true); + childObjectProxy.path = objectProxy.path ? objectProxy.path.slice() : []; + childObjectProxy.path.push(propertyName); + childObjectProxy.protoDepth = objectProxy.protoDepth || 0; + property.value = childObjectProxy; + } else { + // FIXME: this should show something like "getter" (bug 16734). + property.value = { description: "\u2014" }; // em dash + property.isGetter = true; + } + properties.push(property); + } + return properties; +} + +InjectedScript.setPropertyValue = function(objectProxy, propertyName, expression) +{ + var object = InjectedScript._resolveObject(objectProxy); + if (!object) + return false; + + var expressionLength = expression.length; + if (!expressionLength) { + delete object[propertyName]; + return !(propertyName in object); + } + + try { + // Surround the expression in parenthesis so the result of the eval is the result + // of the whole expression not the last potential sub-expression. + + // There is a regression introduced here: eval is now happening against global object, + // not call frame while on a breakpoint. + // TODO: bring evaluation against call frame back. + var result = InjectedScript._window().eval("(" + expression + ")"); + // Store the result in the property. + object[propertyName] = result; + return true; + } catch(e) { + try { + var result = InjectedScript._window().eval("\"" + expression.escapeCharacters("\"") + "\""); + object[propertyName] = result; + return true; + } catch(e) { + return false; + } + } +} + + +InjectedScript.getCompletions = function(expression, includeInspectorCommandLineAPI) +{ + var props = {}; + try { + var expressionResult = InjectedScript._evaluateOn(InjectedScript._window().eval, InjectedScript._window(), expression); + for (var prop in expressionResult) + props[prop] = true; + if (includeInspectorCommandLineAPI) + for (var prop in InjectedScript._window()._inspectorCommandLineAPI) + if (prop.charAt(0) !== '_') + props[prop] = true; + } catch(e) { + } + return props; +} + + +InjectedScript.evaluate = function(expression) +{ + var result = {}; + try { + var value = InjectedScript._evaluateOn(InjectedScript._window().eval, InjectedScript._window(), expression); + if (value === null) + return { value: null }; + if (Object.type(value) === "error") { + result.value = Object.describe(value); + result.isException = true; + return result; + } + + var wrapper = InspectorController.wrapObject(value); + if (typeof wrapper === "object" && wrapper.exception) { + result.value = wrapper.exception; + result.isException = true; + } else { + result.value = wrapper; + } + } catch (e) { + result.value = e.toString(); + result.isException = true; + } + return result; +} + +InjectedScript._evaluateOn = function(evalFunction, object, expression) +{ + InjectedScript._ensureCommandLineAPIInstalled(); + // Surround the expression in with statements to inject our command line API so that + // the window object properties still take more precedent than our API functions. + expression = "with (window._inspectorCommandLineAPI) { with (window) { " + expression + " } }"; + return evalFunction.call(object, expression); +} + +InjectedScript.addInspectedNode = function(nodeId) +{ + var node = InjectedScript._nodeForId(nodeId); + if (!node) + return false; + + InjectedScript._ensureCommandLineAPIInstalled(); + var inspectedNodes = InjectedScript._window()._inspectorCommandLineAPI._inspectedNodes; + inspectedNodes.unshift(node); + if (inspectedNodes.length >= 5) + inspectedNodes.pop(); + return true; +} + +InjectedScript.performSearch = function(whitespaceTrimmedQuery) +{ + var tagNameQuery = whitespaceTrimmedQuery; + var attributeNameQuery = whitespaceTrimmedQuery; + var startTagFound = (tagNameQuery.indexOf("<") === 0); + var endTagFound = (tagNameQuery.lastIndexOf(">") === (tagNameQuery.length - 1)); + + if (startTagFound || endTagFound) { + var tagNameQueryLength = tagNameQuery.length; + tagNameQuery = tagNameQuery.substring((startTagFound ? 1 : 0), (endTagFound ? (tagNameQueryLength - 1) : tagNameQueryLength)); + } + + // Check the tagNameQuery is it is a possibly valid tag name. + if (!/^[a-zA-Z0-9\-_:]+$/.test(tagNameQuery)) + tagNameQuery = null; + + // Check the attributeNameQuery is it is a possibly valid tag name. + if (!/^[a-zA-Z0-9\-_:]+$/.test(attributeNameQuery)) + attributeNameQuery = null; + + const escapedQuery = whitespaceTrimmedQuery.escapeCharacters("'"); + const escapedTagNameQuery = (tagNameQuery ? tagNameQuery.escapeCharacters("'") : null); + const escapedWhitespaceTrimmedQuery = whitespaceTrimmedQuery.escapeCharacters("'"); + const searchResultsProperty = InjectedScript._includedInSearchResultsPropertyName; + + function addNodesToResults(nodes, length, getItem) + { + if (!length) + return; + + var nodeIds = []; + for (var i = 0; i < length; ++i) { + var node = getItem.call(nodes, i); + // Skip this node if it already has the property. + if (searchResultsProperty in node) + continue; + + if (!InjectedScript._searchResults.length) { + InjectedScript._currentSearchResultIndex = 0; + } + + node[searchResultsProperty] = true; + InjectedScript._searchResults.push(node); + var nodeId = InspectorController.pushNodePathToFrontend(node, false); + nodeIds.push(nodeId); + } + InspectorController.addNodesToSearchResult(nodeIds.join(",")); + } + + function matchExactItems(doc) + { + matchExactId.call(this, doc); + matchExactClassNames.call(this, doc); + matchExactTagNames.call(this, doc); + matchExactAttributeNames.call(this, doc); + } + + function matchExactId(doc) + { + const result = doc.__proto__.getElementById.call(doc, whitespaceTrimmedQuery); + addNodesToResults.call(this, result, (result ? 1 : 0), function() { return this }); + } + + function matchExactClassNames(doc) + { + const result = doc.__proto__.getElementsByClassName.call(doc, whitespaceTrimmedQuery); + addNodesToResults.call(this, result, result.length, result.item); + } + + function matchExactTagNames(doc) + { + if (!tagNameQuery) + return; + const result = doc.__proto__.getElementsByTagName.call(doc, tagNameQuery); + addNodesToResults.call(this, result, result.length, result.item); + } + + function matchExactAttributeNames(doc) + { + if (!attributeNameQuery) + return; + const result = doc.__proto__.querySelectorAll.call(doc, "[" + attributeNameQuery + "]"); + addNodesToResults.call(this, result, result.length, result.item); + } + + function matchPartialTagNames(doc) + { + if (!tagNameQuery) + return; + const result = doc.__proto__.evaluate.call(doc, "//*[contains(name(), '" + escapedTagNameQuery + "')]", doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE); + addNodesToResults.call(this, result, result.snapshotLength, result.snapshotItem); + } + + function matchStartOfTagNames(doc) + { + if (!tagNameQuery) + return; + const result = doc.__proto__.evaluate.call(doc, "//*[starts-with(name(), '" + escapedTagNameQuery + "')]", doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE); + addNodesToResults.call(this, result, result.snapshotLength, result.snapshotItem); + } + + function matchPartialTagNamesAndAttributeValues(doc) + { + if (!tagNameQuery) { + matchPartialAttributeValues.call(this, doc); + return; + } + + const result = doc.__proto__.evaluate.call(doc, "//*[contains(name(), '" + escapedTagNameQuery + "') or contains(@*, '" + escapedQuery + "')]", doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE); + addNodesToResults.call(this, result, result.snapshotLength, result.snapshotItem); + } + + function matchPartialAttributeValues(doc) + { + const result = doc.__proto__.evaluate.call(doc, "//*[contains(@*, '" + escapedQuery + "')]", doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE); + addNodesToResults.call(this, result, result.snapshotLength, result.snapshotItem); + } + + function matchStyleSelector(doc) + { + const result = doc.__proto__.querySelectorAll.call(doc, whitespaceTrimmedQuery); + addNodesToResults.call(this, result, result.length, result.item); + } + + function matchPlainText(doc) + { + const result = doc.__proto__.evaluate.call(doc, "//text()[contains(., '" + escapedQuery + "')] | //comment()[contains(., '" + escapedQuery + "')]", doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE); + addNodesToResults.call(this, result, result.snapshotLength, result.snapshotItem); + } + + function matchXPathQuery(doc) + { + const result = doc.__proto__.evaluate.call(doc, whitespaceTrimmedQuery, doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE); + addNodesToResults.call(this, result, result.snapshotLength, result.snapshotItem); + } + + function finishedSearching() + { + // Remove the searchResultsProperty now that the search is finished. + for (var i = 0; i < InjectedScript._searchResults.length; ++i) + delete InjectedScript._searchResults[i][searchResultsProperty]; + } + + const mainFrameDocument = InjectedScript._window().document; + const searchDocuments = [mainFrameDocument]; + var searchFunctions; + if (tagNameQuery && startTagFound && endTagFound) + searchFunctions = [matchExactTagNames, matchPlainText]; + else if (tagNameQuery && startTagFound) + searchFunctions = [matchStartOfTagNames, matchPlainText]; + else if (tagNameQuery && endTagFound) { + // FIXME: we should have a matchEndOfTagNames search function if endTagFound is true but not startTagFound. + // This requires ends-with() support in XPath, WebKit only supports starts-with() and contains(). + searchFunctions = [matchPartialTagNames, matchPlainText]; + } else if (whitespaceTrimmedQuery === "//*" || whitespaceTrimmedQuery === "*") { + // These queries will match every node. Matching everything isn't useful and can be slow for large pages, + // so limit the search functions list to plain text and attribute matching. + searchFunctions = [matchPartialAttributeValues, matchPlainText]; + } else + searchFunctions = [matchExactItems, matchStyleSelector, matchPartialTagNamesAndAttributeValues, matchPlainText, matchXPathQuery]; + + // Find all frames, iframes and object elements to search their documents. + const querySelectorAllFunction = InjectedScript._window().Document.prototype.querySelectorAll; + const subdocumentResult = querySelectorAllFunction.call(mainFrameDocument, "iframe, frame, object"); + + for (var i = 0; i < subdocumentResult.length; ++i) { + var element = subdocumentResult.item(i); + if (element.contentDocument) + searchDocuments.push(element.contentDocument); + } + + const panel = InjectedScript; + var documentIndex = 0; + var searchFunctionIndex = 0; + var chunkIntervalIdentifier = null; + + // Split up the work into chunks so we don't block the UI thread while processing. + + function processChunk() + { + var searchDocument = searchDocuments[documentIndex]; + var searchFunction = searchFunctions[searchFunctionIndex]; + + if (++searchFunctionIndex > searchFunctions.length) { + searchFunction = searchFunctions[0]; + searchFunctionIndex = 0; + + if (++documentIndex > searchDocuments.length) { + if (panel._currentSearchChunkIntervalIdentifier === chunkIntervalIdentifier) + delete panel._currentSearchChunkIntervalIdentifier; + clearInterval(chunkIntervalIdentifier); + finishedSearching.call(panel); + return; + } + + searchDocument = searchDocuments[documentIndex]; + } + + if (!searchDocument || !searchFunction) + return; + + try { + searchFunction.call(panel, searchDocument); + } catch(err) { + // ignore any exceptions. the query might be malformed, but we allow that. + } + } + + processChunk(); + + chunkIntervalIdentifier = setInterval(processChunk, 25); + InjectedScript._currentSearchChunkIntervalIdentifier = chunkIntervalIdentifier; + return true; +} + +InjectedScript.searchCanceled = function() +{ + if (InjectedScript._searchResults) { + const searchResultsProperty = InjectedScript._includedInSearchResultsPropertyName; + for (var i = 0; i < this._searchResults.length; ++i) { + var node = this._searchResults[i]; + + // Remove the searchResultsProperty since there might be an unfinished search. + delete node[searchResultsProperty]; + } + } + + if (InjectedScript._currentSearchChunkIntervalIdentifier) { + clearInterval(InjectedScript._currentSearchChunkIntervalIdentifier); + delete InjectedScript._currentSearchChunkIntervalIdentifier; + } + InjectedScript._searchResults = []; + return true; +} + +InjectedScript.openInInspectedWindow = function(url) +{ + InjectedScript._window().open(url); +} + +InjectedScript.getCallFrames = function() +{ + var callFrame = InspectorController.currentCallFrame(); + if (!callFrame) + return false; + + var result = []; + var depth = 0; + do { + result.push(new InjectedScript.CallFrameProxy(depth++, callFrame)); + callFrame = callFrame.caller; + } while (callFrame); + return result; +} + +InjectedScript.evaluateInCallFrame = function(callFrameId, code) +{ + var callFrame = InjectedScript._callFrameForId(callFrameId); + if (!callFrame) + return false; + return InjectedScript._evaluateOn(callFrame.evaluate, callFrame, code); +} + +InjectedScript._callFrameForId = function(id) +{ + var callFrame = InspectorController.currentCallFrame(); + while (--id >= 0 && callFrame) + callFrame = callFrame.caller; + return callFrame; +} + +InjectedScript._clearConsoleMessages = function() +{ + InspectorController.clearMessages(true); +} + +InjectedScript._inspectObject = function(o) +{ + if (arguments.length === 0) + return; + + var inspectedWindow = InjectedScript._window(); + inspectedWindow.console.log(o); + if (Object.type(o) === "node") { + InspectorController.pushNodePathToFrontend(o, true); + } else { + switch (Object.describe(o)) { + case "Database": + InspectorController.selectDatabase(o); + break; + case "Storage": + InspectorController.selectDOMStorage(o); + break; + } + } +} + +InjectedScript._ensureCommandLineAPIInstalled = function(inspectedWindow) +{ + var inspectedWindow = InjectedScript._window(); + if (inspectedWindow._inspectorCommandLineAPI) + return; + + inspectedWindow.eval("window._inspectorCommandLineAPI = { \ + $: function() { return document.getElementById.apply(document, arguments) }, \ + $$: function() { return document.querySelectorAll.apply(document, arguments) }, \ + $x: function(xpath, context) { \ + var nodes = []; \ + try { \ + var doc = context || document; \ + var results = doc.evaluate(xpath, doc, null, XPathResult.ANY_TYPE, null); \ + var node; \ + while (node = results.iterateNext()) nodes.push(node); \ + } catch (e) {} \ + return nodes; \ + }, \ + dir: function() { return console.dir.apply(console, arguments) }, \ + dirxml: function() { return console.dirxml.apply(console, arguments) }, \ + keys: function(o) { var a = []; for (var k in o) a.push(k); return a; }, \ + values: function(o) { var a = []; for (var k in o) a.push(o[k]); return a; }, \ + profile: function() { return console.profile.apply(console, arguments) }, \ + profileEnd: function() { return console.profileEnd.apply(console, arguments) }, \ + _inspectedNodes: [], \ + get $0() { return _inspectorCommandLineAPI._inspectedNodes[0] }, \ + get $1() { return _inspectorCommandLineAPI._inspectedNodes[1] }, \ + get $2() { return _inspectorCommandLineAPI._inspectedNodes[2] }, \ + get $3() { return _inspectorCommandLineAPI._inspectedNodes[3] }, \ + get $4() { return _inspectorCommandLineAPI._inspectedNodes[4] } \ + };"); + + inspectedWindow._inspectorCommandLineAPI.clear = InspectorController.wrapCallback(InjectedScript._clearConsoleMessages); + inspectedWindow._inspectorCommandLineAPI.inspect = InspectorController.wrapCallback(InjectedScript._inspectObject); +} + +InjectedScript._resolveObject = function(objectProxy) +{ + var object = InjectedScript._objectForId(objectProxy.objectId); + var path = objectProxy.path; + var protoDepth = objectProxy.protoDepth; + + // Follow the property path. + for (var i = 0; object && path && i < path.length; ++i) + object = object[path[i]]; + + // Get to the necessary proto layer. + for (var i = 0; object && protoDepth && i < protoDepth; ++i) + object = object.__proto__; + + return object; +} + +InjectedScript._window = function() +{ + // TODO: replace with 'return window;' once this script is injected into + // the page's context. + return InspectorController.inspectedWindow(); +} + +InjectedScript._nodeForId = function(nodeId) +{ + if (!nodeId) + return null; + return InspectorController.nodeForId(nodeId); +} + +InjectedScript._objectForId = function(objectId) +{ + // There are three types of object ids used: + // - numbers point to DOM Node via the InspectorDOMAgent mapping + // - strings point to console objects cached in InspectorController for lazy evaluation upon them + // - objects contain complex ids and are currently used for scoped objects + if (typeof objectId === "number") { + return InjectedScript._nodeForId(objectId); + } else if (typeof objectId === "string") { + return InspectorController.unwrapObject(objectId); + } else if (typeof objectId === "object") { + var callFrame = InjectedScript._callFrameForId(objectId.callFrame); + if (objectId.thisObject) + return callFrame.thisObject; + else + return callFrame.scopeChain[objectId.chainIndex]; + } + return objectId; +} + +InjectedScript.pushNodeToFrontend = function(objectProxy) +{ + var object = InjectedScript._resolveObject(objectProxy); + if (!object || Object.type(object) !== "node") + return false; + return InspectorController.pushNodePathToFrontend(object, false); +} + +// Called from within InspectorController on the 'inspected page' side. +InjectedScript.createProxyObject = function(object, objectId, abbreviate) +{ + var result = {}; + result.objectId = objectId; + result.type = Object.type(object); + + var type = typeof object; + if ((type === "object" && object !== null) || type === "function") { + for (var subPropertyName in object) { + result.hasChildren = true; + break; + } + } + try { + result.description = Object.describe(object, abbreviate); + } catch (e) { + result.exception = e.toString(); + } + return result; +} + +InjectedScript.CallFrameProxy = function(id, callFrame) +{ + this.id = id; + this.type = callFrame.type; + this.functionName = (this.type === "function" ? callFrame.functionName : ""); + this.sourceID = callFrame.sourceID; + this.line = callFrame.line; + this.scopeChain = this._wrapScopeChain(callFrame); +} + +InjectedScript.CallFrameProxy.prototype = { + _wrapScopeChain: function(callFrame) + { + var foundLocalScope = false; + var scopeChain = callFrame.scopeChain; + var scopeChainProxy = []; + for (var i = 0; i < scopeChain.length; ++i) { + var scopeObject = scopeChain[i]; + var scopeObjectProxy = InjectedScript.createProxyObject(scopeObject, { callFrame: this.id, chainIndex: i }); + + if (Object.prototype.toString.call(scopeObject) === "[object JSActivation]") { + if (!foundLocalScope) + scopeObjectProxy.thisObject = InjectedScript.createProxyObject(callFrame.thisObject, { callFrame: this.id, thisObject: true }); + else + scopeObjectProxy.isClosure = true; + foundLocalScope = true; + scopeObjectProxy.isLocal = true; + } else if (foundLocalScope && scopeObject instanceof InjectedScript._window().Element) + scopeObjectProxy.isElement = true; + else if (foundLocalScope && scopeObject instanceof InjectedScript._window().Document) + scopeObjectProxy.isDocument = true; + else if (!foundLocalScope) + scopeObjectProxy.isWithBlock = true; + scopeObjectProxy.properties = []; + try { + for (var propertyName in scopeObject) + scopeObjectProxy.properties.push(propertyName); + } catch (e) { + } + scopeChainProxy.push(scopeObjectProxy); + } + return scopeChainProxy; + } +} + +Object.type = function(obj) +{ + if (obj === null) + return "null"; + + var type = typeof obj; + if (type !== "object" && type !== "function") + return type; + + var win = InjectedScript._window(); + + if (obj instanceof win.Node) + return (obj.nodeType === undefined ? type : "node"); + if (obj instanceof win.String) + return "string"; + if (obj instanceof win.Array) + return "array"; + if (obj instanceof win.Boolean) + return "boolean"; + if (obj instanceof win.Number) + return "number"; + if (obj instanceof win.Date) + return "date"; + if (obj instanceof win.RegExp) + return "regexp"; + if (obj instanceof win.Error) + return "error"; + return type; +} + +Object.hasProperties = function(obj) +{ + if (typeof obj === "undefined" || typeof obj === "null") + return false; + for (var name in obj) + return true; + return false; +} + +Object.describe = function(obj, abbreviated) +{ + var type1 = Object.type(obj); + var type2 = Object.className(obj); + + switch (type1) { + case "object": + case "node": + return type2; + case "array": + return "[" + obj.toString() + "]"; + case "string": + if (obj.length > 100) + return "\"" + obj.substring(0, 100) + "\u2026\""; + return "\"" + obj + "\""; + case "function": + var objectText = String(obj); + if (!/^function /.test(objectText)) + objectText = (type2 == "object") ? type1 : type2; + else if (abbreviated) + objectText = /.*/.exec(obj)[0].replace(/ +$/g, ""); + return objectText; + case "regexp": + return String(obj).replace(/([\\\/])/g, "\\$1").replace(/\\(\/[gim]*)$/, "$1").substring(1); + default: + return String(obj); + } +} + +Object.className = function(obj) +{ + return Object.prototype.toString.call(obj).replace(/^\[object (.*)\]$/i, "$1") +} + +// Although Function.prototype.bind and String.prototype.escapeCharacters are defined in utilities.js they will soon become +// unavailable in the InjectedScript context. So we define them here for the local use. +// TODO: remove this comment once InjectedScript runs in a separate context. +Function.prototype.bind = function(thisObject) +{ + var func = this; + var args = Array.prototype.slice.call(arguments, 1); + return function() { return func.apply(thisObject, args.concat(Array.prototype.slice.call(arguments, 0))) }; +} + +String.prototype.escapeCharacters = function(chars) +{ + var foundChar = false; + for (var i = 0; i < chars.length; ++i) { + if (this.indexOf(chars.charAt(i)) !== -1) { + foundChar = true; + break; + } + } + + if (!foundChar) + return this; + + var result = ""; + for (var i = 0; i < this.length; ++i) { + if (chars.indexOf(this.charAt(i)) !== -1) + result += "\\"; + result += this.charAt(i); + } + + return result; +} diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/InjectedScriptAccess.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/InjectedScriptAccess.js new file mode 100644 index 0000000..da85d03 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/InjectedScriptAccess.js @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2009 Google Inc. All rights reserved. + * Copyright (C) 2009 Joseph Pecoraro + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +var InjectedScriptAccess = {}; + +InjectedScriptAccess._installHandler = function(methodName) +{ + InjectedScriptAccess[methodName] = function() + { + var allArgs = Array.prototype.slice.call(arguments); + var callback = allArgs[allArgs.length - 1]; + var argsString = JSON.stringify(Array.prototype.slice.call(allArgs, 0, allArgs.length - 1)); + + function myCallback(result, isException) + { + if (!isException) + callback(JSON.parse(result)); + else + WebInspector.console.addMessage(new WebInspector.ConsoleTextMessage("Error dispatching: " + methodName)); + } + var callId = WebInspector.Callback.wrap(myCallback); + InspectorController.dispatchOnInjectedScript(callId, methodName, argsString); + }; +} + +InjectedScriptAccess._installHandler("getStyles"); +InjectedScriptAccess._installHandler("getComputedStyle"); +InjectedScriptAccess._installHandler("getInlineStyle"); +InjectedScriptAccess._installHandler("applyStyleText"); +InjectedScriptAccess._installHandler("setStyleText"); +InjectedScriptAccess._installHandler("toggleStyleEnabled"); +InjectedScriptAccess._installHandler("applyStyleRuleText"); +InjectedScriptAccess._installHandler("addStyleSelector"); +InjectedScriptAccess._installHandler("setStyleProperty"); +InjectedScriptAccess._installHandler("getPrototypes"); +InjectedScriptAccess._installHandler("getProperties"); +InjectedScriptAccess._installHandler("setPropertyValue"); +InjectedScriptAccess._installHandler("getCompletions"); +InjectedScriptAccess._installHandler("evaluate"); +InjectedScriptAccess._installHandler("addInspectedNode"); +InjectedScriptAccess._installHandler("pushNodeToFrontend"); +InjectedScriptAccess._installHandler("evaluate"); +InjectedScriptAccess._installHandler("addInspectedNode"); +InjectedScriptAccess._installHandler("pushNodeToFrontend"); +InjectedScriptAccess._installHandler("performSearch"); +InjectedScriptAccess._installHandler("searchCanceled"); +InjectedScriptAccess._installHandler("openInInspectedWindow"); +InjectedScriptAccess._installHandler("evaluateInCallFrame"); + +WebInspector.didDispatchOnInjectedScript = WebInspector.Callback.processCallback; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/KeyboardShortcut.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/KeyboardShortcut.js new file mode 100644 index 0000000..ed28a48 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/KeyboardShortcut.js @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2009 Apple Inc. All rights reserved. + * Copyright (C) 2009 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.KeyboardShortcut = function() +{ +}; + +/** + * Constants for encoding modifier key set as a bit mask. + * @see #_makeKeyFromCodeAndModifiers + */ +WebInspector.KeyboardShortcut.Modifiers = { + None: 0, // Constant for empty modifiers set. + Shift: 1, + Ctrl: 2, + Alt: 4, + Meta: 8 // Command key on Mac, Win key on other platforms. +}; + +WebInspector.KeyboardShortcut.KeyCodes = { + Esc: 27, + Space: 32, + PageUp: 33, // also NUM_NORTH_EAST + PageDown: 34, // also NUM_SOUTH_EAST + End: 35, // also NUM_SOUTH_WEST + Home: 36, // also NUM_NORTH_WEST + Left: 37, // also NUM_WEST + Up: 38, // also NUM_NORTH + Right: 39, // also NUM_EAST + Down: 40, // also NUM_SOUTH + F1: 112, + F2: 113, + F3: 114, + F4: 115, + F5: 116, + F6: 117, + F7: 118, + F8: 119, + F9: 120, + F10: 121, + F11: 122, + F12: 123, + Semicolon: 186, // ; + Comma: 188, // , + Period: 190, // . + Slash: 191, // / + Apostrophe: 192, // ` + SingleQuote: 222, // ' +}; + +/** + * Creates a number encoding keyCode in the lower 8 bits and modifiers mask in the higher 8 bits. + * It is usefull for matching pressed keys. + * @param {number} keyCode Code of the key. + * @param {number} optModifiers Optional list of modifiers passed as additional paramerters. + */ +WebInspector.KeyboardShortcut.makeKey = function(keyCode, optModifiers) +{ + var modifiers = WebInspector.KeyboardShortcut.Modifiers.None; + for (var i = 1; i < arguments.length; i++) + modifiers |= arguments[i]; + return WebInspector.KeyboardShortcut._makeKeyFromCodeAndModifiers(keyCode, modifiers); +}; + +WebInspector.KeyboardShortcut.makeKeyFromEvent = function(keyboardEvent) +{ + var modifiers = WebInspector.KeyboardShortcut.Modifiers.None; + if (keyboardEvent.shiftKey) + modifiers |= WebInspector.KeyboardShortcut.Modifiers.Shift; + if (keyboardEvent.ctrlKey) + modifiers |= WebInspector.KeyboardShortcut.Modifiers.Ctrl; + if (keyboardEvent.altKey) + modifiers |= WebInspector.KeyboardShortcut.Modifiers.Alt; + if (keyboardEvent.metaKey) + modifiers |= WebInspector.KeyboardShortcut.Modifiers.Meta; + return WebInspector.KeyboardShortcut._makeKeyFromCodeAndModifiers(keyboardEvent.keyCode, modifiers); +}; + +WebInspector.KeyboardShortcut._makeKeyFromCodeAndModifiers = function(keyCode, modifiers) +{ + return (keyCode & 255) | (modifiers << 8); +}; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/MetricsSidebarPane.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/MetricsSidebarPane.js new file mode 100644 index 0000000..a33653b --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/MetricsSidebarPane.js @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.MetricsSidebarPane = function() +{ + WebInspector.SidebarPane.call(this, WebInspector.UIString("Metrics")); + this._inlineStyleId = null; +} + +WebInspector.MetricsSidebarPane.prototype = { + update: function(node) + { + var body = this.bodyElement; + + body.removeChildren(); + + if (node) + this.node = node; + else + node = this.node; + + if (!node || !node.ownerDocument.defaultView) + return; + + if (node.nodeType !== Node.ELEMENT_NODE) + return; + + var self = this; + var callback = function(stylePayload) { + if (!stylePayload) + return; + var style = WebInspector.CSSStyleDeclaration.parseStyle(stylePayload); + self._update(node, body, style); + }; + InjectedScriptAccess.getComputedStyle(node.id, callback); + + var inlineStyleCallback = function(stylePayload) { + if (!stylePayload) + return; + self._inlineStyleId = stylePayload.id; + }; + InjectedScriptAccess.getInlineStyle(node.id, inlineStyleCallback); + }, + + _update: function(node, body, style) + { + var metricsElement = document.createElement("div"); + metricsElement.className = "metrics"; + + function createBoxPartElement(style, name, side, suffix) + { + var propertyName = (name !== "position" ? name + "-" : "") + side + suffix; + var value = style.getPropertyValue(propertyName); + if (value === "" || (name !== "position" && value === "0px")) + value = "\u2012"; + else if (name === "position" && value === "auto") + value = "\u2012"; + value = value.replace(/px$/, ""); + + var element = document.createElement("div"); + element.className = side; + element.textContent = value; + element.addEventListener("dblclick", this.startEditing.bind(this, element, name, propertyName), false); + return element; + } + + // Display types for which margin is ignored. + var noMarginDisplayType = { + "table-cell": true, + "table-column": true, + "table-column-group": true, + "table-footer-group": true, + "table-header-group": true, + "table-row": true, + "table-row-group": true + }; + + // Display types for which padding is ignored. + var noPaddingDisplayType = { + "table-column": true, + "table-column-group": true, + "table-footer-group": true, + "table-header-group": true, + "table-row": true, + "table-row-group": true + }; + + // Position types for which top, left, bottom and right are ignored. + var noPositionType = { + "static": true + }; + + var boxes = ["content", "padding", "border", "margin", "position"]; + var boxLabels = [WebInspector.UIString("content"), WebInspector.UIString("padding"), WebInspector.UIString("border"), WebInspector.UIString("margin"), WebInspector.UIString("position")]; + var previousBox; + for (var i = 0; i < boxes.length; ++i) { + var name = boxes[i]; + + if (name === "margin" && noMarginDisplayType[style.display]) + continue; + if (name === "padding" && noPaddingDisplayType[style.display]) + continue; + if (name === "position" && noPositionType[style.position]) + continue; + + var boxElement = document.createElement("div"); + boxElement.className = name; + + if (name === "content") { + var width = style.width.replace(/px$/, ""); + var widthElement = document.createElement("span"); + widthElement.textContent = width; + widthElement.addEventListener("dblclick", this.startEditing.bind(this, widthElement, "width", "width"), false); + + var height = style.height.replace(/px$/, ""); + var heightElement = document.createElement("span"); + heightElement.textContent = height; + heightElement.addEventListener("dblclick", this.startEditing.bind(this, heightElement, "height", "height"), false); + + boxElement.appendChild(widthElement); + boxElement.appendChild(document.createTextNode(" \u00D7 ")); + boxElement.appendChild(heightElement); + } else { + var suffix = (name === "border" ? "-width" : ""); + + var labelElement = document.createElement("div"); + labelElement.className = "label"; + labelElement.textContent = boxLabels[i]; + boxElement.appendChild(labelElement); + + boxElement.appendChild(createBoxPartElement.call(this, style, name, "top", suffix)); + boxElement.appendChild(document.createElement("br")); + boxElement.appendChild(createBoxPartElement.call(this, style, name, "left", suffix)); + + if (previousBox) + boxElement.appendChild(previousBox); + + boxElement.appendChild(createBoxPartElement.call(this, style, name, "right", suffix)); + boxElement.appendChild(document.createElement("br")); + boxElement.appendChild(createBoxPartElement.call(this, style, name, "bottom", suffix)); + } + + previousBox = boxElement; + } + + metricsElement.appendChild(previousBox); + body.appendChild(metricsElement); + }, + + startEditing: function(targetElement, box, styleProperty) + { + if (WebInspector.isBeingEdited(targetElement)) + return; + + var context = { box: box, styleProperty: styleProperty }; + + WebInspector.startEditing(targetElement, this.editingCommitted.bind(this), this.editingCancelled.bind(this), context); + }, + + editingCancelled: function(element, context) + { + this.update(); + }, + + editingCommitted: function(element, userInput, previousContent, context) + { + if (userInput === previousContent) + return this.editingCancelled(element, context); // nothing changed, so cancel + + if (context.box !== "position" && (!userInput || userInput === "\u2012")) + userInput = "0px"; + else if (context.box === "position" && (!userInput || userInput === "\u2012")) + userInput = "auto"; + + // Append a "px" unit if the user input was just a number. + if (/^\d+$/.test(userInput)) + userInput += "px"; + + var self = this; + var callback = function(success) { + if (!success) + return; + self.dispatchEventToListeners("metrics edited"); + self.update(); + }; + InjectedScriptAccess.setStyleProperty(this._inlineStyleId, context.styleProperty, userInput, callback); + } +} + +WebInspector.MetricsSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Object.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Object.js new file mode 100644 index 0000000..80202b0 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Object.js @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.Object = function() { +} + +WebInspector.Object.prototype = { + addEventListener: function(eventType, listener, thisObject) { + if (!("_listeners" in this)) + this._listeners = {}; + if (!(eventType in this._listeners)) + this._listeners[eventType] = []; + this._listeners[eventType].push({ thisObject: thisObject, listener: listener }); + }, + + removeEventListener: function(eventType, listener, thisObject) { + if (!("_listeners" in this) || !(eventType in this._listeners)) + return; + var listeners = this._listeners[eventType]; + for (var i = 0; i < listeners.length; ++i) { + if (listener && listeners[i].listener === listener && listeners[i].thisObject === thisObject) + listeners.splice(i, 1); + else if (!listener && thisObject && listeners[i].thisObject === thisObject) + listeners.splice(i, 1); + } + + if (!listeners.length) + delete this._listeners[eventType]; + }, + + dispatchEventToListeners: function(eventType) { + if (!("_listeners" in this) || !(eventType in this._listeners)) + return; + + var stoppedPropagation = false; + + function stopPropagation() + { + stoppedPropagation = true; + } + + function preventDefault() + { + this.defaultPrevented = true; + } + + var event = {target: this, type: eventType, defaultPrevented: false}; + event.stopPropagation = stopPropagation.bind(event); + event.preventDefault = preventDefault.bind(event); + + var listeners = this._listeners[eventType]; + for (var i = 0; i < listeners.length; ++i) { + listeners[i].listener.call(listeners[i].thisObject, event); + if (stoppedPropagation) + break; + } + + return event.defaultPrevented; + } +} diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ObjectPropertiesSection.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ObjectPropertiesSection.js new file mode 100644 index 0000000..8bb4e35 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ObjectPropertiesSection.js @@ -0,0 +1,258 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * Copyright (C) 2009 Joseph Pecoraro + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.ObjectPropertiesSection = function(object, title, subtitle, emptyPlaceholder, ignoreHasOwnProperty, extraProperties, treeElementConstructor) +{ + this.emptyPlaceholder = (emptyPlaceholder || WebInspector.UIString("No Properties")); + this.object = object; + this.ignoreHasOwnProperty = ignoreHasOwnProperty; + this.extraProperties = extraProperties; + this.treeElementConstructor = treeElementConstructor || WebInspector.ObjectPropertyTreeElement; + this.editable = true; + + WebInspector.PropertiesSection.call(this, title, subtitle); +} + +WebInspector.ObjectPropertiesSection.prototype = { + onpopulate: function() + { + this.update(); + }, + + update: function() + { + var self = this; + var callback = function(properties) { + if (!properties) + return; + self.updateProperties(properties); + }; + InjectedScriptAccess.getProperties(this.object, this.ignoreHasOwnProperty, callback); + }, + + updateProperties: function(properties, rootTreeElementConstructor, rootPropertyComparer) + { + if (!rootTreeElementConstructor) + rootTreeElementConstructor = this.treeElementConstructor; + + if (!rootPropertyComparer) + rootPropertyComparer = WebInspector.ObjectPropertiesSection.CompareProperties; + + if (this.extraProperties) + for (var i = 0; i < this.extraProperties.length; ++i) + properties.push(this.extraProperties[i]); + + properties.sort(rootPropertyComparer); + + this.propertiesTreeOutline.removeChildren(); + + for (var i = 0; i < properties.length; ++i) + this.propertiesTreeOutline.appendChild(new rootTreeElementConstructor(properties[i])); + + if (!this.propertiesTreeOutline.children.length) { + var title = "<div class=\"info\">" + this.emptyPlaceholder + "</div>"; + var infoElement = new TreeElement(title, null, false); + this.propertiesTreeOutline.appendChild(infoElement); + } + } +} + +WebInspector.ObjectPropertiesSection.prototype.__proto__ = WebInspector.PropertiesSection.prototype; + +WebInspector.ObjectPropertiesSection.CompareProperties = function(propertyA, propertyB) +{ + var a = propertyA.name; + var b = propertyB.name; + + // if used elsewhere make sure to + // - convert a and b to strings (not needed here, properties are all strings) + // - check if a == b (not needed here, no two properties can be the same) + + var diff = 0; + var chunk = /^\d+|^\D+/; + var chunka, chunkb, anum, bnum; + while (diff === 0) { + if (!a && b) + return -1; + if (!b && a) + return 1; + chunka = a.match(chunk)[0]; + chunkb = b.match(chunk)[0]; + anum = !isNaN(chunka); + bnum = !isNaN(chunkb); + if (anum && !bnum) + return -1; + if (bnum && !anum) + return 1; + if (anum && bnum) { + diff = chunka - chunkb; + if (diff === 0 && chunka.length !== chunkb.length) { + if (!+chunka && !+chunkb) // chunks are strings of all 0s (special case) + return chunka.length - chunkb.length; + else + return chunkb.length - chunka.length; + } + } else if (chunka !== chunkb) + return (chunka < chunkb) ? -1 : 1; + a = a.substring(chunka.length); + b = b.substring(chunkb.length); + } + return diff; +} + +WebInspector.ObjectPropertyTreeElement = function(property) +{ + this.property = property; + + // Pass an empty title, the title gets made later in onattach. + TreeElement.call(this, "", null, false); +} + +WebInspector.ObjectPropertyTreeElement.prototype = { + onpopulate: function() + { + if (this.children.length && !this.shouldRefreshChildren) + return; + + var callback = function(properties) { + this.removeChildren(); + if (!properties) + return; + + properties.sort(WebInspector.ObjectPropertiesSection.CompareProperties); + for (var i = 0; i < properties.length; ++i) { + this.appendChild(new this.treeOutline.section.treeElementConstructor(properties[i])); + } + }; + InjectedScriptAccess.getProperties(this.property.value, false, callback.bind(this)); + }, + + ondblclick: function(element, event) + { + this.startEditing(); + }, + + onattach: function() + { + this.update(); + }, + + update: function() + { + this.nameElement = document.createElement("span"); + this.nameElement.className = "name"; + this.nameElement.textContent = this.property.name; + + var separatorElement = document.createElement("span"); + separatorElement.className = "separator"; + separatorElement.textContent = ": "; + + this.valueElement = document.createElement("span"); + this.valueElement.className = "value"; + this.valueElement.textContent = this.property.value.description; + if (this.property.isGetter) + this.valueElement.addStyleClass("dimmed"); + + this.listItemElement.removeChildren(); + + this.listItemElement.appendChild(this.nameElement); + this.listItemElement.appendChild(separatorElement); + this.listItemElement.appendChild(this.valueElement); + this.hasChildren = this.property.value.hasChildren; + }, + + updateSiblings: function() + { + if (this.parent.root) + this.treeOutline.section.update(); + else + this.parent.shouldRefreshChildren = true; + }, + + startEditing: function() + { + if (WebInspector.isBeingEdited(this.valueElement) || !this.treeOutline.section.editable) + return; + + var context = { expanded: this.expanded }; + + // Lie about our children to prevent expanding on double click and to collapse subproperties. + this.hasChildren = false; + + this.listItemElement.addStyleClass("editing-sub-part"); + + WebInspector.startEditing(this.valueElement, this.editingCommitted.bind(this), this.editingCancelled.bind(this), context); + }, + + editingEnded: function(context) + { + this.listItemElement.scrollLeft = 0; + this.listItemElement.removeStyleClass("editing-sub-part"); + if (context.expanded) + this.expand(); + }, + + editingCancelled: function(element, context) + { + this.update(); + this.editingEnded(context); + }, + + editingCommitted: function(element, userInput, previousContent, context) + { + if (userInput === previousContent) + return this.editingCancelled(element, context); // nothing changed, so cancel + + this.applyExpression(userInput, true); + + this.editingEnded(context); + }, + + applyExpression: function(expression, updateInterface) + { + expression = expression.trimWhitespace(); + var expressionLength = expression.length; + var self = this; + var callback = function(success) { + if (!updateInterface) + return; + + if (!success) + self.update(); + + if (!expressionLength) { + // The property was deleted, so remove this tree element. + self.parent.removeChild(this); + } else { + // Call updateSiblings since their value might be based on the value that just changed. + self.updateSiblings(); + } + }; + InjectedScriptAccess.setPropertyValue(this.property.parentObjectProxy, this.property.name, expression.trimWhitespace(), callback); + } +} + +WebInspector.ObjectPropertyTreeElement.prototype.__proto__ = TreeElement.prototype; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ObjectProxy.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ObjectProxy.js new file mode 100644 index 0000000..03d16ab --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ObjectProxy.js @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2009 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.ObjectProxy = function(objectId, path, protoDepth, description, hasChildren) +{ + this.objectId = objectId; + this.path = path || []; + this.protoDepth = protoDepth || 0; + this.description = description; + this.hasChildren = hasChildren; +} + +WebInspector.ObjectPropertyProxy = function(name, value) +{ + this.name = name; + this.value = value; +} diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Panel.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Panel.js new file mode 100644 index 0000000..5046f6b --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Panel.js @@ -0,0 +1,273 @@ +/* + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.Panel = function() +{ + WebInspector.View.call(this); + + this.element.addStyleClass("panel"); +} + +WebInspector.Panel.prototype = { + get toolbarItem() + { + if (this._toolbarItem) + return this._toolbarItem; + + // Sample toolbar item as markup: + // <button class="toolbar-item resources toggleable"> + // <div class="toolbar-icon"></div> + // <div class="toolbar-label">Resources</div> + // </button> + + this._toolbarItem = document.createElement("button"); + this._toolbarItem.className = "toolbar-item toggleable"; + this._toolbarItem.panel = this; + + if ("toolbarItemClass" in this) + this._toolbarItem.addStyleClass(this.toolbarItemClass); + + var iconElement = document.createElement("div"); + iconElement.className = "toolbar-icon"; + this._toolbarItem.appendChild(iconElement); + + if ("toolbarItemLabel" in this) { + var labelElement = document.createElement("div"); + labelElement.className = "toolbar-label"; + labelElement.textContent = this.toolbarItemLabel; + this._toolbarItem.appendChild(labelElement); + } + + return this._toolbarItem; + }, + + show: function() + { + WebInspector.View.prototype.show.call(this); + + var statusBarItems = this.statusBarItems; + if (statusBarItems) { + this._statusBarItemContainer = document.createElement("div"); + for (var i = 0; i < statusBarItems.length; ++i) + this._statusBarItemContainer.appendChild(statusBarItems[i]); + document.getElementById("main-status-bar").appendChild(this._statusBarItemContainer); + } + + if ("_toolbarItem" in this) + this._toolbarItem.addStyleClass("toggled-on"); + + WebInspector.currentFocusElement = document.getElementById("main-panels"); + }, + + hide: function() + { + WebInspector.View.prototype.hide.call(this); + + if (this._statusBarItemContainer && this._statusBarItemContainer.parentNode) + this._statusBarItemContainer.parentNode.removeChild(this._statusBarItemContainer); + delete this._statusBarItemContainer; + if ("_toolbarItem" in this) + this._toolbarItem.removeStyleClass("toggled-on"); + }, + + attach: function() + { + if (!this.element.parentNode) + document.getElementById("main-panels").appendChild(this.element); + }, + + searchCanceled: function(startingNewSearch) + { + if (this._searchResults) { + for (var i = 0; i < this._searchResults.length; ++i) { + var view = this._searchResults[i]; + if (view.searchCanceled) + view.searchCanceled(); + delete view.currentQuery; + } + } + + WebInspector.updateSearchMatchesCount(0, this); + + if (this._currentSearchChunkIntervalIdentifier) { + clearInterval(this._currentSearchChunkIntervalIdentifier); + delete this._currentSearchChunkIntervalIdentifier; + } + + this._totalSearchMatches = 0; + this._currentSearchResultIndex = 0; + this._searchResults = []; + }, + + performSearch: function(query) + { + // Call searchCanceled since it will reset everything we need before doing a new search. + this.searchCanceled(true); + + var searchableViews = this.searchableViews; + if (!searchableViews || !searchableViews.length) + return; + + var parentElement = this.viewsContainerElement; + var visibleView = this.visibleView; + var sortFuction = this.searchResultsSortFunction; + + var matchesCountUpdateTimeout = null; + + function updateMatchesCount() + { + WebInspector.updateSearchMatchesCount(this._totalSearchMatches, this); + matchesCountUpdateTimeout = null; + } + + function updateMatchesCountSoon() + { + if (matchesCountUpdateTimeout) + return; + // Update the matches count every half-second so it doesn't feel twitchy. + matchesCountUpdateTimeout = setTimeout(updateMatchesCount.bind(this), 500); + } + + function finishedCallback(view, searchMatches) + { + if (!searchMatches) + return; + + this._totalSearchMatches += searchMatches; + this._searchResults.push(view); + + if (sortFuction) + this._searchResults.sort(sortFuction); + + if (this.searchMatchFound) + this.searchMatchFound(view, searchMatches); + + updateMatchesCountSoon.call(this); + + if (view === visibleView) + view.jumpToFirstSearchResult(); + } + + var i = 0; + var panel = this; + var boundFinishedCallback = finishedCallback.bind(this); + var chunkIntervalIdentifier = null; + + // Split up the work into chunks so we don't block the + // UI thread while processing. + + function processChunk() + { + var view = searchableViews[i]; + + if (++i >= searchableViews.length) { + if (panel._currentSearchChunkIntervalIdentifier === chunkIntervalIdentifier) + delete panel._currentSearchChunkIntervalIdentifier; + clearInterval(chunkIntervalIdentifier); + } + + if (!view) + return; + + if (view.element.parentNode !== parentElement && view.element.parentNode && parentElement) + view.detach(); + + view.currentQuery = query; + view.performSearch(query, boundFinishedCallback); + } + + processChunk(); + + chunkIntervalIdentifier = setInterval(processChunk, 25); + this._currentSearchChunkIntervalIdentifier = chunkIntervalIdentifier; + }, + + jumpToNextSearchResult: function() + { + if (!this.showView || !this._searchResults || !this._searchResults.length) + return; + + var showFirstResult = false; + + this._currentSearchResultIndex = this._searchResults.indexOf(this.visibleView); + if (this._currentSearchResultIndex === -1) { + this._currentSearchResultIndex = 0; + showFirstResult = true; + } + + var currentView = this._searchResults[this._currentSearchResultIndex]; + + if (currentView.showingLastSearchResult()) { + if (++this._currentSearchResultIndex >= this._searchResults.length) + this._currentSearchResultIndex = 0; + currentView = this._searchResults[this._currentSearchResultIndex]; + showFirstResult = true; + } + + if (currentView !== this.visibleView) + this.showView(currentView); + + if (showFirstResult) + currentView.jumpToFirstSearchResult(); + else + currentView.jumpToNextSearchResult(); + }, + + jumpToPreviousSearchResult: function() + { + if (!this.showView || !this._searchResults || !this._searchResults.length) + return; + + var showLastResult = false; + + this._currentSearchResultIndex = this._searchResults.indexOf(this.visibleView); + if (this._currentSearchResultIndex === -1) { + this._currentSearchResultIndex = 0; + showLastResult = true; + } + + var currentView = this._searchResults[this._currentSearchResultIndex]; + + if (currentView.showingFirstSearchResult()) { + if (--this._currentSearchResultIndex < 0) + this._currentSearchResultIndex = (this._searchResults.length - 1); + currentView = this._searchResults[this._currentSearchResultIndex]; + showLastResult = true; + } + + if (currentView !== this.visibleView) + this.showView(currentView); + + if (showLastResult) + currentView.jumpToLastSearchResult(); + else + currentView.jumpToPreviousSearchResult(); + } +} + +WebInspector.Panel.prototype.__proto__ = WebInspector.View.prototype; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/PanelEnablerView.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/PanelEnablerView.js new file mode 100644 index 0000000..fab6d76 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/PanelEnablerView.js @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.PanelEnablerView = function(identifier, headingText, disclaimerText, buttonTitle) +{ + WebInspector.View.call(this); + + this.element.addStyleClass("panel-enabler-view"); + this.element.addStyleClass(identifier); + + this.contentElement = document.createElement("div"); + this.contentElement.className = "panel-enabler-view-content"; + this.element.appendChild(this.contentElement); + + this.imageElement = document.createElement("img"); + this.contentElement.appendChild(this.imageElement); + + this.choicesForm = document.createElement("form"); + this.contentElement.appendChild(this.choicesForm); + + this.headerElement = document.createElement("h1"); + this.headerElement.textContent = headingText; + this.choicesForm.appendChild(this.headerElement); + + var self = this; + function enableOption(text, checked) { + var label = document.createElement("label"); + var option = document.createElement("input"); + option.type = "radio"; + option.name = "enable-option"; + if (checked) + option.checked = true; + label.appendChild(option); + label.appendChild(document.createTextNode(text)); + self.choicesForm.appendChild(label); + return option; + }; + + this.enabledForSession = enableOption(WebInspector.UIString("Only enable for this session"), true); + this.enabledAlways = enableOption(WebInspector.UIString("Always enable")); + + this.disclaimerElement = document.createElement("div"); + this.disclaimerElement.className = "panel-enabler-disclaimer"; + this.disclaimerElement.textContent = disclaimerText; + this.choicesForm.appendChild(this.disclaimerElement); + + this.enableButton = document.createElement("button"); + this.enableButton.setAttribute("type", "button"); + this.enableButton.textContent = buttonTitle; + this.enableButton.addEventListener("click", this._enableButtonCicked.bind(this), false); + this.choicesForm.appendChild(this.enableButton); + + window.addEventListener("resize", this._windowResized.bind(this), true); +} + +WebInspector.PanelEnablerView.prototype = { + _enableButtonCicked: function() + { + this.dispatchEventToListeners("enable clicked"); + }, + + _windowResized: function() + { + this.imageElement.removeStyleClass("hidden"); + + if (this.element.offsetWidth < (this.choicesForm.offsetWidth + this.imageElement.offsetWidth)) + this.imageElement.addStyleClass("hidden"); + }, + + get alwaysEnabled() { + return this.enabledAlways.checked; + } +} + +WebInspector.PanelEnablerView.prototype.__proto__ = WebInspector.View.prototype; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Placard.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Placard.js new file mode 100644 index 0000000..69a168e --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Placard.js @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.Placard = function(title, subtitle) +{ + this.element = document.createElement("div"); + this.element.className = "placard"; + this.element.placard = this; + + this.titleElement = document.createElement("div"); + this.titleElement.className = "title"; + + this.subtitleElement = document.createElement("div"); + this.subtitleElement.className = "subtitle"; + + this.element.appendChild(this.subtitleElement); + this.element.appendChild(this.titleElement); + + this.title = title; + this.subtitle = subtitle; + this.selected = false; +} + +WebInspector.Placard.prototype = { + get title() + { + return this._title; + }, + + set title(x) + { + if (this._title === x) + return; + this._title = x; + this.titleElement.textContent = x; + }, + + get subtitle() + { + return this._subtitle; + }, + + set subtitle(x) + { + if (this._subtitle === x) + return; + this._subtitle = x; + this.subtitleElement.innerHTML = x; + }, + + get selected() + { + return this._selected; + }, + + set selected(x) + { + if (x) + this.select(); + else + this.deselect(); + }, + + select: function() + { + if (this._selected) + return; + this._selected = true; + this.element.addStyleClass("selected"); + }, + + deselect: function() + { + if (!this._selected) + return; + this._selected = false; + this.element.removeStyleClass("selected"); + }, + + toggleSelected: function() + { + this.selected = !this.selected; + } +} diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Popup.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Popup.js new file mode 100644 index 0000000..9c8ef24 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Popup.js @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2009 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * This class provides a popup that can be shown relative to an anchor element + * or at an arbitrary absolute position. + * Points are Objects: {x: xValue, y: yValue}. + * Rectangles are Objects: {x: xValue, y: yValue, width: widthValue, height: heightValue}. + * + * element is an optional unparented visible element (style.display != "none" AND style.visibility != "hidden"). + * If the element is absent/undefined, it must have been set with the element(x) setter before the show() method invocation. + */ +WebInspector.Popup = function(element) +{ + if (element) + this.element = element; + this._keyHandler = this._keyEventHandler.bind(this); + this._mouseDownHandler = this._mouseDownEventHandler.bind(this); + this._visible = false; + this._autoHide = true; +} + +WebInspector.Popup.prototype = { + show: function() + { + if (this.visible) + return; + var ownerDocument = this._contentElement.ownerDocument; + if (!ownerDocument) + return; + + this._glasspaneElement = ownerDocument.createElement("div"); + this._glasspaneElement.className = "popup-glasspane"; + ownerDocument.body.appendChild(this._glasspaneElement); + + this._contentElement.positionAt(0, 0); + this._contentElement.removeStyleClass("hidden"); + ownerDocument.body.appendChild(this._contentElement); + + this.positionElement(); + this._visible = true; + ownerDocument.addEventListener("keydown", this._keyHandler, false); + ownerDocument.addEventListener("mousedown", this._mouseDownHandler, false); + }, + + hide: function() + { + if (this.visible) { + this._visible = false; + this._contentElement.ownerDocument.removeEventListener("keydown", this._keyHandler, false); + this._contentElement.ownerDocument.removeEventListener("mousedown", this._mouseDownHandler, false); + this._glasspaneElement.parentElement.removeChild(this._glasspaneElement); + this._contentElement.parentElement.removeChild(this._contentElement); + } + }, + + get visible() + { + return this._visible; + }, + + set element(x) + { + this._checkNotVisible(); + this._contentElement = x; + this._contentElement.addStyleClass("hidden"); + }, + + get element() + { + return this._contentElement; + }, + + positionElement: function() + { + var element = this._contentElement; + var anchorElement = this._anchorElement; + + var targetDocument = element.ownerDocument; + var targetDocumentBody = targetDocument.body; + var targetDocumentElement = targetDocument.documentElement; + var clippingBox = {x: 0, y: 0, width: targetDocumentElement.clientWidth, height: targetDocumentElement.clientHeight}; + var parentElement = element.offsetParent || element.parentElement; + + var anchorPosition = {x: anchorElement.totalOffsetLeft, y: anchorElement.totalOffsetTop}; + + // FIXME(apavlov@chromium.org): Translate anchorPosition to the element.ownerDocument frame when https://bugs.webkit.org/show_bug.cgi?id=28913 is fixed. + var anchorBox = {x: anchorPosition.x, y: anchorPosition.y, width: anchorElement.offsetWidth, height: anchorElement.offsetHeight}; + var elementBox = {x: element.totalOffsetLeft, y: element.totalOffsetTop, width: element.offsetWidth, height: element.offsetHeight}; + var newElementPosition = {x: 0, y: 0}; + + if (anchorBox.y - elementBox.height >= clippingBox.y) + newElementPosition.y = anchorBox.y - elementBox.height; + else + newElementPosition.y = Math.min(anchorBox.y + anchorBox.height, Math.max(clippingBox.y, clippingBox.y + clippingBox.height - elementBox.height)); + + if (anchorBox.x + elementBox.height <= clippingBox.x + clippingBox.height) + newElementPosition.x = anchorBox.x; + else + newElementPosition.x = Math.max(clippingBox.x, clippingBox.x + clippingBox.height - elementBox.height); + element.positionAt(newElementPosition.x, newElementPosition.y); + }, + + set anchor(x) + { + this._checkNotVisible(); + this._anchorElement = x; + }, + + get anchor() + { + return this._anchorElement; + }, + + set autoHide(x) + { + this._autoHide = x; + }, + + _checkNotVisible: function() + { + if (this.visible) + throw new Error("The popup must not be visible."); + }, + + _keyEventHandler: function(event) + { + // Escape hides the popup. + if (event.keyIdentifier == "U+001B") { + this.hide(); + event.preventDefault(); + event.handled = true; + } + }, + + _mouseDownEventHandler: function(event) + { + if (this._autoHide && event.originalTarget === this._glasspaneElement) + this.hide(); + } +} diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ProfileDataGridTree.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ProfileDataGridTree.js new file mode 100644 index 0000000..356f57d --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ProfileDataGridTree.js @@ -0,0 +1,398 @@ +/* + * Copyright (C) 2009 280 North Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.ProfileDataGridNode = function(profileView, profileNode, owningTree, hasChildren) +{ + this.profileView = profileView; + this.profileNode = profileNode; + + WebInspector.DataGridNode.call(this, null, hasChildren); + + this.addEventListener("populate", this._populate, this); + + this.tree = owningTree; + + this.childrenByCallUID = {}; + this.lastComparator = null; + + this.callUID = profileNode.callUID; + this.selfTime = profileNode.selfTime; + this.totalTime = profileNode.totalTime; + this.functionName = profileNode.functionName; + this.numberOfCalls = profileNode.numberOfCalls; + this.url = profileNode.url; +} + +WebInspector.ProfileDataGridNode.prototype = { + get data() + { + function formatMilliseconds(time) + { + return Number.secondsToString(time / 1000, WebInspector.UIString.bind(WebInspector), !Preferences.samplingCPUProfiler); + } + + var data = {}; + + data["function"] = this.functionName; + data["calls"] = this.numberOfCalls; + + if (this.profileView.showSelfTimeAsPercent) + data["self"] = WebInspector.UIString("%.2f%%", this.selfPercent); + else + data["self"] = formatMilliseconds(this.selfTime); + + if (this.profileView.showTotalTimeAsPercent) + data["total"] = WebInspector.UIString("%.2f%%", this.totalPercent); + else + data["total"] = formatMilliseconds(this.totalTime); + + if (this.profileView.showAverageTimeAsPercent) + data["average"] = WebInspector.UIString("%.2f%%", this.averagePercent); + else + data["average"] = formatMilliseconds(this.averageTime); + + return data; + }, + + createCell: function(columnIdentifier) + { + var cell = WebInspector.DataGridNode.prototype.createCell.call(this, columnIdentifier); + + if (columnIdentifier === "self" && this._searchMatchedSelfColumn) + cell.addStyleClass("highlight"); + else if (columnIdentifier === "total" && this._searchMatchedTotalColumn) + cell.addStyleClass("highlight"); + else if (columnIdentifier === "average" && this._searchMatchedAverageColumn) + cell.addStyleClass("highlight"); + else if (columnIdentifier === "calls" && this._searchMatchedCallsColumn) + cell.addStyleClass("highlight"); + + if (columnIdentifier !== "function") + return cell; + + if (this.profileNode._searchMatchedFunctionColumn) + cell.addStyleClass("highlight"); + + if (this.profileNode.url) { + var fileName = WebInspector.displayNameForURL(this.profileNode.url); + + var urlElement = document.createElement("a"); + urlElement.className = "profile-node-file webkit-html-resource-link"; + urlElement.href = this.profileNode.url; + urlElement.lineNumber = this.profileNode.lineNumber; + + if (this.profileNode.lineNumber > 0) + urlElement.textContent = fileName + ":" + this.profileNode.lineNumber; + else + urlElement.textContent = fileName; + + cell.insertBefore(urlElement, cell.firstChild); + } + + return cell; + }, + + select: function(supressSelectedEvent) + { + WebInspector.DataGridNode.prototype.select.call(this, supressSelectedEvent); + this.profileView._dataGridNodeSelected(this); + }, + + deselect: function(supressDeselectedEvent) + { + WebInspector.DataGridNode.prototype.deselect.call(this, supressDeselectedEvent); + this.profileView._dataGridNodeDeselected(this); + }, + + expand: function() + { + if (!this.parent) { + var currentComparator = this.parent.lastComparator; + + if (!currentComparator || (currentComparator === this.lastComparator)) + return; + + this.sort(currentComparator); + } + + WebInspector.DataGridNode.prototype.expand.call(this); + }, + + sort: function(/*Function*/ comparator, /*Boolean*/ force) + { + var gridNodeGroups = [[this]]; + + for (var gridNodeGroupIndex = 0; gridNodeGroupIndex < gridNodeGroups.length; ++gridNodeGroupIndex) { + var gridNodes = gridNodeGroups[gridNodeGroupIndex]; + var count = gridNodes.length; + + for (var index = 0; index < count; ++index) { + var gridNode = gridNodes[index]; + + // If the grid node is collapsed, then don't sort children (save operation for later). + // If the grid node has the same sorting as previously, then there is no point in sorting it again. + if (!force && !gridNode.expanded || gridNode.lastComparator === comparator) + continue; + + gridNode.lastComparator = comparator; + + var children = gridNode.children; + var childCount = children.length; + + if (childCount) { + children.sort(comparator); + + for (var childIndex = 0; childIndex < childCount; ++childIndex) + children[childIndex]._recalculateSiblings(childIndex); + + gridNodeGroups.push(children); + } + } + } + }, + + insertChild: function(/*ProfileDataGridNode*/ profileDataGridNode, index) + { + WebInspector.DataGridNode.prototype.insertChild.call(this, profileDataGridNode, index); + + this.childrenByCallUID[profileDataGridNode.callUID] = profileDataGridNode; + }, + + removeChild: function(/*ProfileDataGridNode*/ profileDataGridNode) + { + WebInspector.DataGridNode.prototype.removeChild.call(this, profileDataGridNode); + + delete this.childrenByCallUID[profileDataGridNode.callUID]; + }, + + removeChildren: function(/*ProfileDataGridNode*/ profileDataGridNode) + { + WebInspector.DataGridNode.prototype.removeChildren.call(this); + + this.childrenByCallUID = {}; + }, + + findChild: function(/*Node*/ node) + { + if (!node) + return null; + return this.childrenByCallUID[node.callUID]; + }, + + get averageTime() + { + return this.selfTime / Math.max(1, this.numberOfCalls); + }, + + get averagePercent() + { + return this.averageTime / this.tree.totalTime * 100.0; + }, + + get selfPercent() + { + return this.selfTime / this.tree.totalTime * 100.0; + }, + + get totalPercent() + { + return this.totalTime / this.tree.totalTime * 100.0; + }, + + // When focusing and collapsing we modify lots of nodes in the tree. + // This allows us to restore them all to their original state when we revert. + _save: function() + { + if (this._savedChildren) + return; + + this._savedSelfTime = this.selfTime; + this._savedTotalTime = this.totalTime; + this._savedNumberOfCalls = this.numberOfCalls; + + this._savedChildren = this.children.slice(); + }, + + // When focusing and collapsing we modify lots of nodes in the tree. + // This allows us to restore them all to their original state when we revert. + _restore: function() + { + if (!this._savedChildren) + return; + + this.selfTime = this._savedSelfTime; + this.totalTime = this._savedTotalTime; + this.numberOfCalls = this._savedNumberOfCalls; + + this.removeChildren(); + + var children = this._savedChildren; + var count = children.length; + + for (var index = 0; index < count; ++index) { + children[index]._restore(); + this.appendChild(children[index]); + } + }, + + _merge: function(child, shouldAbsorb) + { + this.selfTime += child.selfTime; + + if (!shouldAbsorb) { + this.totalTime += child.totalTime; + this.numberOfCalls += child.numberOfCalls; + } + + var children = this.children.slice(); + + this.removeChildren(); + + var count = children.length; + + for (var index = 0; index < count; ++index) { + if (!shouldAbsorb || children[index] !== child) + this.appendChild(children[index]); + } + + children = child.children.slice(); + count = children.length; + + for (var index = 0; index < count; ++index) { + var orphanedChild = children[index], + existingChild = this.childrenByCallUID[orphanedChild.callUID]; + + if (existingChild) + existingChild._merge(orphanedChild, false); + else + this.appendChild(orphanedChild); + } + } +} + +WebInspector.ProfileDataGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype; + +WebInspector.ProfileDataGridTree = function(profileView, profileNode) +{ + this.tree = this; + this.children = []; + + this.profileView = profileView; + + this.totalTime = profileNode.totalTime; + this.lastComparator = null; + + this.childrenByCallUID = {}; +} + +WebInspector.ProfileDataGridTree.prototype = { + get expanded() + { + return true; + }, + + appendChild: function(child) + { + this.insertChild(child, this.children.length); + }, + + insertChild: function(child, index) + { + this.children.splice(index, 0, child); + this.childrenByCallUID[child.callUID] = child; + }, + + removeChildren: function() + { + this.children = []; + this.childrenByCallUID = {}; + }, + + findChild: WebInspector.ProfileDataGridNode.prototype.findChild, + sort: WebInspector.ProfileDataGridNode.prototype.sort, + + _save: function() + { + if (this._savedChildren) + return; + + this._savedTotalTime = this.totalTime; + this._savedChildren = this.children.slice(); + }, + + restore: function() + { + if (!this._savedChildren) + return; + + this.children = this._savedChildren; + this.totalTime = this._savedTotalTime; + + var children = this.children; + var count = children.length; + + for (var index = 0; index < count; ++index) + children[index]._restore(); + + this._savedChildren = null; + } +} + +WebInspector.ProfileDataGridTree.propertyComparators = [{}, {}]; + +WebInspector.ProfileDataGridTree.propertyComparator = function(/*String*/ property, /*Boolean*/ isAscending) +{ + var comparator = this.propertyComparators[(isAscending ? 1 : 0)][property]; + + if (!comparator) { + if (isAscending) { + comparator = function(lhs, rhs) + { + if (lhs[property] < rhs[property]) + return -1; + + if (lhs[property] > rhs[property]) + return 1; + + return 0; + } + } else { + comparator = function(lhs, rhs) + { + if (lhs[property] > rhs[property]) + return -1; + + if (lhs[property] < rhs[property]) + return 1; + + return 0; + } + } + + this.propertyComparators[(isAscending ? 1 : 0)][property] = comparator; + } + + return comparator; +} diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ProfileView.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ProfileView.js new file mode 100644 index 0000000..2b8c6ce --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ProfileView.js @@ -0,0 +1,539 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.ProfileView = function(profile) +{ + WebInspector.View.call(this); + + this.element.addStyleClass("profile-view"); + + this.showSelfTimeAsPercent = true; + this.showTotalTimeAsPercent = true; + this.showAverageTimeAsPercent = true; + + var columns = { "self": { title: WebInspector.UIString("Self"), width: "72px", sort: "descending", sortable: true }, + "total": { title: WebInspector.UIString("Total"), width: "72px", sortable: true }, + "average": { title: WebInspector.UIString("Average"), width: "72px", sortable: true }, + "calls": { title: WebInspector.UIString("Calls"), width: "54px", sortable: true }, + "function": { title: WebInspector.UIString("Function"), disclosure: true, sortable: true } }; + + if (Preferences.samplingCPUProfiler) { + delete columns.average; + delete columns.calls; + } + + this.dataGrid = new WebInspector.DataGrid(columns); + this.dataGrid.addEventListener("sorting changed", this._sortData, this); + this.dataGrid.element.addEventListener("mousedown", this._mouseDownInDataGrid.bind(this), true); + this.element.appendChild(this.dataGrid.element); + + this.viewSelectElement = document.createElement("select"); + this.viewSelectElement.className = "status-bar-item"; + this.viewSelectElement.addEventListener("change", this._changeView.bind(this), false); + this.view = "Heavy"; + + var heavyViewOption = document.createElement("option"); + heavyViewOption.label = WebInspector.UIString("Heavy (Bottom Up)"); + var treeViewOption = document.createElement("option"); + treeViewOption.label = WebInspector.UIString("Tree (Top Down)"); + this.viewSelectElement.appendChild(heavyViewOption); + this.viewSelectElement.appendChild(treeViewOption); + + this.percentButton = new WebInspector.StatusBarButton("", "percent-time-status-bar-item"); + this.percentButton.addEventListener("click", this._percentClicked.bind(this), false); + + this.focusButton = new WebInspector.StatusBarButton(WebInspector.UIString("Focus selected function."), "focus-profile-node-status-bar-item"); + this.focusButton.disabled = true; + this.focusButton.addEventListener("click", this._focusClicked.bind(this), false); + + this.excludeButton = new WebInspector.StatusBarButton(WebInspector.UIString("Exclude selected function."), "exclude-profile-node-status-bar-item"); + this.excludeButton.disabled = true; + this.excludeButton.addEventListener("click", this._excludeClicked.bind(this), false); + + this.resetButton = new WebInspector.StatusBarButton(WebInspector.UIString("Restore all functions."), "reset-profile-status-bar-item"); + this.resetButton.visible = false; + this.resetButton.addEventListener("click", this._resetClicked.bind(this), false); + + this.profile = profile; + + this.profileDataGridTree = this.bottomUpProfileDataGridTree; + this.profileDataGridTree.sort(WebInspector.ProfileDataGridTree.propertyComparator("selfTime", false)); + + this.refresh(); + + this._updatePercentButton(); +} + +WebInspector.ProfileView.prototype = { + get statusBarItems() + { + return [this.viewSelectElement, this.percentButton.element, this.focusButton.element, this.excludeButton.element, this.resetButton.element]; + }, + + get profile() + { + return this._profile; + }, + + set profile(profile) + { + this._profile = profile; + }, + + get bottomUpProfileDataGridTree() + { + if (!this._bottomUpProfileDataGridTree) + this._bottomUpProfileDataGridTree = new WebInspector.BottomUpProfileDataGridTree(this, this.profile.head); + return this._bottomUpProfileDataGridTree; + }, + + get topDownProfileDataGridTree() + { + if (!this._topDownProfileDataGridTree) + this._topDownProfileDataGridTree = new WebInspector.TopDownProfileDataGridTree(this, this.profile.head); + return this._topDownProfileDataGridTree; + }, + + get currentTree() + { + return this._currentTree; + }, + + set currentTree(tree) + { + this._currentTree = tree; + this.refresh(); + }, + + get topDownTree() + { + if (!this._topDownTree) { + this._topDownTree = WebInspector.TopDownTreeFactory.create(this.profile.head); + this._sortProfile(this._topDownTree); + } + + return this._topDownTree; + }, + + get bottomUpTree() + { + if (!this._bottomUpTree) { + this._bottomUpTree = WebInspector.BottomUpTreeFactory.create(this.profile.head); + this._sortProfile(this._bottomUpTree); + } + + return this._bottomUpTree; + }, + + show: function(parentElement) + { + WebInspector.View.prototype.show.call(this, parentElement); + this.dataGrid.updateWidths(); + }, + + hide: function() + { + WebInspector.View.prototype.hide.call(this); + this._currentSearchResultIndex = -1; + }, + + resize: function() + { + if (this.dataGrid) + this.dataGrid.updateWidths(); + }, + + refresh: function() + { + var selectedProfileNode = this.dataGrid.selectedNode ? this.dataGrid.selectedNode.profileNode : null; + + this.dataGrid.removeChildren(); + + var children = this.profileDataGridTree.children; + var count = children.length; + + for (var index = 0; index < count; ++index) + this.dataGrid.appendChild(children[index]); + + if (selectedProfileNode) + selectedProfileNode.selected = true; + }, + + refreshVisibleData: function() + { + var child = this.dataGrid.children[0]; + while (child) { + child.refresh(); + child = child.traverseNextNode(false, null, true); + } + }, + + refreshShowAsPercents: function() + { + this._updatePercentButton(); + this.refreshVisibleData(); + }, + + searchCanceled: function() + { + if (this._searchResults) { + for (var i = 0; i < this._searchResults.length; ++i) { + var profileNode = this._searchResults[i].profileNode; + + delete profileNode._searchMatchedSelfColumn; + delete profileNode._searchMatchedTotalColumn; + delete profileNode._searchMatchedCallsColumn; + delete profileNode._searchMatchedFunctionColumn; + + profileNode.refresh(); + } + } + + delete this._searchFinishedCallback; + this._currentSearchResultIndex = -1; + this._searchResults = []; + }, + + performSearch: function(query, finishedCallback) + { + // Call searchCanceled since it will reset everything we need before doing a new search. + this.searchCanceled(); + + query = query.trimWhitespace(); + + if (!query.length) + return; + + this._searchFinishedCallback = finishedCallback; + + var greaterThan = (query.indexOf(">") === 0); + var lessThan = (query.indexOf("<") === 0); + var equalTo = (query.indexOf("=") === 0 || ((greaterThan || lessThan) && query.indexOf("=") === 1)); + var percentUnits = (query.lastIndexOf("%") === (query.length - 1)); + var millisecondsUnits = (query.length > 2 && query.lastIndexOf("ms") === (query.length - 2)); + var secondsUnits = (!millisecondsUnits && query.lastIndexOf("s") === (query.length - 1)); + + var queryNumber = parseFloat(query); + if (greaterThan || lessThan || equalTo) { + if (equalTo && (greaterThan || lessThan)) + queryNumber = parseFloat(query.substring(2)); + else + queryNumber = parseFloat(query.substring(1)); + } + + var queryNumberMilliseconds = (secondsUnits ? (queryNumber * 1000) : queryNumber); + + // Make equalTo implicitly true if it wasn't specified there is no other operator. + if (!isNaN(queryNumber) && !(greaterThan || lessThan)) + equalTo = true; + + function matchesQuery(/*ProfileDataGridNode*/ profileDataGridNode) + { + delete profileDataGridNode._searchMatchedSelfColumn; + delete profileDataGridNode._searchMatchedTotalColumn; + delete profileDataGridNode._searchMatchedAverageColumn; + delete profileDataGridNode._searchMatchedCallsColumn; + delete profileDataGridNode._searchMatchedFunctionColumn; + + if (percentUnits) { + if (lessThan) { + if (profileDataGridNode.selfPercent < queryNumber) + profileDataGridNode._searchMatchedSelfColumn = true; + if (profileDataGridNode.totalPercent < queryNumber) + profileDataGridNode._searchMatchedTotalColumn = true; + if (profileDataGridNode.averagePercent < queryNumberMilliseconds) + profileDataGridNode._searchMatchedAverageColumn = true; + } else if (greaterThan) { + if (profileDataGridNode.selfPercent > queryNumber) + profileDataGridNode._searchMatchedSelfColumn = true; + if (profileDataGridNode.totalPercent > queryNumber) + profileDataGridNode._searchMatchedTotalColumn = true; + if (profileDataGridNode.averagePercent < queryNumberMilliseconds) + profileDataGridNode._searchMatchedAverageColumn = true; + } + + if (equalTo) { + if (profileDataGridNode.selfPercent == queryNumber) + profileDataGridNode._searchMatchedSelfColumn = true; + if (profileDataGridNode.totalPercent == queryNumber) + profileDataGridNode._searchMatchedTotalColumn = true; + if (profileDataGridNode.averagePercent < queryNumberMilliseconds) + profileDataGridNode._searchMatchedAverageColumn = true; + } + } else if (millisecondsUnits || secondsUnits) { + if (lessThan) { + if (profileDataGridNode.selfTime < queryNumberMilliseconds) + profileDataGridNode._searchMatchedSelfColumn = true; + if (profileDataGridNode.totalTime < queryNumberMilliseconds) + profileDataGridNode._searchMatchedTotalColumn = true; + if (profileDataGridNode.averageTime < queryNumberMilliseconds) + profileDataGridNode._searchMatchedAverageColumn = true; + } else if (greaterThan) { + if (profileDataGridNode.selfTime > queryNumberMilliseconds) + profileDataGridNode._searchMatchedSelfColumn = true; + if (profileDataGridNode.totalTime > queryNumberMilliseconds) + profileDataGridNode._searchMatchedTotalColumn = true; + if (profileDataGridNode.averageTime > queryNumberMilliseconds) + profileDataGridNode._searchMatchedAverageColumn = true; + } + + if (equalTo) { + if (profileDataGridNode.selfTime == queryNumberMilliseconds) + profileDataGridNode._searchMatchedSelfColumn = true; + if (profileDataGridNode.totalTime == queryNumberMilliseconds) + profileDataGridNode._searchMatchedTotalColumn = true; + if (profileDataGridNode.averageTime == queryNumberMilliseconds) + profileDataGridNode._searchMatchedAverageColumn = true; + } + } else { + if (equalTo && profileDataGridNode.numberOfCalls == queryNumber) + profileDataGridNode._searchMatchedCallsColumn = true; + if (greaterThan && profileDataGridNode.numberOfCalls > queryNumber) + profileDataGridNode._searchMatchedCallsColumn = true; + if (lessThan && profileDataGridNode.numberOfCalls < queryNumber) + profileDataGridNode._searchMatchedCallsColumn = true; + } + + if (profileDataGridNode.functionName.hasSubstring(query, true) || profileDataGridNode.url.hasSubstring(query, true)) + profileDataGridNode._searchMatchedFunctionColumn = true; + + if (profileDataGridNode._searchMatchedSelfColumn || + profileDataGridNode._searchMatchedTotalColumn || + profileDataGridNode._searchMatchedAverageColumn || + profileDataGridNode._searchMatchedCallsColumn || + profileDataGridNode._searchMatchedFunctionColumn) + { + profileDataGridNode.refresh(); + return true; + } + + return false; + } + + var current = this.profileDataGridTree.children[0]; + + while (current) { + if (matchesQuery(current)) { + this._searchResults.push({ profileNode: current }); + } + + current = current.traverseNextNode(false, null, false); + } + + finishedCallback(this, this._searchResults.length); + }, + + jumpToFirstSearchResult: function() + { + if (!this._searchResults || !this._searchResults.length) + return; + this._currentSearchResultIndex = 0; + this._jumpToSearchResult(this._currentSearchResultIndex); + }, + + jumpToLastSearchResult: function() + { + if (!this._searchResults || !this._searchResults.length) + return; + this._currentSearchResultIndex = (this._searchResults.length - 1); + this._jumpToSearchResult(this._currentSearchResultIndex); + }, + + jumpToNextSearchResult: function() + { + if (!this._searchResults || !this._searchResults.length) + return; + if (++this._currentSearchResultIndex >= this._searchResults.length) + this._currentSearchResultIndex = 0; + this._jumpToSearchResult(this._currentSearchResultIndex); + }, + + jumpToPreviousSearchResult: function() + { + if (!this._searchResults || !this._searchResults.length) + return; + if (--this._currentSearchResultIndex < 0) + this._currentSearchResultIndex = (this._searchResults.length - 1); + this._jumpToSearchResult(this._currentSearchResultIndex); + }, + + showingFirstSearchResult: function() + { + return (this._currentSearchResultIndex === 0); + }, + + showingLastSearchResult: function() + { + return (this._searchResults && this._currentSearchResultIndex === (this._searchResults.length - 1)); + }, + + _jumpToSearchResult: function(index) + { + var searchResult = this._searchResults[index]; + if (!searchResult) + return; + + var profileNode = searchResult.profileNode; + profileNode.reveal(); + profileNode.select(); + }, + + _changeView: function(event) + { + if (!event || !this.profile) + return; + + if (event.target.selectedIndex == 1 && this.view == "Heavy") { + this.profileDataGridTree = this.topDownProfileDataGridTree; + this._sortProfile(); + this.view = "Tree"; + } else if (event.target.selectedIndex == 0 && this.view == "Tree") { + this.profileDataGridTree = this.bottomUpProfileDataGridTree; + this._sortProfile(); + this.view = "Heavy"; + } + + if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults) + return; + + // The current search needs to be performed again. First negate out previous match + // count by calling the search finished callback with a negative number of matches. + // Then perform the search again the with same query and callback. + this._searchFinishedCallback(this, -this._searchResults.length); + this.performSearch(this.currentQuery, this._searchFinishedCallback); + }, + + _percentClicked: function(event) + { + var currentState = this.showSelfTimeAsPercent && this.showTotalTimeAsPercent && this.showAverageTimeAsPercent; + this.showSelfTimeAsPercent = !currentState; + this.showTotalTimeAsPercent = !currentState; + this.showAverageTimeAsPercent = !currentState; + this.refreshShowAsPercents(); + }, + + _updatePercentButton: function() + { + if (this.showSelfTimeAsPercent && this.showTotalTimeAsPercent && this.showAverageTimeAsPercent) { + this.percentButton.title = WebInspector.UIString("Show absolute total and self times."); + this.percentButton.toggled = true; + } else { + this.percentButton.title = WebInspector.UIString("Show total and self times as percentages."); + this.percentButton.toggled = false; + } + }, + + _focusClicked: function(event) + { + if (!this.dataGrid.selectedNode) + return; + + this.resetButton.visible = true; + this.profileDataGridTree.focus(this.dataGrid.selectedNode); + this.refresh(); + this.refreshVisibleData(); + }, + + _excludeClicked: function(event) + { + var selectedNode = this.dataGrid.selectedNode + + if (!selectedNode) + return; + + selectedNode.deselect(); + + this.resetButton.visible = true; + this.profileDataGridTree.exclude(selectedNode); + this.refresh(); + this.refreshVisibleData(); + }, + + _resetClicked: function(event) + { + this.resetButton.visible = false; + this.profileDataGridTree.restore(); + this.refresh(); + this.refreshVisibleData(); + }, + + _dataGridNodeSelected: function(node) + { + this.focusButton.disabled = false; + this.excludeButton.disabled = false; + }, + + _dataGridNodeDeselected: function(node) + { + this.focusButton.disabled = true; + this.excludeButton.disabled = true; + }, + + _sortData: function(event) + { + this._sortProfile(this.profile); + }, + + _sortProfile: function() + { + var sortAscending = this.dataGrid.sortOrder === "ascending"; + var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier; + var sortProperty = { + "average": "averageTime", + "self": "selfTime", + "total": "totalTime", + "calls": "numberOfCalls", + "function": "functionName" + }[sortColumnIdentifier]; + + this.profileDataGridTree.sort(WebInspector.ProfileDataGridTree.propertyComparator(sortProperty, sortAscending)); + + this.refresh(); + }, + + _mouseDownInDataGrid: function(event) + { + if (event.detail < 2) + return; + + var cell = event.target.enclosingNodeOrSelfWithNodeName("td"); + if (!cell || (!cell.hasStyleClass("total-column") && !cell.hasStyleClass("self-column") && !cell.hasStyleClass("average-column"))) + return; + + if (cell.hasStyleClass("total-column")) + this.showTotalTimeAsPercent = !this.showTotalTimeAsPercent; + else if (cell.hasStyleClass("self-column")) + this.showSelfTimeAsPercent = !this.showSelfTimeAsPercent; + else if (cell.hasStyleClass("average-column")) + this.showAverageTimeAsPercent = !this.showAverageTimeAsPercent; + + this.refreshShowAsPercents(); + + event.preventDefault(); + event.stopPropagation(); + } +} + +WebInspector.ProfileView.prototype.__proto__ = WebInspector.View.prototype; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ProfilesPanel.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ProfilesPanel.js new file mode 100644 index 0000000..c010033 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ProfilesPanel.js @@ -0,0 +1,534 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +const UserInitiatedProfileName = "org.webkit.profiles.user-initiated"; + +WebInspector.ProfilesPanel = function() +{ + WebInspector.Panel.call(this); + + this.element.addStyleClass("profiles"); + + var panelEnablerHeading = WebInspector.UIString("You need to enable profiling before you can use the Profiles panel."); + var panelEnablerDisclaimer = WebInspector.UIString("Enabling profiling will make scripts run slower."); + var panelEnablerButton = WebInspector.UIString("Enable Profiling"); + this.panelEnablerView = new WebInspector.PanelEnablerView("profiles", panelEnablerHeading, panelEnablerDisclaimer, panelEnablerButton); + this.panelEnablerView.addEventListener("enable clicked", this._enableProfiling, this); + + this.element.appendChild(this.panelEnablerView.element); + + this.sidebarElement = document.createElement("div"); + this.sidebarElement.id = "profiles-sidebar"; + this.sidebarElement.className = "sidebar"; + this.element.appendChild(this.sidebarElement); + + this.sidebarResizeElement = document.createElement("div"); + this.sidebarResizeElement.className = "sidebar-resizer-vertical"; + this.sidebarResizeElement.addEventListener("mousedown", this._startSidebarDragging.bind(this), false); + this.element.appendChild(this.sidebarResizeElement); + + this.sidebarTreeElement = document.createElement("ol"); + this.sidebarTreeElement.className = "sidebar-tree"; + this.sidebarElement.appendChild(this.sidebarTreeElement); + + this.sidebarTree = new TreeOutline(this.sidebarTreeElement); + + this.profilesListTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("CPU PROFILES"), null, true); + this.sidebarTree.appendChild(this.profilesListTreeElement); + this.profilesListTreeElement.expand(); + + this.snapshotsListTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("HEAP SNAPSHOTS"), null, true); + if (Preferences.heapProfilerPresent) { + this.sidebarTree.appendChild(this.snapshotsListTreeElement); + this.snapshotsListTreeElement.expand(); + } + + this.profileViews = document.createElement("div"); + this.profileViews.id = "profile-views"; + this.element.appendChild(this.profileViews); + + this.enableToggleButton = new WebInspector.StatusBarButton("", "enable-toggle-status-bar-item"); + this.enableToggleButton.addEventListener("click", this._toggleProfiling.bind(this), false); + + this.recordButton = new WebInspector.StatusBarButton(WebInspector.UIString("Start profiling."), "record-profile-status-bar-item"); + this.recordButton.addEventListener("click", this._recordClicked.bind(this), false); + + this.recording = false; + + this.snapshotButton = new WebInspector.StatusBarButton(WebInspector.UIString("Take heap snapshot."), "heap-snapshot-status-bar-item"); + this.snapshotButton.visible = Preferences.heapProfilerPresent; + this.snapshotButton.addEventListener("click", this._snapshotClicked.bind(this), false); + + this.profileViewStatusBarItemsContainer = document.createElement("div"); + this.profileViewStatusBarItemsContainer.id = "profile-view-status-bar-items"; + + this.reset(); +} + +WebInspector.ProfilesPanel.prototype = { + toolbarItemClass: "profiles", + + get toolbarItemLabel() + { + return WebInspector.UIString("Profiles"); + }, + + get statusBarItems() + { + return [this.enableToggleButton.element, this.recordButton.element, this.snapshotButton.element, this.profileViewStatusBarItemsContainer]; + }, + + show: function() + { + WebInspector.Panel.prototype.show.call(this); + this._updateSidebarWidth(); + if (this._shouldPopulateProfiles) + this._populateProfiles(); + }, + + populateInterface: function() + { + if (this.visible) + this._populateProfiles(); + else + this._shouldPopulateProfiles = true; + }, + + profilerWasEnabled: function() + { + this.reset(); + this.populateInterface(); + }, + + profilerWasDisabled: function() + { + this.reset(); + }, + + reset: function() + { + if (this._profiles) { + var profiledLength = this._profiles.length; + for (var i = 0; i < profiledLength; ++i) { + var profile = this._profiles[i]; + delete profile._profileView; + } + } + + delete this.currentQuery; + this.searchCanceled(); + + this._profiles = []; + this._profilesIdMap = {}; + this._profileGroups = {}; + this._profileGroupsForLinks = {} + + this.sidebarTreeElement.removeStyleClass("some-expandable"); + + this.profilesListTreeElement.removeChildren(); + this.snapshotsListTreeElement.removeChildren(); + this.profileViews.removeChildren(); + + this.profileViewStatusBarItemsContainer.removeChildren(); + + this._updateInterface(); + }, + + handleKeyEvent: function(event) + { + this.sidebarTree.handleKeyEvent(event); + }, + + addProfile: function(profile) + { + this._profiles.push(profile); + this._profilesIdMap[profile.uid] = profile; + + var sidebarParent = this.profilesListTreeElement; + var small = false; + var alternateTitle; + + if (profile.title.indexOf(UserInitiatedProfileName) !== 0) { + if (!(profile.title in this._profileGroups)) + this._profileGroups[profile.title] = []; + + var group = this._profileGroups[profile.title]; + group.push(profile); + + if (group.length === 2) { + // Make a group TreeElement now that there are 2 profiles. + group._profilesTreeElement = new WebInspector.ProfileGroupSidebarTreeElement(profile.title); + + // Insert at the same index for the first profile of the group. + var index = this.sidebarTree.children.indexOf(group[0]._profilesTreeElement); + this.sidebarTree.insertChild(group._profilesTreeElement, index); + + // Move the first profile to the group. + var selected = group[0]._profilesTreeElement.selected; + this.sidebarTree.removeChild(group[0]._profilesTreeElement); + group._profilesTreeElement.appendChild(group[0]._profilesTreeElement); + if (selected) { + group[0]._profilesTreeElement.select(); + group[0]._profilesTreeElement.reveal(); + } + + group[0]._profilesTreeElement.small = true; + group[0]._profilesTreeElement.mainTitle = WebInspector.UIString("Run %d", 1); + + this.sidebarTreeElement.addStyleClass("some-expandable"); + } + + if (group.length >= 2) { + sidebarParent = group._profilesTreeElement; + alternateTitle = WebInspector.UIString("Run %d", group.length); + small = true; + } + } + + var profileTreeElement = new WebInspector.ProfileSidebarTreeElement(profile); + profileTreeElement.small = small; + if (alternateTitle) + profileTreeElement.mainTitle = alternateTitle; + profile._profilesTreeElement = profileTreeElement; + + sidebarParent.appendChild(profileTreeElement); + }, + + showProfile: function(profile) + { + if (!profile) + return; + + if (this.visibleView) + this.visibleView.hide(); + + var view = this.profileViewForProfile(profile); + + view.show(this.profileViews); + + profile._profilesTreeElement.select(true); + profile._profilesTreeElement.reveal(); + + this.visibleView = view; + + this.profileViewStatusBarItemsContainer.removeChildren(); + + var statusBarItems = view.statusBarItems; + for (var i = 0; i < statusBarItems.length; ++i) + this.profileViewStatusBarItemsContainer.appendChild(statusBarItems[i]); + }, + + showView: function(view) + { + this.showProfile(view.profile); + }, + + profileViewForProfile: function(profile) + { + if (!profile) + return null; + if (!profile._profileView) + profile._profileView = new WebInspector.ProfileView(profile); + return profile._profileView; + }, + + showProfileById: function(uid) + { + this.showProfile(this._profilesIdMap[uid]); + }, + + closeVisibleView: function() + { + if (this.visibleView) + this.visibleView.hide(); + delete this.visibleView; + }, + + displayTitleForProfileLink: function(title) + { + title = unescape(title); + if (title.indexOf(UserInitiatedProfileName) === 0) { + title = WebInspector.UIString("Profile %d", title.substring(UserInitiatedProfileName.length + 1)); + } else { + if (!(title in this._profileGroupsForLinks)) + this._profileGroupsForLinks[title] = 0; + + groupNumber = ++this._profileGroupsForLinks[title]; + + if (groupNumber > 2) + // The title is used in the console message announcing that a profile has started so it gets + // incremented twice as often as it's displayed + title += " " + WebInspector.UIString("Run %d", groupNumber / 2); + } + + return title; + }, + + get searchableViews() + { + var views = []; + + const visibleView = this.visibleView; + if (visibleView && visibleView.performSearch) + views.push(visibleView); + + var profilesLength = this._profiles.length; + for (var i = 0; i < profilesLength; ++i) { + var view = this.profileViewForProfile(this._profiles[i]); + if (!view.performSearch || view === visibleView) + continue; + views.push(view); + } + + return views; + }, + + searchMatchFound: function(view, matches) + { + view.profile._profilesTreeElement.searchMatches = matches; + }, + + searchCanceled: function(startingNewSearch) + { + WebInspector.Panel.prototype.searchCanceled.call(this, startingNewSearch); + + if (!this._profiles) + return; + + for (var i = 0; i < this._profiles.length; ++i) { + var profile = this._profiles[i]; + profile._profilesTreeElement.searchMatches = 0; + } + }, + + setRecordingProfile: function(isProfiling) + { + this.recording = isProfiling; + + if (isProfiling) { + this.recordButton.toggled = true; + this.recordButton.title = WebInspector.UIString("Stop profiling."); + } else { + this.recordButton.toggled = false; + this.recordButton.title = WebInspector.UIString("Start profiling."); + } + }, + + resize: function() + { + var visibleView = this.visibleView; + if (visibleView && "resize" in visibleView) + visibleView.resize(); + }, + + _updateInterface: function() + { + if (InspectorController.profilerEnabled()) { + this.enableToggleButton.title = WebInspector.UIString("Profiling enabled. Click to disable."); + this.enableToggleButton.toggled = true; + this.recordButton.visible = true; + if (Preferences.heapProfilerPresent) + this.snapshotButton.visible = true; + this.profileViewStatusBarItemsContainer.removeStyleClass("hidden"); + this.panelEnablerView.visible = false; + } else { + this.enableToggleButton.title = WebInspector.UIString("Profiling disabled. Click to enable."); + this.enableToggleButton.toggled = false; + this.recordButton.visible = false; + this.snapshotButton.visible = false; + this.profileViewStatusBarItemsContainer.addStyleClass("hidden"); + this.panelEnablerView.visible = true; + } + }, + + _recordClicked: function() + { + this.recording = !this.recording; + + if (this.recording) + InspectorController.startProfiling(); + else + InspectorController.stopProfiling(); + }, + + _snapshotClicked: function() + { + InspectorController.takeHeapSnapshot(); + }, + + _enableProfiling: function() + { + if (InspectorController.profilerEnabled()) + return; + this._toggleProfiling(this.panelEnablerView.alwaysEnabled); + }, + + _toggleProfiling: function(optionalAlways) + { + if (InspectorController.profilerEnabled()) + InspectorController.disableProfiler(true); + else + InspectorController.enableProfiler(!!optionalAlways); + }, + + _populateProfiles: function() + { + if (this.sidebarTree.children.length) + return; + + var profiles = InspectorController.profiles(); + var profilesLength = profiles.length; + for (var i = 0; i < profilesLength; ++i) { + var profile = profiles[i]; + this.addProfile(profile); + } + + if (this.sidebarTree.children[0]) + this.sidebarTree.children[0].select(); + + delete this._shouldPopulateProfiles; + }, + + _startSidebarDragging: function(event) + { + WebInspector.elementDragStart(this.sidebarResizeElement, this._sidebarDragging.bind(this), this._endSidebarDragging.bind(this), event, "col-resize"); + }, + + _sidebarDragging: function(event) + { + this._updateSidebarWidth(event.pageX); + + event.preventDefault(); + }, + + _endSidebarDragging: function(event) + { + WebInspector.elementDragEnd(event); + }, + + _updateSidebarWidth: function(width) + { + if (this.sidebarElement.offsetWidth <= 0) { + // The stylesheet hasn't loaded yet or the window is closed, + // so we can't calculate what is need. Return early. + return; + } + + if (!("_currentSidebarWidth" in this)) + this._currentSidebarWidth = this.sidebarElement.offsetWidth; + + if (typeof width === "undefined") + width = this._currentSidebarWidth; + + width = Number.constrain(width, Preferences.minSidebarWidth, window.innerWidth / 2); + + this._currentSidebarWidth = width; + + this.sidebarElement.style.width = width + "px"; + this.profileViews.style.left = width + "px"; + this.profileViewStatusBarItemsContainer.style.left = width + "px"; + this.sidebarResizeElement.style.left = (width - 3) + "px"; + + var visibleView = this.visibleView; + if (visibleView && "resize" in visibleView) + visibleView.resize(); + } +} + +WebInspector.ProfilesPanel.prototype.__proto__ = WebInspector.Panel.prototype; + +WebInspector.ProfileSidebarTreeElement = function(profile) +{ + this.profile = profile; + + if (this.profile.title.indexOf(UserInitiatedProfileName) === 0) + this._profileNumber = this.profile.title.substring(UserInitiatedProfileName.length + 1); + + WebInspector.SidebarTreeElement.call(this, "profile-sidebar-tree-item", "", "", profile, false); + + this.refreshTitles(); +} + +WebInspector.ProfileSidebarTreeElement.prototype = { + onselect: function() + { + WebInspector.panels.profiles.showProfile(this.profile); + }, + + get mainTitle() + { + if (this._mainTitle) + return this._mainTitle; + if (this.profile.title.indexOf(UserInitiatedProfileName) === 0) + return WebInspector.UIString("Profile %d", this._profileNumber); + return this.profile.title; + }, + + set mainTitle(x) + { + this._mainTitle = x; + this.refreshTitles(); + }, + + get subtitle() + { + // There is no subtitle. + }, + + set subtitle(x) + { + // Can't change subtitle. + }, + + set searchMatches(matches) + { + if (!matches) { + if (!this.bubbleElement) + return; + this.bubbleElement.removeStyleClass("search-matches"); + this.bubbleText = ""; + return; + } + + this.bubbleText = matches; + this.bubbleElement.addStyleClass("search-matches"); + } +} + +WebInspector.ProfileSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype; + +WebInspector.ProfileGroupSidebarTreeElement = function(title, subtitle) +{ + WebInspector.SidebarTreeElement.call(this, "profile-group-sidebar-tree-item", title, subtitle, null, true); +} + +WebInspector.ProfileGroupSidebarTreeElement.prototype = { + onselect: function() + { + WebInspector.panels.profiles.showProfile(this.children[this.children.length - 1].profile); + } +} + +WebInspector.ProfileGroupSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/PropertiesSection.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/PropertiesSection.js new file mode 100644 index 0000000..a4b2fba --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/PropertiesSection.js @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.PropertiesSection = function(title, subtitle) +{ + this.element = document.createElement("div"); + this.element.className = "section"; + + this.headerElement = document.createElement("div"); + this.headerElement.className = "header"; + + this.titleElement = document.createElement("div"); + this.titleElement.className = "title"; + + this.subtitleElement = document.createElement("div"); + this.subtitleElement.className = "subtitle"; + + this.headerElement.appendChild(this.subtitleElement); + this.headerElement.appendChild(this.titleElement); + + this.headerElement.addEventListener("click", this.toggleExpanded.bind(this), false); + + this.propertiesElement = document.createElement("ol"); + this.propertiesElement.className = "properties"; + this.propertiesTreeOutline = new TreeOutline(this.propertiesElement); + this.propertiesTreeOutline.section = this; + + this.element.appendChild(this.headerElement); + this.element.appendChild(this.propertiesElement); + + this.title = title; + this.subtitle = subtitle; + this._expanded = false; +} + +WebInspector.PropertiesSection.prototype = { + get title() + { + return this._title; + }, + + set title(x) + { + if (this._title === x) + return; + this._title = x; + + if (x instanceof Node) { + this.titleElement.removeChildren(); + this.titleElement.appendChild(x); + } else + this.titleElement.textContent = x; + }, + + get subtitle() + { + return this._subtitle; + }, + + set subtitle(x) + { + if (this._subtitle === x) + return; + this._subtitle = x; + this.subtitleElement.innerHTML = x; + }, + + get expanded() + { + return this._expanded; + }, + + set expanded(x) + { + if (x) + this.expand(); + else + this.collapse(); + }, + + get populated() + { + return this._populated; + }, + + set populated(x) + { + this._populated = x; + if (!x && this.onpopulate && this._expanded) { + this.onpopulate(this); + this._populated = true; + } + }, + + expand: function() + { + if (this._expanded) + return; + this._expanded = true; + this.element.addStyleClass("expanded"); + + if (!this._populated && this.onpopulate) { + this.onpopulate(this); + this._populated = true; + } + }, + + collapse: function() + { + if (!this._expanded) + return; + this._expanded = false; + this.element.removeStyleClass("expanded"); + }, + + toggleExpanded: function() + { + this.expanded = !this.expanded; + } +} diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/PropertiesSidebarPane.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/PropertiesSidebarPane.js new file mode 100644 index 0000000..ec08210 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/PropertiesSidebarPane.js @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.PropertiesSidebarPane = function() +{ + WebInspector.SidebarPane.call(this, WebInspector.UIString("Properties")); +} + +WebInspector.PropertiesSidebarPane.prototype = { + update: function(node) + { + var body = this.bodyElement; + + body.removeChildren(); + + this.sections = []; + + if (!node) + return; + + var self = this; + var callback = function(prototypes) { + var body = self.bodyElement; + body.removeChildren(); + self.sections = []; + + // Get array of prototype user-friendly names. + for (var i = 0; i < prototypes.length; ++i) { + var prototype = new WebInspector.ObjectProxy(node.id, [], i); + var section = new WebInspector.ObjectPropertiesSection(prototype, prototypes[i], WebInspector.UIString("Prototype")); + self.sections.push(section); + body.appendChild(section.element); + } + }; + InjectedScriptAccess.getPrototypes(node.id, callback); + } +} + +WebInspector.PropertiesSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Resource.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Resource.js new file mode 100644 index 0000000..4dac093 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Resource.js @@ -0,0 +1,622 @@ +/* + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.Resource = function(requestHeaders, url, domain, path, lastPathComponent, identifier, mainResource, cached, requestMethod, requestFormData) +{ + this.identifier = identifier; + + this.startTime = -1; + this.endTime = -1; + this.mainResource = mainResource; + this.requestHeaders = requestHeaders; + this.url = url; + this.domain = domain; + this.path = path; + this.lastPathComponent = lastPathComponent; + this.cached = cached; + this.requestMethod = requestMethod || ""; + this.requestFormData = requestFormData || ""; + + this.category = WebInspector.resourceCategories.other; +} + +// Keep these in sync with WebCore::InspectorResource::Type +WebInspector.Resource.Type = { + Document: 0, + Stylesheet: 1, + Image: 2, + Font: 3, + Script: 4, + XHR: 5, + Other: 6, + + isTextType: function(type) + { + return (type === this.Document) || (type === this.Stylesheet) || (type === this.Script) || (type === this.XHR); + }, + + toString: function(type) + { + switch (type) { + case this.Document: + return WebInspector.UIString("document"); + case this.Stylesheet: + return WebInspector.UIString("stylesheet"); + case this.Image: + return WebInspector.UIString("image"); + case this.Font: + return WebInspector.UIString("font"); + case this.Script: + return WebInspector.UIString("script"); + case this.XHR: + return WebInspector.UIString("XHR"); + case this.Other: + default: + return WebInspector.UIString("other"); + } + } +} + +WebInspector.Resource.prototype = { + get url() + { + return this._url; + }, + + set url(x) + { + if (this._url === x) + return; + + var oldURL = this._url; + this._url = x; + + // FIXME: We should make the WebInspector object listen for the "url changed" event. + // Then resourceURLChanged can be removed. + WebInspector.resourceURLChanged(this, oldURL); + + this.dispatchEventToListeners("url changed"); + }, + + get domain() + { + return this._domain; + }, + + set domain(x) + { + if (this._domain === x) + return; + this._domain = x; + }, + + get lastPathComponent() + { + return this._lastPathComponent; + }, + + set lastPathComponent(x) + { + if (this._lastPathComponent === x) + return; + this._lastPathComponent = x; + this._lastPathComponentLowerCase = x ? x.toLowerCase() : null; + }, + + get displayName() + { + var title = this.lastPathComponent; + if (!title) + title = this.displayDomain; + if (!title && this.url) + title = this.url.trimURL(WebInspector.mainResource ? WebInspector.mainResource.domain : ""); + if (title === "/") + title = this.url; + return title; + }, + + get displayDomain() + { + // WebInspector.Database calls this, so don't access more than this.domain. + if (this.domain && (!WebInspector.mainResource || (WebInspector.mainResource && this.domain !== WebInspector.mainResource.domain))) + return this.domain; + return ""; + }, + + get startTime() + { + return this._startTime || -1; + }, + + set startTime(x) + { + if (this._startTime === x) + return; + + this._startTime = x; + + if (WebInspector.panels.resources) + WebInspector.panels.resources.refreshResource(this); + }, + + get responseReceivedTime() + { + return this._responseReceivedTime || -1; + }, + + set responseReceivedTime(x) + { + if (this._responseReceivedTime === x) + return; + + this._responseReceivedTime = x; + + if (WebInspector.panels.resources) + WebInspector.panels.resources.refreshResource(this); + }, + + get endTime() + { + return this._endTime || -1; + }, + + set endTime(x) + { + if (this._endTime === x) + return; + + this._endTime = x; + + if (WebInspector.panels.resources) + WebInspector.panels.resources.refreshResource(this); + }, + + get duration() + { + if (this._endTime === -1 || this._startTime === -1) + return -1; + return this._endTime - this._startTime; + }, + + get latency() + { + if (this._responseReceivedTime === -1 || this._startTime === -1) + return -1; + return this._responseReceivedTime - this._startTime; + }, + + get contentLength() + { + return this._contentLength || 0; + }, + + set contentLength(x) + { + if (this._contentLength === x) + return; + + this._contentLength = x; + + if (WebInspector.panels.resources) + WebInspector.panels.resources.refreshResource(this); + }, + + get expectedContentLength() + { + return this._expectedContentLength || 0; + }, + + set expectedContentLength(x) + { + if (this._expectedContentLength === x) + return; + this._expectedContentLength = x; + }, + + get finished() + { + return this._finished; + }, + + set finished(x) + { + if (this._finished === x) + return; + + this._finished = x; + + if (x) { + this._checkTips(); + this._checkWarnings(); + this.dispatchEventToListeners("finished"); + } + }, + + get failed() + { + return this._failed; + }, + + set failed(x) + { + this._failed = x; + }, + + get category() + { + return this._category; + }, + + set category(x) + { + if (this._category === x) + return; + + var oldCategory = this._category; + if (oldCategory) + oldCategory.removeResource(this); + + this._category = x; + + if (this._category) + this._category.addResource(this); + + if (WebInspector.panels.resources) { + WebInspector.panels.resources.refreshResource(this); + WebInspector.panels.resources.recreateViewForResourceIfNeeded(this); + } + }, + + get mimeType() + { + return this._mimeType; + }, + + set mimeType(x) + { + if (this._mimeType === x) + return; + + this._mimeType = x; + }, + + get type() + { + return this._type; + }, + + set type(x) + { + if (this._type === x) + return; + + this._type = x; + + switch (x) { + case WebInspector.Resource.Type.Document: + this.category = WebInspector.resourceCategories.documents; + break; + case WebInspector.Resource.Type.Stylesheet: + this.category = WebInspector.resourceCategories.stylesheets; + break; + case WebInspector.Resource.Type.Script: + this.category = WebInspector.resourceCategories.scripts; + break; + case WebInspector.Resource.Type.Image: + this.category = WebInspector.resourceCategories.images; + break; + case WebInspector.Resource.Type.Font: + this.category = WebInspector.resourceCategories.fonts; + break; + case WebInspector.Resource.Type.XHR: + this.category = WebInspector.resourceCategories.xhr; + break; + case WebInspector.Resource.Type.Other: + default: + this.category = WebInspector.resourceCategories.other; + break; + } + }, + + get requestHeaders() + { + if (this._requestHeaders === undefined) + this._requestHeaders = {}; + return this._requestHeaders; + }, + + set requestHeaders(x) + { + if (this._requestHeaders === x) + return; + + this._requestHeaders = x; + delete this._sortedRequestHeaders; + + this.dispatchEventToListeners("requestHeaders changed"); + }, + + get sortedRequestHeaders() + { + if (this._sortedRequestHeaders !== undefined) + return this._sortedRequestHeaders; + + this._sortedRequestHeaders = []; + for (var key in this.requestHeaders) + this._sortedRequestHeaders.push({header: key, value: this.requestHeaders[key]}); + this._sortedRequestHeaders.sort(function(a,b) { return a.header.localeCompare(b.header) }); + + return this._sortedRequestHeaders; + }, + + get responseHeaders() + { + if (this._responseHeaders === undefined) + this._responseHeaders = {}; + return this._responseHeaders; + }, + + set responseHeaders(x) + { + if (this._responseHeaders === x) + return; + + this._responseHeaders = x; + delete this._sortedResponseHeaders; + + this.dispatchEventToListeners("responseHeaders changed"); + }, + + get sortedResponseHeaders() + { + if (this._sortedResponseHeaders !== undefined) + return this._sortedResponseHeaders; + + this._sortedResponseHeaders = []; + for (var key in this.responseHeaders) + this._sortedResponseHeaders.push({header: key, value: this.responseHeaders[key]}); + this._sortedResponseHeaders.sort(function(a,b) { return a.header.localeCompare(b.header) }); + + return this._sortedResponseHeaders; + }, + + get scripts() + { + if (!("_scripts" in this)) + this._scripts = []; + return this._scripts; + }, + + addScript: function(script) + { + if (!script) + return; + this.scripts.unshift(script); + script.resource = this; + }, + + removeAllScripts: function() + { + if (!this._scripts) + return; + + for (var i = 0; i < this._scripts.length; ++i) { + if (this._scripts[i].resource === this) + delete this._scripts[i].resource; + } + + delete this._scripts; + }, + + removeScript: function(script) + { + if (!script) + return; + + if (script.resource === this) + delete script.resource; + + if (!this._scripts) + return; + + this._scripts.remove(script); + }, + + get errors() + { + return this._errors || 0; + }, + + set errors(x) + { + this._errors = x; + }, + + get warnings() + { + return this._warnings || 0; + }, + + set warnings(x) + { + this._warnings = x; + }, + + get tips() + { + if (!("_tips" in this)) + this._tips = {}; + return this._tips; + }, + + _addTip: function(tip) + { + if (tip.id in this.tips) + return; + + this.tips[tip.id] = tip; + + // FIXME: Re-enable this code once we have a scope bar in the Console. + // Otherwise, we flood the Console with too many tips. + /* + var msg = new WebInspector.ConsoleMessage(WebInspector.ConsoleMessage.MessageSource.Other, + WebInspector.ConsoleMessage.MessageType.Log, WebInspector.ConsoleMessage.MessageLevel.Tip, + -1, this.url, null, 1, tip.message); + WebInspector.console.addMessage(msg); + */ + }, + + _checkTips: function() + { + for (var tip in WebInspector.Tips) + this._checkTip(WebInspector.Tips[tip]); + }, + + _checkTip: function(tip) + { + var addTip = false; + switch (tip.id) { + case WebInspector.Tips.ResourceNotCompressed.id: + addTip = this._shouldCompress(); + break; + } + + if (addTip) + this._addTip(tip); + }, + + _shouldCompress: function() + { + return WebInspector.Resource.Type.isTextType(this.type) + && this.domain + && !("Content-Encoding" in this.responseHeaders) + && this.contentLength !== undefined + && this.contentLength >= 512; + }, + + _mimeTypeIsConsistentWithType: function() + { + if (typeof this.type === "undefined" + || this.type === WebInspector.Resource.Type.Other + || this.type === WebInspector.Resource.Type.XHR) + return true; + + if (this.mimeType in WebInspector.MIMETypes) + return this.type in WebInspector.MIMETypes[this.mimeType]; + + return true; + }, + + _checkWarnings: function() + { + for (var warning in WebInspector.Warnings) + this._checkWarning(WebInspector.Warnings[warning]); + }, + + _checkWarning: function(warning) + { + var msg; + switch (warning.id) { + case WebInspector.Warnings.IncorrectMIMEType.id: + if (!this._mimeTypeIsConsistentWithType()) + msg = new WebInspector.ConsoleMessage(WebInspector.ConsoleMessage.MessageSource.Other, + WebInspector.ConsoleMessage.MessageType.Log, + WebInspector.ConsoleMessage.MessageLevel.Warning, -1, this.url, null, 1, + String.sprintf(WebInspector.Warnings.IncorrectMIMEType.message, + WebInspector.Resource.Type.toString(this.type), this.mimeType)); + break; + } + + if (msg) + WebInspector.console.addMessage(msg); + } +} + +WebInspector.Resource.prototype.__proto__ = WebInspector.Object.prototype; + +WebInspector.Resource.CompareByStartTime = function(a, b) +{ + if (a.startTime < b.startTime) + return -1; + if (a.startTime > b.startTime) + return 1; + return 0; +} + +WebInspector.Resource.CompareByResponseReceivedTime = function(a, b) +{ + if (a.responseReceivedTime === -1 && b.responseReceivedTime !== -1) + return 1; + if (a.responseReceivedTime !== -1 && b.responseReceivedTime === -1) + return -1; + if (a.responseReceivedTime < b.responseReceivedTime) + return -1; + if (a.responseReceivedTime > b.responseReceivedTime) + return 1; + return 0; +} + +WebInspector.Resource.CompareByEndTime = function(a, b) +{ + if (a.endTime === -1 && b.endTime !== -1) + return 1; + if (a.endTime !== -1 && b.endTime === -1) + return -1; + if (a.endTime < b.endTime) + return -1; + if (a.endTime > b.endTime) + return 1; + return 0; +} + +WebInspector.Resource.CompareByDuration = function(a, b) +{ + if (a.duration < b.duration) + return -1; + if (a.duration > b.duration) + return 1; + return 0; +} + +WebInspector.Resource.CompareByLatency = function(a, b) +{ + if (a.latency < b.latency) + return -1; + if (a.latency > b.latency) + return 1; + return 0; +} + +WebInspector.Resource.CompareBySize = function(a, b) +{ + if (a.contentLength < b.contentLength) + return -1; + if (a.contentLength > b.contentLength) + return 1; + return 0; +} diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ResourceCategory.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ResourceCategory.js new file mode 100644 index 0000000..fc508d0 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ResourceCategory.js @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.ResourceCategory = function(title, name) +{ + this.name = name; + this.title = title; + this.resources = []; +} + +WebInspector.ResourceCategory.prototype = { + toString: function() + { + return this.title; + }, + + addResource: function(resource) + { + var a = resource; + var resourcesLength = this.resources.length; + for (var i = 0; i < resourcesLength; ++i) { + var b = this.resources[i]; + if (a._lastPathComponentLowerCase && b._lastPathComponentLowerCase) + if (a._lastPathComponentLowerCase < b._lastPathComponentLowerCase) + break; + else if (a.name && b.name) + if (a.name < b.name) + break; + } + + this.resources.splice(i, 0, resource); + }, + + removeResource: function(resource) + { + this.resources.remove(resource, true); + }, + + removeAllResources: function(resource) + { + this.resources = []; + } +} diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ResourceView.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ResourceView.js new file mode 100644 index 0000000..d745920 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ResourceView.js @@ -0,0 +1,263 @@ +/* + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) IBM Corp. 2009 All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.ResourceView = function(resource) +{ + WebInspector.View.call(this); + + this.element.addStyleClass("resource-view"); + + this.resource = resource; + + this.headersElement = document.createElement("div"); + this.headersElement.className = "resource-view-headers"; + this.element.appendChild(this.headersElement); + + this.contentElement = document.createElement("div"); + this.contentElement.className = "resource-view-content"; + this.element.appendChild(this.contentElement); + + this.headersListElement = document.createElement("ol"); + this.headersListElement.className = "outline-disclosure"; + this.headersElement.appendChild(this.headersListElement); + + this.headersTreeOutline = new TreeOutline(this.headersListElement); + this.headersTreeOutline.expandTreeElementsWhenArrowing = true; + + this.urlTreeElement = new TreeElement("", null, false); + this.urlTreeElement.selectable = false; + this.headersTreeOutline.appendChild(this.urlTreeElement); + + this.requestHeadersTreeElement = new TreeElement("", null, true); + this.requestHeadersTreeElement.expanded = false; + this.requestHeadersTreeElement.selectable = false; + this.headersTreeOutline.appendChild(this.requestHeadersTreeElement); + + this._decodeHover = WebInspector.UIString("Double-Click to toggle between URL encoded and decoded formats"); + this._decodeRequestParameters = true; + + this.queryStringTreeElement = new TreeElement("", null, true); + this.queryStringTreeElement.expanded = false; + this.queryStringTreeElement.selectable = false; + this.queryStringTreeElement.hidden = true; + this.headersTreeOutline.appendChild(this.queryStringTreeElement); + + this.formDataTreeElement = new TreeElement("", null, true); + this.formDataTreeElement.expanded = false; + this.formDataTreeElement.selectable = false; + this.formDataTreeElement.hidden = true; + this.headersTreeOutline.appendChild(this.formDataTreeElement); + + this.requestPayloadTreeElement = new TreeElement(WebInspector.UIString("Request Payload"), null, true); + this.requestPayloadTreeElement.expanded = false; + this.requestPayloadTreeElement.selectable = false; + this.requestPayloadTreeElement.hidden = true; + this.headersTreeOutline.appendChild(this.requestPayloadTreeElement); + + this.responseHeadersTreeElement = new TreeElement("", null, true); + this.responseHeadersTreeElement.expanded = false; + this.responseHeadersTreeElement.selectable = false; + this.headersTreeOutline.appendChild(this.responseHeadersTreeElement); + + this.headersVisible = true; + + resource.addEventListener("url changed", this._refreshURL, this); + resource.addEventListener("requestHeaders changed", this._refreshRequestHeaders, this); + resource.addEventListener("responseHeaders changed", this._refreshResponseHeaders, this); + + this._refreshURL(); + this._refreshRequestHeaders(); + this._refreshResponseHeaders(); +} + +WebInspector.ResourceView.prototype = { + get headersVisible() + { + return this._headersVisible; + }, + + set headersVisible(x) + { + if (x === this._headersVisible) + return; + + this._headersVisible = x; + + if (x) + this.element.addStyleClass("headers-visible"); + else + this.element.removeStyleClass("headers-visible"); + }, + + attach: function() + { + if (!this.element.parentNode) { + var parentElement = (document.getElementById("resource-views") || document.getElementById("script-resource-views")); + if (parentElement) + parentElement.appendChild(this.element); + } + }, + + _refreshURL: function() + { + var url = this.resource.url; + this.urlTreeElement.title = this.resource.requestMethod + " " + url.escapeHTML(); + this._refreshQueryString(); + }, + + _refreshQueryString: function() + { + var url = this.resource.url; + var hasQueryString = url.indexOf("?") >= 0; + + if (!hasQueryString) { + this.queryStringTreeElement.hidden = true; + return; + } + + this.queryStringTreeElement.hidden = false; + var parmString = url.split("?", 2)[1]; + this._refreshParms(WebInspector.UIString("Query String Parameters"), parmString, this.queryStringTreeElement); + }, + + _refreshFormData: function() + { + this.formDataTreeElement.hidden = true; + this.requestPayloadTreeElement.hidden = true; + + var isFormData = this.resource.requestFormData; + if (!isFormData) + return; + + var isFormEncoded = false; + var requestContentType = this._getHeaderValue(this.resource.requestHeaders, "Content-Type"); + if (requestContentType == "application/x-www-form-urlencoded") + isFormEncoded = true; + + if (isFormEncoded) { + this.formDataTreeElement.hidden = false; + this._refreshParms(WebInspector.UIString("Form Data"), this.resource.requestFormData, this.formDataTreeElement); + } else { + this.requestPayloadTreeElement.hidden = false; + this._refreshRequestPayload(this.resource.requestFormData); + } + }, + + _refreshRequestPayload: function(formData) + { + this.requestPayloadTreeElement.removeChildren(); + + var title = "<div class=\"header-name\"> </div>"; + title += "<div class=\"raw-form-data header-value\">" + formData.escapeHTML() + "</div>"; + var parmTreeElement = new TreeElement(title, null, false); + this.requestPayloadTreeElement.appendChild(parmTreeElement); + }, + + _refreshParms: function(title, parmString, parmsTreeElement) + { + var parms = parmString.split("&"); + for (var i = 0; i < parms.length; ++i) { + var parm = parms[i]; + parm = parm.split("=", 2); + if (parm.length == 1) + parm.push(""); + parms[i] = parm; + } + + parmsTreeElement.removeChildren(); + + parmsTreeElement.title = title + "<span class=\"header-count\">" + WebInspector.UIString(" (%d)", parms.length) + "</span>"; + + for (var i = 0; i < parms.length; ++i) { + var key = parms[i][0]; + var val = parms[i][1]; + + if (val.indexOf("%") >= 0) + if (this._decodeRequestParameters) + val = decodeURIComponent(val).replace(/\+/g, " "); + + var title = "<div class=\"header-name\">" + key.escapeHTML() + ":</div>"; + title += "<div class=\"header-value\">" + val.escapeHTML() + "</div>"; + + var parmTreeElement = new TreeElement(title, null, false); + parmTreeElement.selectable = false; + parmTreeElement.tooltip = this._decodeHover; + parmTreeElement.ondblclick = this._toggleURLdecoding.bind(this); + parmsTreeElement.appendChild(parmTreeElement); + } + }, + + _toggleURLdecoding: function(treeElement, event) + { + this._decodeRequestParameters = !this._decodeRequestParameters; + this._refreshQueryString(); + this._refreshFormData(); + }, + + _getHeaderValue: function(headers, key) + { + var lowerKey = key.toLowerCase(); + for (var testKey in headers) { + if (testKey.toLowerCase() === lowerKey) + return headers[testKey]; + } + }, + + _refreshRequestHeaders: function() + { + this._refreshHeaders(WebInspector.UIString("Request Headers"), this.resource.sortedRequestHeaders, this.requestHeadersTreeElement); + this._refreshFormData(); + }, + + _refreshResponseHeaders: function() + { + this._refreshHeaders(WebInspector.UIString("Response Headers"), this.resource.sortedResponseHeaders, this.responseHeadersTreeElement); + }, + + _refreshHeaders: function(title, headers, headersTreeElement) + { + headersTreeElement.removeChildren(); + + var length = headers.length; + headersTreeElement.title = title.escapeHTML() + "<span class=\"header-count\">" + WebInspector.UIString(" (%d)", length) + "</span>"; + headersTreeElement.hidden = !length; + + var length = headers.length; + for (var i = 0; i < length; ++i) { + var title = "<div class=\"header-name\">" + headers[i].header.escapeHTML() + ":</div>"; + title += "<div class=\"header-value\">" + headers[i].value.escapeHTML() + "</div>" + + var headerTreeElement = new TreeElement(title, null, false); + headerTreeElement.selectable = false; + headersTreeElement.appendChild(headerTreeElement); + } + } +} + +WebInspector.ResourceView.prototype.__proto__ = WebInspector.View.prototype; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ResourcesPanel.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ResourcesPanel.js new file mode 100644 index 0000000..680f66c --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ResourcesPanel.js @@ -0,0 +1,1462 @@ +/* + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2008, 2009 Anthony Ricaud <rik@webkit.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.ResourcesPanel = function() +{ + WebInspector.Panel.call(this); + + this.element.addStyleClass("resources"); + + this.filterBarElement = document.createElement("div"); + this.filterBarElement.id = "resources-filter"; + this.element.appendChild(this.filterBarElement); + + this.viewsContainerElement = document.createElement("div"); + this.viewsContainerElement.id = "resource-views"; + this.element.appendChild(this.viewsContainerElement); + + this.containerElement = document.createElement("div"); + this.containerElement.id = "resources-container"; + this.containerElement.addEventListener("scroll", this._updateDividersLabelBarPosition.bind(this), false); + this.element.appendChild(this.containerElement); + + this.sidebarElement = document.createElement("div"); + this.sidebarElement.id = "resources-sidebar"; + this.sidebarElement.className = "sidebar"; + this.containerElement.appendChild(this.sidebarElement); + + this.sidebarResizeElement = document.createElement("div"); + this.sidebarResizeElement.className = "sidebar-resizer-vertical"; + this.sidebarResizeElement.addEventListener("mousedown", this._startSidebarDragging.bind(this), false); + this.element.appendChild(this.sidebarResizeElement); + + this.containerContentElement = document.createElement("div"); + this.containerContentElement.id = "resources-container-content"; + this.containerElement.appendChild(this.containerContentElement); + + this.summaryBar = new WebInspector.SummaryBar(this.categories); + this.summaryBar.element.id = "resources-summary"; + this.containerContentElement.appendChild(this.summaryBar.element); + + this.resourcesGraphsElement = document.createElement("div"); + this.resourcesGraphsElement.id = "resources-graphs"; + this.containerContentElement.appendChild(this.resourcesGraphsElement); + + this.dividersElement = document.createElement("div"); + this.dividersElement.id = "resources-dividers"; + this.containerContentElement.appendChild(this.dividersElement); + + this.dividersLabelBarElement = document.createElement("div"); + this.dividersLabelBarElement.id = "resources-dividers-label-bar"; + this.containerContentElement.appendChild(this.dividersLabelBarElement); + + this.sidebarTreeElement = document.createElement("ol"); + this.sidebarTreeElement.className = "sidebar-tree"; + this.sidebarElement.appendChild(this.sidebarTreeElement); + + this.sidebarTree = new TreeOutline(this.sidebarTreeElement); + + var timeGraphItem = new WebInspector.SidebarTreeElement("resources-time-graph-sidebar-item", WebInspector.UIString("Time")); + timeGraphItem.onselect = this._graphSelected.bind(this); + + var transferTimeCalculator = new WebInspector.ResourceTransferTimeCalculator(); + var transferDurationCalculator = new WebInspector.ResourceTransferDurationCalculator(); + + timeGraphItem.sortingOptions = [ + { name: WebInspector.UIString("Sort by Start Time"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByAscendingStartTime, calculator: transferTimeCalculator }, + { name: WebInspector.UIString("Sort by Response Time"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByAscendingResponseReceivedTime, calculator: transferTimeCalculator }, + { name: WebInspector.UIString("Sort by End Time"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByAscendingEndTime, calculator: transferTimeCalculator }, + { name: WebInspector.UIString("Sort by Duration"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByDescendingDuration, calculator: transferDurationCalculator }, + { name: WebInspector.UIString("Sort by Latency"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByDescendingLatency, calculator: transferDurationCalculator }, + ]; + + timeGraphItem.selectedSortingOptionIndex = 1; + + var sizeGraphItem = new WebInspector.SidebarTreeElement("resources-size-graph-sidebar-item", WebInspector.UIString("Size")); + sizeGraphItem.onselect = this._graphSelected.bind(this); + + var transferSizeCalculator = new WebInspector.ResourceTransferSizeCalculator(); + sizeGraphItem.sortingOptions = [ + { name: WebInspector.UIString("Sort by Size"), sortingFunction: WebInspector.ResourceSidebarTreeElement.CompareByDescendingSize, calculator: transferSizeCalculator }, + ]; + + sizeGraphItem.selectedSortingOptionIndex = 0; + + this.graphsTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("GRAPHS"), {}, true); + this.sidebarTree.appendChild(this.graphsTreeElement); + + this.graphsTreeElement.appendChild(timeGraphItem); + this.graphsTreeElement.appendChild(sizeGraphItem); + this.graphsTreeElement.expand(); + + this.resourcesTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("RESOURCES"), {}, true); + this.sidebarTree.appendChild(this.resourcesTreeElement); + + this.resourcesTreeElement.expand(); + + var panelEnablerHeading = WebInspector.UIString("You need to enable resource tracking to use this panel."); + var panelEnablerDisclaimer = WebInspector.UIString("Enabling resource tracking will reload the page and make page loading slower."); + var panelEnablerButton = WebInspector.UIString("Enable resource tracking"); + + this.panelEnablerView = new WebInspector.PanelEnablerView("resources", panelEnablerHeading, panelEnablerDisclaimer, panelEnablerButton); + this.panelEnablerView.addEventListener("enable clicked", this._enableResourceTracking, this); + + this.element.appendChild(this.panelEnablerView.element); + + this.enableToggleButton = new WebInspector.StatusBarButton("", "enable-toggle-status-bar-item"); + this.enableToggleButton.addEventListener("click", this._toggleResourceTracking.bind(this), false); + + this.largerResourcesButton = new WebInspector.StatusBarButton(WebInspector.UIString("Use small resource rows."), "resources-larger-resources-status-bar-item"); + this.largerResourcesButton.toggled = true; + this.largerResourcesButton.addEventListener("click", this._toggleLargerResources.bind(this), false); + + this.sortingSelectElement = document.createElement("select"); + this.sortingSelectElement.className = "status-bar-item"; + this.sortingSelectElement.addEventListener("change", this._changeSortingFunction.bind(this), false); + + var createFilterElement = function (category) { + var categoryElement = document.createElement("li"); + categoryElement.category = category; + categoryElement.addStyleClass(category); + var label = WebInspector.UIString("All"); + if (WebInspector.resourceCategories[category]) + label = WebInspector.resourceCategories[category].title; + categoryElement.appendChild(document.createTextNode(label)); + categoryElement.addEventListener("click", this._updateFilter.bind(this), false); + this.filterBarElement.appendChild(categoryElement); + return categoryElement; + }; + + var allElement = createFilterElement.call(this, "all"); + this.filter(allElement.category); + for (var category in this.categories) + createFilterElement.call(this, category); + + this.reset(); + + timeGraphItem.select(); +} + +WebInspector.ResourcesPanel.prototype = { + toolbarItemClass: "resources", + + get categories() + { + if (!this._categories) { + this._categories = {documents: {color: {r: 47, g: 102, b: 236}}, stylesheets: {color: {r: 157, g: 231, b: 119}}, images: {color: {r: 164, g: 60, b: 255}}, scripts: {color: {r: 255, g: 121, b: 0}}, xhr: {color: {r: 231, g: 231, b: 10}}, fonts: {color: {r: 255, g: 82, b: 62}}, other: {color: {r: 186, g: 186, b: 186}}}; + for (var category in this._categories) { + this._categories[category].title = WebInspector.resourceCategories[category].title; + } + } + return this._categories; + }, + + filter: function (category) { + if (this._filterCategory && this._filterCategory === category) + return; + + if (this._filterCategory) { + var filterElement = this.filterBarElement.getElementsByClassName(this._filterCategory)[0]; + filterElement.removeStyleClass("selected"); + var oldClass = "filter-" + this._filterCategory; + this.resourcesTreeElement.childrenListElement.removeStyleClass(oldClass); + this.resourcesGraphsElement.removeStyleClass(oldClass); + } + this._filterCategory = category; + var filterElement = this.filterBarElement.getElementsByClassName(this._filterCategory)[0]; + filterElement.addStyleClass("selected"); + var newClass = "filter-" + this._filterCategory; + this.resourcesTreeElement.childrenListElement.addStyleClass(newClass); + this.resourcesGraphsElement.addStyleClass(newClass); + }, + + _updateFilter: function (e) { + this.filter(e.target.category); + }, + + get toolbarItemLabel() + { + return WebInspector.UIString("Resources"); + }, + + get statusBarItems() + { + return [this.enableToggleButton.element, this.largerResourcesButton.element, this.sortingSelectElement]; + }, + + show: function() + { + WebInspector.Panel.prototype.show.call(this); + + this._updateDividersLabelBarPosition(); + this._updateSidebarWidth(); + this.refreshIfNeeded(); + + var visibleView = this.visibleView; + if (visibleView) { + visibleView.headersVisible = true; + visibleView.show(this.viewsContainerElement); + } + + // Hide any views that are visible that are not this panel's current visible view. + // This can happen when a ResourceView is visible in the Scripts panel then switched + // to the this panel. + var resourcesLength = this._resources.length; + for (var i = 0; i < resourcesLength; ++i) { + var resource = this._resources[i]; + var view = resource._resourcesView; + if (!view || view === visibleView) + continue; + view.visible = false; + } + }, + + resize: function() + { + this._updateGraphDividersIfNeeded(); + + var visibleView = this.visibleView; + if (visibleView && "resize" in visibleView) + visibleView.resize(); + }, + + get searchableViews() + { + var views = []; + + const visibleView = this.visibleView; + if (visibleView && visibleView.performSearch) + views.push(visibleView); + + var resourcesLength = this._resources.length; + for (var i = 0; i < resourcesLength; ++i) { + var resource = this._resources[i]; + if (!resource._resourcesTreeElement) + continue; + var resourceView = this.resourceViewForResource(resource); + if (!resourceView.performSearch || resourceView === visibleView) + continue; + views.push(resourceView); + } + + return views; + }, + + get searchResultsSortFunction() + { + const resourceTreeElementSortFunction = this.sortingFunction; + + function sortFuction(a, b) + { + return resourceTreeElementSortFunction(a.resource._resourcesTreeElement, b.resource._resourcesTreeElement); + } + + return sortFuction; + }, + + searchMatchFound: function(view, matches) + { + view.resource._resourcesTreeElement.searchMatches = matches; + }, + + searchCanceled: function(startingNewSearch) + { + WebInspector.Panel.prototype.searchCanceled.call(this, startingNewSearch); + + if (startingNewSearch || !this._resources) + return; + + for (var i = 0; i < this._resources.length; ++i) { + var resource = this._resources[i]; + if (resource._resourcesTreeElement) + resource._resourcesTreeElement.updateErrorsAndWarnings(); + } + }, + + performSearch: function(query) + { + for (var i = 0; i < this._resources.length; ++i) { + var resource = this._resources[i]; + if (resource._resourcesTreeElement) + resource._resourcesTreeElement.resetBubble(); + } + + WebInspector.Panel.prototype.performSearch.call(this, query); + }, + + get visibleView() + { + if (this.visibleResource) + return this.visibleResource._resourcesView; + return null; + }, + + get calculator() + { + return this._calculator; + }, + + set calculator(x) + { + if (!x || this._calculator === x) + return; + + this._calculator = x; + this._calculator.reset(); + + this._staleResources = this._resources; + this.refresh(); + }, + + get sortingFunction() + { + return this._sortingFunction; + }, + + set sortingFunction(x) + { + this._sortingFunction = x; + this._sortResourcesIfNeeded(); + }, + + get needsRefresh() + { + return this._needsRefresh; + }, + + set needsRefresh(x) + { + if (this._needsRefresh === x) + return; + + this._needsRefresh = x; + + if (x) { + if (this.visible && !("_refreshTimeout" in this)) + this._refreshTimeout = setTimeout(this.refresh.bind(this), 500); + } else { + if ("_refreshTimeout" in this) { + clearTimeout(this._refreshTimeout); + delete this._refreshTimeout; + } + } + }, + + refreshIfNeeded: function() + { + if (this.needsRefresh) + this.refresh(); + }, + + refresh: function() + { + this.needsRefresh = false; + + var staleResourcesLength = this._staleResources.length; + var boundariesChanged = false; + + for (var i = 0; i < staleResourcesLength; ++i) { + var resource = this._staleResources[i]; + if (!resource._resourcesTreeElement) { + // Create the resource tree element and graph. + resource._resourcesTreeElement = new WebInspector.ResourceSidebarTreeElement(resource); + resource._resourcesTreeElement._resourceGraph = new WebInspector.ResourceGraph(resource); + + this.resourcesTreeElement.appendChild(resource._resourcesTreeElement); + this.resourcesGraphsElement.appendChild(resource._resourcesTreeElement._resourceGraph.graphElement); + } + + resource._resourcesTreeElement.refresh(); + + if (this.calculator.updateBoundaries(resource)) + boundariesChanged = true; + } + + if (boundariesChanged) { + // The boundaries changed, so all resource graphs are stale. + this._staleResources = this._resources; + staleResourcesLength = this._staleResources.length; + } + + for (var i = 0; i < staleResourcesLength; ++i) + this._staleResources[i]._resourcesTreeElement._resourceGraph.refresh(this.calculator); + + this._staleResources = []; + + this._updateGraphDividersIfNeeded(); + this._sortResourcesIfNeeded(); + this._updateSummaryGraph(); + }, + + resourceTrackingWasEnabled: function() + { + this.reset(); + }, + + resourceTrackingWasDisabled: function() + { + this.reset(); + }, + + reset: function() + { + this.closeVisibleResource(); + + this.containerElement.scrollTop = 0; + + delete this.currentQuery; + this.searchCanceled(); + + if (this._calculator) + this._calculator.reset(); + + if (this._resources) { + var resourcesLength = this._resources.length; + for (var i = 0; i < resourcesLength; ++i) { + var resource = this._resources[i]; + + resource.warnings = 0; + resource.errors = 0; + + delete resource._resourcesTreeElement; + delete resource._resourcesView; + } + } + + this._resources = []; + this._staleResources = []; + + this.resourcesTreeElement.removeChildren(); + this.viewsContainerElement.removeChildren(); + this.resourcesGraphsElement.removeChildren(); + this.summaryBar.reset(); + + this._updateGraphDividersIfNeeded(true); + + if (InspectorController.resourceTrackingEnabled()) { + this.enableToggleButton.title = WebInspector.UIString("Resource tracking enabled. Click to disable."); + this.enableToggleButton.toggled = true; + this.largerResourcesButton.visible = true; + this.sortingSelectElement.removeStyleClass("hidden"); + this.panelEnablerView.visible = false; + } else { + this.enableToggleButton.title = WebInspector.UIString("Resource tracking disabled. Click to enable."); + this.enableToggleButton.toggled = false; + this.largerResourcesButton.visible = false; + this.sortingSelectElement.addStyleClass("hidden"); + this.panelEnablerView.visible = true; + } + }, + + addResource: function(resource) + { + this._resources.push(resource); + this.refreshResource(resource); + }, + + removeResource: function(resource) + { + if (this.visibleView === resource._resourcesView) + this.closeVisibleResource(); + + this._resources.remove(resource, true); + + if (resource._resourcesTreeElement) { + this.resourcesTreeElement.removeChild(resource._resourcesTreeElement); + this.resourcesGraphsElement.removeChild(resource._resourcesTreeElement._resourceGraph.graphElement); + } + + resource.warnings = 0; + resource.errors = 0; + + delete resource._resourcesTreeElement; + delete resource._resourcesView; + + this._adjustScrollPosition(); + }, + + addMessageToResource: function(resource, msg) + { + if (!resource) + return; + + switch (msg.level) { + case WebInspector.ConsoleMessage.MessageLevel.Warning: + resource.warnings += msg.repeatDelta; + break; + case WebInspector.ConsoleMessage.MessageLevel.Error: + resource.errors += msg.repeatDelta; + break; + } + + if (!this.currentQuery && resource._resourcesTreeElement) + resource._resourcesTreeElement.updateErrorsAndWarnings(); + + var view = this.resourceViewForResource(resource); + if (view.addMessage) + view.addMessage(msg); + }, + + clearMessages: function() + { + var resourcesLength = this._resources.length; + for (var i = 0; i < resourcesLength; ++i) { + var resource = this._resources[i]; + resource.warnings = 0; + resource.errors = 0; + + if (!this.currentQuery && resource._resourcesTreeElement) + resource._resourcesTreeElement.updateErrorsAndWarnings(); + + var view = resource._resourcesView; + if (!view || !view.clearMessages) + continue; + view.clearMessages(); + } + }, + + refreshResource: function(resource) + { + this._staleResources.push(resource); + this.needsRefresh = true; + }, + + recreateViewForResourceIfNeeded: function(resource) + { + if (!resource || !resource._resourcesView) + return; + + var newView = this._createResourceView(resource); + if (newView.prototype === resource._resourcesView.prototype) + return; + + resource.warnings = 0; + resource.errors = 0; + + if (!this.currentQuery && resource._resourcesTreeElement) + resource._resourcesTreeElement.updateErrorsAndWarnings(); + + var oldView = resource._resourcesView; + + resource._resourcesView.detach(); + delete resource._resourcesView; + + resource._resourcesView = newView; + + newView.headersVisible = oldView.headersVisible; + + if (oldView.visible && oldView.element.parentNode) + newView.show(oldView.element.parentNode); + }, + + showResource: function(resource, line) + { + if (!resource) + return; + + this.containerElement.addStyleClass("viewing-resource"); + + if (this.visibleResource && this.visibleResource._resourcesView) + this.visibleResource._resourcesView.hide(); + + var view = this.resourceViewForResource(resource); + view.headersVisible = true; + view.show(this.viewsContainerElement); + + if (line) { + if (view.revealLine) + view.revealLine(line); + if (view.highlightLine) + view.highlightLine(line); + } + + if (resource._resourcesTreeElement) { + resource._resourcesTreeElement.reveal(); + resource._resourcesTreeElement.select(true); + } + + this.visibleResource = resource; + + this._updateSidebarWidth(); + }, + + showView: function(view) + { + if (!view) + return; + this.showResource(view.resource); + }, + + closeVisibleResource: function() + { + this.containerElement.removeStyleClass("viewing-resource"); + this._updateDividersLabelBarPosition(); + + if (this.visibleResource && this.visibleResource._resourcesView) + this.visibleResource._resourcesView.hide(); + delete this.visibleResource; + + if (this._lastSelectedGraphTreeElement) + this._lastSelectedGraphTreeElement.select(true); + + this._updateSidebarWidth(); + }, + + resourceViewForResource: function(resource) + { + if (!resource) + return null; + if (!resource._resourcesView) + resource._resourcesView = this._createResourceView(resource); + return resource._resourcesView; + }, + + sourceFrameForResource: function(resource) + { + var view = this.resourceViewForResource(resource); + if (!view) + return null; + + if (!view.setupSourceFrameIfNeeded) + return null; + + // Setting up the source frame requires that we be attached. + if (!this.element.parentNode) + this.attach(); + + view.setupSourceFrameIfNeeded(); + return view.sourceFrame; + }, + + handleKeyEvent: function(event) + { + this.sidebarTree.handleKeyEvent(event); + }, + + _sortResourcesIfNeeded: function() + { + var sortedElements = [].concat(this.resourcesTreeElement.children); + sortedElements.sort(this.sortingFunction); + + var sortedElementsLength = sortedElements.length; + for (var i = 0; i < sortedElementsLength; ++i) { + var treeElement = sortedElements[i]; + if (treeElement === this.resourcesTreeElement.children[i]) + continue; + + var wasSelected = treeElement.selected; + this.resourcesTreeElement.removeChild(treeElement); + this.resourcesTreeElement.insertChild(treeElement, i); + if (wasSelected) + treeElement.select(true); + + var graphElement = treeElement._resourceGraph.graphElement; + this.resourcesGraphsElement.insertBefore(graphElement, this.resourcesGraphsElement.children[i]); + } + }, + + _updateGraphDividersIfNeeded: function(force) + { + if (!this.visible) { + this.needsRefresh = true; + return; + } + + if (document.body.offsetWidth <= 0) { + // The stylesheet hasn't loaded yet or the window is closed, + // so we can't calculate what is need. Return early. + return; + } + + var dividerCount = Math.round(this.dividersElement.offsetWidth / 64); + var slice = this.calculator.boundarySpan / dividerCount; + if (!force && this._currentDividerSlice === slice) + return; + + this._currentDividerSlice = slice; + + this.dividersElement.removeChildren(); + this.dividersLabelBarElement.removeChildren(); + + for (var i = 1; i <= dividerCount; ++i) { + var divider = document.createElement("div"); + divider.className = "resources-divider"; + if (i === dividerCount) + divider.addStyleClass("last"); + divider.style.left = ((i / dividerCount) * 100) + "%"; + + this.dividersElement.appendChild(divider.cloneNode()); + + var label = document.createElement("div"); + label.className = "resources-divider-label"; + if (!isNaN(slice)) + label.textContent = this.calculator.formatValue(slice * i); + divider.appendChild(label); + + this.dividersLabelBarElement.appendChild(divider); + } + }, + + _updateSummaryGraph: function() + { + this.summaryBar.update(this._resources); + }, + + _updateDividersLabelBarPosition: function() + { + var scrollTop = this.containerElement.scrollTop; + var dividersTop = (scrollTop < this.summaryBar.element.offsetHeight ? this.summaryBar.element.offsetHeight : scrollTop); + this.dividersElement.style.top = scrollTop + "px"; + this.dividersLabelBarElement.style.top = dividersTop + "px"; + }, + + _graphSelected: function(treeElement) + { + if (this._lastSelectedGraphTreeElement) + this._lastSelectedGraphTreeElement.selectedSortingOptionIndex = this.sortingSelectElement.selectedIndex; + + this._lastSelectedGraphTreeElement = treeElement; + + this.sortingSelectElement.removeChildren(); + for (var i = 0; i < treeElement.sortingOptions.length; ++i) { + var sortingOption = treeElement.sortingOptions[i]; + var option = document.createElement("option"); + option.label = sortingOption.name; + option.sortingFunction = sortingOption.sortingFunction; + option.calculator = sortingOption.calculator; + this.sortingSelectElement.appendChild(option); + } + + this.sortingSelectElement.selectedIndex = treeElement.selectedSortingOptionIndex; + this._changeSortingFunction(); + + this.closeVisibleResource(); + this.containerElement.scrollTop = 0; + }, + + _toggleLargerResources: function() + { + if (!this.resourcesTreeElement._childrenListNode) + return; + + this.resourcesTreeElement.smallChildren = !this.resourcesTreeElement.smallChildren; + + if (this.resourcesTreeElement.smallChildren) { + this.resourcesGraphsElement.addStyleClass("small"); + this.largerResourcesButton.title = WebInspector.UIString("Use large resource rows."); + this.largerResourcesButton.toggled = false; + this._adjustScrollPosition(); + } else { + this.resourcesGraphsElement.removeStyleClass("small"); + this.largerResourcesButton.title = WebInspector.UIString("Use small resource rows."); + this.largerResourcesButton.toggled = true; + } + }, + + _adjustScrollPosition: function() + { + // Prevent the container from being scrolled off the end. + if ((this.containerElement.scrollTop + this.containerElement.offsetHeight) > this.sidebarElement.offsetHeight) + this.containerElement.scrollTop = (this.sidebarElement.offsetHeight - this.containerElement.offsetHeight); + }, + + _changeSortingFunction: function() + { + var selectedOption = this.sortingSelectElement[this.sortingSelectElement.selectedIndex]; + this.sortingFunction = selectedOption.sortingFunction; + this.calculator = this.summaryBar.calculator = selectedOption.calculator; + }, + + _createResourceView: function(resource) + { + switch (resource.category) { + case WebInspector.resourceCategories.documents: + case WebInspector.resourceCategories.stylesheets: + case WebInspector.resourceCategories.scripts: + case WebInspector.resourceCategories.xhr: + return new WebInspector.SourceView(resource); + case WebInspector.resourceCategories.images: + return new WebInspector.ImageView(resource); + case WebInspector.resourceCategories.fonts: + return new WebInspector.FontView(resource); + default: + return new WebInspector.ResourceView(resource); + } + }, + + _startSidebarDragging: function(event) + { + WebInspector.elementDragStart(this.sidebarResizeElement, this._sidebarDragging.bind(this), this._endSidebarDragging.bind(this), event, "col-resize"); + }, + + _sidebarDragging: function(event) + { + this._updateSidebarWidth(event.pageX); + + event.preventDefault(); + }, + + _endSidebarDragging: function(event) + { + WebInspector.elementDragEnd(event); + }, + + _updateSidebarWidth: function(width) + { + if (this.sidebarElement.offsetWidth <= 0) { + // The stylesheet hasn't loaded yet or the window is closed, + // so we can't calculate what is need. Return early. + return; + } + + if (!("_currentSidebarWidth" in this)) + this._currentSidebarWidth = this.sidebarElement.offsetWidth; + + if (typeof width === "undefined") + width = this._currentSidebarWidth; + + width = Number.constrain(width, Preferences.minSidebarWidth, window.innerWidth / 2); + + this._currentSidebarWidth = width; + + if (this.visibleResource) { + this.containerElement.style.width = width + "px"; + this.sidebarElement.style.removeProperty("width"); + } else { + this.sidebarElement.style.width = width + "px"; + this.containerElement.style.removeProperty("width"); + } + + this.containerContentElement.style.left = width + "px"; + this.viewsContainerElement.style.left = width + "px"; + this.sidebarResizeElement.style.left = (width - 3) + "px"; + + this._updateGraphDividersIfNeeded(); + + var visibleView = this.visibleView; + if (visibleView && "resize" in visibleView) + visibleView.resize(); + }, + + _enableResourceTracking: function() + { + if (InspectorController.resourceTrackingEnabled()) + return; + this._toggleResourceTracking(this.panelEnablerView.alwaysEnabled); + }, + + _toggleResourceTracking: function(optionalAlways) + { + if (InspectorController.resourceTrackingEnabled()) { + this.largerResourcesButton.visible = false; + this.sortingSelectElement.visible = false; + InspectorController.disableResourceTracking(true); + } else { + this.largerResourcesButton.visible = true; + this.sortingSelectElement.visible = true; + InspectorController.enableResourceTracking(!!optionalAlways); + } + } +} + +WebInspector.ResourcesPanel.prototype.__proto__ = WebInspector.Panel.prototype; + +WebInspector.ResourceCalculator = function() +{ +} + +WebInspector.ResourceCalculator.prototype = { + computeSummaryValues: function(resources) + { + var total = 0; + var categoryValues = {}; + + var resourcesLength = resources.length; + for (var i = 0; i < resourcesLength; ++i) { + var resource = resources[i]; + var value = this._value(resource); + if (typeof value === "undefined") + continue; + if (!(resource.category.name in categoryValues)) + categoryValues[resource.category.name] = 0; + categoryValues[resource.category.name] += value; + total += value; + } + + return {categoryValues: categoryValues, total: total}; + }, + + computeBarGraphPercentages: function(resource) + { + return {start: 0, middle: 0, end: (this._value(resource) / this.boundarySpan) * 100}; + }, + + computeBarGraphLabels: function(resource) + { + const label = this.formatValue(this._value(resource)); + var tooltip = label; + if (resource.cached) + tooltip = WebInspector.UIString("%s (from cache)", tooltip); + return {left: label, right: label, tooltip: tooltip}; + }, + + get boundarySpan() + { + return this.maximumBoundary - this.minimumBoundary; + }, + + updateBoundaries: function(resource) + { + this.minimumBoundary = 0; + + var value = this._value(resource); + if (typeof this.maximumBoundary === "undefined" || value > this.maximumBoundary) { + this.maximumBoundary = value; + return true; + } + + return false; + }, + + reset: function() + { + delete this.minimumBoundary; + delete this.maximumBoundary; + }, + + _value: function(resource) + { + return 0; + }, + + formatValue: function(value) + { + return value.toString(); + } +} + +WebInspector.ResourceTimeCalculator = function(startAtZero) +{ + WebInspector.ResourceCalculator.call(this); + this.startAtZero = startAtZero; +} + +WebInspector.ResourceTimeCalculator.prototype = { + computeSummaryValues: function(resources) + { + var resourcesByCategory = {}; + var resourcesLength = resources.length; + for (var i = 0; i < resourcesLength; ++i) { + var resource = resources[i]; + if (!(resource.category.name in resourcesByCategory)) + resourcesByCategory[resource.category.name] = []; + resourcesByCategory[resource.category.name].push(resource); + } + + var earliestStart; + var latestEnd; + var categoryValues = {}; + for (var category in resourcesByCategory) { + resourcesByCategory[category].sort(WebInspector.Resource.CompareByTime); + categoryValues[category] = 0; + + var segment = {start: -1, end: -1}; + + var categoryResources = resourcesByCategory[category]; + var resourcesLength = categoryResources.length; + for (var i = 0; i < resourcesLength; ++i) { + var resource = categoryResources[i]; + if (resource.startTime === -1 || resource.endTime === -1) + continue; + + if (typeof earliestStart === "undefined") + earliestStart = resource.startTime; + else + earliestStart = Math.min(earliestStart, resource.startTime); + + if (typeof latestEnd === "undefined") + latestEnd = resource.endTime; + else + latestEnd = Math.max(latestEnd, resource.endTime); + + if (resource.startTime <= segment.end) { + segment.end = Math.max(segment.end, resource.endTime); + continue; + } + + categoryValues[category] += segment.end - segment.start; + + segment.start = resource.startTime; + segment.end = resource.endTime; + } + + // Add the last segment + categoryValues[category] += segment.end - segment.start; + } + + return {categoryValues: categoryValues, total: latestEnd - earliestStart}; + }, + + computeBarGraphPercentages: function(resource) + { + if (resource.startTime !== -1) + var start = ((resource.startTime - this.minimumBoundary) / this.boundarySpan) * 100; + else + var start = 0; + + if (resource.responseReceivedTime !== -1) + var middle = ((resource.responseReceivedTime - this.minimumBoundary) / this.boundarySpan) * 100; + else + var middle = (this.startAtZero ? start : 100); + + if (resource.endTime !== -1) + var end = ((resource.endTime - this.minimumBoundary) / this.boundarySpan) * 100; + else + var end = (this.startAtZero ? middle : 100); + + if (this.startAtZero) { + end -= start; + middle -= start; + start = 0; + } + + return {start: start, middle: middle, end: end}; + }, + + computeBarGraphLabels: function(resource) + { + var leftLabel = ""; + if (resource.latency > 0) + leftLabel = this.formatValue(resource.latency); + + var rightLabel = ""; + if (resource.responseReceivedTime !== -1 && resource.endTime !== -1) + rightLabel = this.formatValue(resource.endTime - resource.responseReceivedTime); + + if (leftLabel && rightLabel) { + var total = this.formatValue(resource.duration); + var tooltip = WebInspector.UIString("%s latency, %s download (%s total)", leftLabel, rightLabel, total); + } else if (leftLabel) + var tooltip = WebInspector.UIString("%s latency", leftLabel); + else if (rightLabel) + var tooltip = WebInspector.UIString("%s download", rightLabel); + + if (resource.cached) + tooltip = WebInspector.UIString("%s (from cache)", tooltip); + + return {left: leftLabel, right: rightLabel, tooltip: tooltip}; + }, + + updateBoundaries: function(resource) + { + var didChange = false; + + var lowerBound; + if (this.startAtZero) + lowerBound = 0; + else + lowerBound = this._lowerBound(resource); + + if (lowerBound !== -1 && (typeof this.minimumBoundary === "undefined" || lowerBound < this.minimumBoundary)) { + this.minimumBoundary = lowerBound; + didChange = true; + } + + var upperBound = this._upperBound(resource); + if (upperBound !== -1 && (typeof this.maximumBoundary === "undefined" || upperBound > this.maximumBoundary)) { + this.maximumBoundary = upperBound; + didChange = true; + } + + return didChange; + }, + + formatValue: function(value) + { + return Number.secondsToString(value, WebInspector.UIString.bind(WebInspector)); + }, + + _lowerBound: function(resource) + { + return 0; + }, + + _upperBound: function(resource) + { + return 0; + }, +} + +WebInspector.ResourceTimeCalculator.prototype.__proto__ = WebInspector.ResourceCalculator.prototype; + +WebInspector.ResourceTransferTimeCalculator = function() +{ + WebInspector.ResourceTimeCalculator.call(this, false); +} + +WebInspector.ResourceTransferTimeCalculator.prototype = { + formatValue: function(value) + { + return Number.secondsToString(value, WebInspector.UIString.bind(WebInspector)); + }, + + _lowerBound: function(resource) + { + return resource.startTime; + }, + + _upperBound: function(resource) + { + return resource.endTime; + } +} + +WebInspector.ResourceTransferTimeCalculator.prototype.__proto__ = WebInspector.ResourceTimeCalculator.prototype; + +WebInspector.ResourceTransferDurationCalculator = function() +{ + WebInspector.ResourceTimeCalculator.call(this, true); +} + +WebInspector.ResourceTransferDurationCalculator.prototype = { + formatValue: function(value) + { + return Number.secondsToString(value, WebInspector.UIString.bind(WebInspector)); + }, + + _upperBound: function(resource) + { + return resource.duration; + } +} + +WebInspector.ResourceTransferDurationCalculator.prototype.__proto__ = WebInspector.ResourceTimeCalculator.prototype; + +WebInspector.ResourceTransferSizeCalculator = function() +{ + WebInspector.ResourceCalculator.call(this); +} + +WebInspector.ResourceTransferSizeCalculator.prototype = { + _value: function(resource) + { + return resource.contentLength; + }, + + formatValue: function(value) + { + return Number.bytesToString(value, WebInspector.UIString.bind(WebInspector)); + } +} + +WebInspector.ResourceTransferSizeCalculator.prototype.__proto__ = WebInspector.ResourceCalculator.prototype; + +WebInspector.ResourceSidebarTreeElement = function(resource) +{ + this.resource = resource; + + this.createIconElement(); + + WebInspector.SidebarTreeElement.call(this, "resource-sidebar-tree-item", "", "", resource); + + this.refreshTitles(); +} + +WebInspector.ResourceSidebarTreeElement.prototype = { + onattach: function() + { + WebInspector.SidebarTreeElement.prototype.onattach.call(this); + + var link = document.createElement("a"); + link.href = this.resource.url; + link.className = "invisible"; + while (this._listItemNode.firstChild) + link.appendChild(this._listItemNode.firstChild); + this._listItemNode.appendChild(link); + this._listItemNode.addStyleClass("resources-category-" + this.resource.category.name); + }, + + onselect: function() + { + WebInspector.panels.resources.showResource(this.resource); + }, + + ondblclick: function(treeElement, event) + { + InjectedScriptAccess.openInInspectedWindow(this.resource.url); + }, + + get mainTitle() + { + return this.resource.displayName; + }, + + set mainTitle(x) + { + // Do nothing. + }, + + get subtitle() + { + var subtitle = this.resource.displayDomain; + + if (this.resource.path && this.resource.lastPathComponent) { + var lastPathComponentIndex = this.resource.path.lastIndexOf("/" + this.resource.lastPathComponent); + if (lastPathComponentIndex != -1) + subtitle += this.resource.path.substring(0, lastPathComponentIndex); + } + + return subtitle; + }, + + set subtitle(x) + { + // Do nothing. + }, + + get selectable() + { + return WebInspector.panels.resources._filterCategory == "all" || WebInspector.panels.resources._filterCategory == this.resource.category.name; + }, + + createIconElement: function() + { + var previousIconElement = this.iconElement; + + if (this.resource.category === WebInspector.resourceCategories.images) { + var previewImage = document.createElement("img"); + previewImage.className = "image-resource-icon-preview"; + previewImage.src = this.resource.url; + + this.iconElement = document.createElement("div"); + this.iconElement.className = "icon"; + this.iconElement.appendChild(previewImage); + } else { + this.iconElement = document.createElement("img"); + this.iconElement.className = "icon"; + } + + if (previousIconElement) + previousIconElement.parentNode.replaceChild(this.iconElement, previousIconElement); + }, + + refresh: function() + { + this.refreshTitles(); + + if (!this._listItemNode.hasStyleClass("resources-category-" + this.resource.category.name)) { + this._listItemNode.removeMatchingStyleClasses("resources-category-\\w+"); + this._listItemNode.addStyleClass("resources-category-" + this.resource.category.name); + + this.createIconElement(); + } + }, + + resetBubble: function() + { + this.bubbleText = ""; + this.bubbleElement.removeStyleClass("search-matches"); + this.bubbleElement.removeStyleClass("warning"); + this.bubbleElement.removeStyleClass("error"); + }, + + set searchMatches(matches) + { + this.resetBubble(); + + if (!matches) + return; + + this.bubbleText = matches; + this.bubbleElement.addStyleClass("search-matches"); + }, + + updateErrorsAndWarnings: function() + { + this.resetBubble(); + + if (this.resource.warnings || this.resource.errors) + this.bubbleText = (this.resource.warnings + this.resource.errors); + + if (this.resource.warnings) + this.bubbleElement.addStyleClass("warning"); + + if (this.resource.errors) + this.bubbleElement.addStyleClass("error"); + } +} + +WebInspector.ResourceSidebarTreeElement.CompareByAscendingStartTime = function(a, b) +{ + return WebInspector.Resource.CompareByStartTime(a.resource, b.resource) + || WebInspector.Resource.CompareByEndTime(a.resource, b.resource) + || WebInspector.Resource.CompareByResponseReceivedTime(a.resource, b.resource); +} + +WebInspector.ResourceSidebarTreeElement.CompareByAscendingResponseReceivedTime = function(a, b) +{ + return WebInspector.Resource.CompareByResponseReceivedTime(a.resource, b.resource) + || WebInspector.Resource.CompareByStartTime(a.resource, b.resource) + || WebInspector.Resource.CompareByEndTime(a.resource, b.resource); +} + +WebInspector.ResourceSidebarTreeElement.CompareByAscendingEndTime = function(a, b) +{ + return WebInspector.Resource.CompareByEndTime(a.resource, b.resource) + || WebInspector.Resource.CompareByStartTime(a.resource, b.resource) + || WebInspector.Resource.CompareByResponseReceivedTime(a.resource, b.resource); +} + +WebInspector.ResourceSidebarTreeElement.CompareByDescendingDuration = function(a, b) +{ + return -1 * WebInspector.Resource.CompareByDuration(a.resource, b.resource); +} + +WebInspector.ResourceSidebarTreeElement.CompareByDescendingLatency = function(a, b) +{ + return -1 * WebInspector.Resource.CompareByLatency(a.resource, b.resource); +} + +WebInspector.ResourceSidebarTreeElement.CompareByDescendingSize = function(a, b) +{ + return -1 * WebInspector.Resource.CompareBySize(a.resource, b.resource); +} + +WebInspector.ResourceSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype; + +WebInspector.ResourceGraph = function(resource) +{ + this.resource = resource; + + this._graphElement = document.createElement("div"); + this._graphElement.className = "resources-graph-side"; + this._graphElement.addEventListener("mouseover", this.refreshLabelPositions.bind(this), false); + + if (resource.cached) + this._graphElement.addStyleClass("resource-cached"); + + this._barAreaElement = document.createElement("div"); + this._barAreaElement.className = "resources-graph-bar-area hidden"; + this._graphElement.appendChild(this._barAreaElement); + + this._barLeftElement = document.createElement("div"); + this._barLeftElement.className = "resources-graph-bar waiting"; + this._barAreaElement.appendChild(this._barLeftElement); + + this._barRightElement = document.createElement("div"); + this._barRightElement.className = "resources-graph-bar"; + this._barAreaElement.appendChild(this._barRightElement); + + this._labelLeftElement = document.createElement("div"); + this._labelLeftElement.className = "resources-graph-label waiting"; + this._barAreaElement.appendChild(this._labelLeftElement); + + this._labelRightElement = document.createElement("div"); + this._labelRightElement.className = "resources-graph-label"; + this._barAreaElement.appendChild(this._labelRightElement); + + this._graphElement.addStyleClass("resources-category-" + resource.category.name); +} + +WebInspector.ResourceGraph.prototype = { + get graphElement() + { + return this._graphElement; + }, + + refreshLabelPositions: function() + { + this._labelLeftElement.style.removeProperty("left"); + this._labelLeftElement.style.removeProperty("right"); + this._labelLeftElement.removeStyleClass("before"); + this._labelLeftElement.removeStyleClass("hidden"); + + this._labelRightElement.style.removeProperty("left"); + this._labelRightElement.style.removeProperty("right"); + this._labelRightElement.removeStyleClass("after"); + this._labelRightElement.removeStyleClass("hidden"); + + const labelPadding = 10; + const rightBarWidth = (this._barRightElement.offsetWidth - labelPadding); + const leftBarWidth = ((this._barLeftElement.offsetWidth - this._barRightElement.offsetWidth) - labelPadding); + + var labelBefore = (this._labelLeftElement.offsetWidth > leftBarWidth); + var labelAfter = (this._labelRightElement.offsetWidth > rightBarWidth); + + if (labelBefore) { + if ((this._graphElement.offsetWidth * (this._percentages.start / 100)) < (this._labelLeftElement.offsetWidth + 10)) + this._labelLeftElement.addStyleClass("hidden"); + this._labelLeftElement.style.setProperty("right", (100 - this._percentages.start) + "%"); + this._labelLeftElement.addStyleClass("before"); + } else { + this._labelLeftElement.style.setProperty("left", this._percentages.start + "%"); + this._labelLeftElement.style.setProperty("right", (100 - this._percentages.middle) + "%"); + } + + if (labelAfter) { + if ((this._graphElement.offsetWidth * ((100 - this._percentages.end) / 100)) < (this._labelRightElement.offsetWidth + 10)) + this._labelRightElement.addStyleClass("hidden"); + this._labelRightElement.style.setProperty("left", this._percentages.end + "%"); + this._labelRightElement.addStyleClass("after"); + } else { + this._labelRightElement.style.setProperty("left", this._percentages.middle + "%"); + this._labelRightElement.style.setProperty("right", (100 - this._percentages.end) + "%"); + } + }, + + refresh: function(calculator) + { + var percentages = calculator.computeBarGraphPercentages(this.resource); + var labels = calculator.computeBarGraphLabels(this.resource); + + this._percentages = percentages; + + this._barAreaElement.removeStyleClass("hidden"); + + if (!this._graphElement.hasStyleClass("resources-category-" + this.resource.category.name)) { + this._graphElement.removeMatchingStyleClasses("resources-category-\\w+"); + this._graphElement.addStyleClass("resources-category-" + this.resource.category.name); + } + + this._barLeftElement.style.setProperty("left", percentages.start + "%"); + this._barLeftElement.style.setProperty("right", (100 - percentages.end) + "%"); + + this._barRightElement.style.setProperty("left", percentages.middle + "%"); + this._barRightElement.style.setProperty("right", (100 - percentages.end) + "%"); + + this._labelLeftElement.textContent = labels.left; + this._labelRightElement.textContent = labels.right; + + var tooltip = (labels.tooltip || ""); + this._barLeftElement.title = tooltip; + this._labelLeftElement.title = tooltip; + this._labelRightElement.title = tooltip; + this._barRightElement.title = tooltip; + } +} diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ScopeChainSidebarPane.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ScopeChainSidebarPane.js new file mode 100644 index 0000000..3875324 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ScopeChainSidebarPane.js @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.ScopeChainSidebarPane = function() +{ + WebInspector.SidebarPane.call(this, WebInspector.UIString("Scope Variables")); + this._expandedProperties = []; +} + +WebInspector.ScopeChainSidebarPane.prototype = { + update: function(callFrame) + { + this.bodyElement.removeChildren(); + + this.sections = []; + this.callFrame = callFrame; + + if (!callFrame) { + var infoElement = document.createElement("div"); + infoElement.className = "info"; + infoElement.textContent = WebInspector.UIString("Not Paused"); + this.bodyElement.appendChild(infoElement); + return; + } + + var foundLocalScope = false; + var scopeChain = callFrame.scopeChain; + for (var i = 0; i < scopeChain.length; ++i) { + var scopeObjectProxy = scopeChain[i]; + var title = null; + var subtitle = scopeObjectProxy.description; + var emptyPlaceholder = null; + var extraProperties = null; + + if (scopeObjectProxy.isLocal) { + if (scopeObjectProxy.thisObject) { + extraProperties = [ new WebInspector.ObjectPropertyProxy("this", scopeObjectProxy.thisObject) ]; + title = WebInspector.UIString("Local"); + } else + title = WebInspector.UIString("Closure"); + emptyPlaceholder = WebInspector.UIString("No Variables"); + subtitle = null; + foundLocalScope = true; + } else if (i === (scopeChain.length - 1)) + title = WebInspector.UIString("Global"); + else if (scopeObjectProxy.isElement) + title = WebInspector.UIString("Event Target"); + else if (scopeObjectProxy.isDocument) + title = WebInspector.UIString("Event Document"); + else if (scopeObjectProxy.isWithBlock) + title = WebInspector.UIString("With Block"); + + if (!title || title === subtitle) + subtitle = null; + + var section = new WebInspector.ObjectPropertiesSection(scopeObjectProxy, title, subtitle, emptyPlaceholder, true, extraProperties, WebInspector.ScopeVariableTreeElement); + section.editInSelectedCallFrameWhenPaused = true; + section.pane = this; + + if (!foundLocalScope || scopeObjectProxy.isLocal) + section.expanded = true; + + this.sections.push(section); + this.bodyElement.appendChild(section.element); + } + } +} + +WebInspector.ScopeChainSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype; + +WebInspector.ScopeVariableTreeElement = function(property) +{ + WebInspector.ObjectPropertyTreeElement.call(this, property); +} + +WebInspector.ScopeVariableTreeElement.prototype = { + onattach: function() + { + WebInspector.ObjectPropertyTreeElement.prototype.onattach.call(this); + if (this.hasChildren && this.propertyIdentifier in this.treeOutline.section.pane._expandedProperties) + this.expand(); + }, + + onexpand: function() + { + this.treeOutline.section.pane._expandedProperties[this.propertyIdentifier] = true; + }, + + oncollapse: function() + { + delete this.treeOutline.section.pane._expandedProperties[this.propertyIdentifier]; + }, + + get propertyIdentifier() + { + if ("_propertyIdentifier" in this) + return this._propertyIdentifier; + var section = this.treeOutline.section; + this._propertyIdentifier = section.title + ":" + (section.subtitle ? section.subtitle + ":" : "") + this.propertyPath; + return this._propertyIdentifier; + }, + + get propertyPath() + { + if ("_propertyPath" in this) + return this._propertyPath; + + var current = this; + var result; + + do { + if (result) + result = current.property.name + "." + result; + else + result = current.property.name; + current = current.parent; + } while (current && !current.root); + + this._propertyPath = result; + return result; + } +} + +WebInspector.ScopeVariableTreeElement.prototype.__proto__ = WebInspector.ObjectPropertyTreeElement.prototype; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Script.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Script.js new file mode 100644 index 0000000..e6413a9 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/Script.js @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.Script = function(sourceID, sourceURL, source, startingLine, errorLine, errorMessage) +{ + this.sourceID = sourceID; + this.sourceURL = sourceURL; + this.source = source; + this.startingLine = startingLine; + this.errorLine = errorLine; + this.errorMessage = errorMessage; + + // if no URL, look for "//@ sourceURL=" decorator + // note that this sourceURL comment decorator is behavior that FireBug added + // in it's 1.1 release as noted in the release notes: + // http://fbug.googlecode.com/svn/branches/firebug1.1/docs/ReleaseNotes_1.1.txt + if (!sourceURL) { + // use of [ \t] rather than \s is to prevent \n from matching + var pattern = /^\s*\/\/[ \t]*@[ \t]*sourceURL[ \t]*=[ \t]*(\S+).*$/m; + var match = pattern.exec(source); + + if (match) + this.sourceURL = WebInspector.UIString("(program): %s", match[1]); + } +} + +WebInspector.Script.prototype = { +} diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ScriptView.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ScriptView.js new file mode 100644 index 0000000..124190c --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ScriptView.js @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.ScriptView = function(script) +{ + WebInspector.View.call(this); + + this.element.addStyleClass("script-view"); + + this.script = script; + + this._frameNeedsSetup = true; + this._sourceFrameSetup = false; + + this.sourceFrame = new WebInspector.SourceFrame(null, this._addBreakpoint.bind(this)); + + this.element.appendChild(this.sourceFrame.element); +} + +WebInspector.ScriptView.prototype = { + show: function(parentElement) + { + WebInspector.View.prototype.show.call(this, parentElement); + this.setupSourceFrameIfNeeded(); + }, + + hide: function() + { + WebInspector.View.prototype.hide.call(this); + this._currentSearchResultIndex = -1; + }, + + setupSourceFrameIfNeeded: function() + { + if (!this._frameNeedsSetup) + return; + + this.attach(); + + if (!InspectorController.addSourceToFrame("text/javascript", this.script.source, this.sourceFrame.element)) + return; + + delete this._frameNeedsSetup; + + this.sourceFrame.addEventListener("syntax highlighting complete", this._syntaxHighlightingComplete, this); + this.sourceFrame.syntaxHighlightJavascript(); + }, + + attach: function() + { + if (!this.element.parentNode) + document.getElementById("script-resource-views").appendChild(this.element); + }, + + _addBreakpoint: function(line) + { + var breakpoint = new WebInspector.Breakpoint(this.script.sourceURL, line, this.script.sourceID); + WebInspector.panels.scripts.addBreakpoint(breakpoint); + }, + + // The follow methods are pulled from SourceView, since they are + // generic and work with ScriptView just fine. + + revealLine: WebInspector.SourceView.prototype.revealLine, + highlightLine: WebInspector.SourceView.prototype.highlightLine, + addMessage: WebInspector.SourceView.prototype.addMessage, + clearMessages: WebInspector.SourceView.prototype.clearMessages, + searchCanceled: WebInspector.SourceView.prototype.searchCanceled, + performSearch: WebInspector.SourceView.prototype.performSearch, + jumpToFirstSearchResult: WebInspector.SourceView.prototype.jumpToFirstSearchResult, + jumpToLastSearchResult: WebInspector.SourceView.prototype.jumpToLastSearchResult, + jumpToNextSearchResult: WebInspector.SourceView.prototype.jumpToNextSearchResult, + jumpToPreviousSearchResult: WebInspector.SourceView.prototype.jumpToPreviousSearchResult, + showingFirstSearchResult: WebInspector.SourceView.prototype.showingFirstSearchResult, + showingLastSearchResult: WebInspector.SourceView.prototype.showingLastSearchResult, + _jumpToSearchResult: WebInspector.SourceView.prototype._jumpToSearchResult, + _sourceFrameSetupFinished: WebInspector.SourceView.prototype._sourceFrameSetupFinished, + _syntaxHighlightingComplete: WebInspector.SourceView.prototype._syntaxHighlightingComplete +} + +WebInspector.ScriptView.prototype.__proto__ = WebInspector.View.prototype; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ScriptsPanel.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ScriptsPanel.js new file mode 100644 index 0000000..04f27bb --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/ScriptsPanel.js @@ -0,0 +1,918 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.ScriptsPanel = function() +{ + WebInspector.Panel.call(this); + + this.element.addStyleClass("scripts"); + + this.topStatusBar = document.createElement("div"); + this.topStatusBar.className = "status-bar"; + this.topStatusBar.id = "scripts-status-bar"; + this.element.appendChild(this.topStatusBar); + + this.backButton = document.createElement("button"); + this.backButton.className = "status-bar-item"; + this.backButton.id = "scripts-back"; + this.backButton.title = WebInspector.UIString("Show the previous script resource."); + this.backButton.disabled = true; + this.backButton.appendChild(document.createElement("img")); + this.backButton.addEventListener("click", this._goBack.bind(this), false); + this.topStatusBar.appendChild(this.backButton); + + this.forwardButton = document.createElement("button"); + this.forwardButton.className = "status-bar-item"; + this.forwardButton.id = "scripts-forward"; + this.forwardButton.title = WebInspector.UIString("Show the next script resource."); + this.forwardButton.disabled = true; + this.forwardButton.appendChild(document.createElement("img")); + this.forwardButton.addEventListener("click", this._goForward.bind(this), false); + this.topStatusBar.appendChild(this.forwardButton); + + this.filesSelectElement = document.createElement("select"); + this.filesSelectElement.className = "status-bar-item"; + this.filesSelectElement.id = "scripts-files"; + this.filesSelectElement.addEventListener("change", this._changeVisibleFile.bind(this), false); + this.filesSelectElement.handleKeyEvent = this.handleKeyEvent.bind(this); + this.topStatusBar.appendChild(this.filesSelectElement); + + this.functionsSelectElement = document.createElement("select"); + this.functionsSelectElement.className = "status-bar-item"; + this.functionsSelectElement.id = "scripts-functions"; + + // FIXME: append the functions select element to the top status bar when it is implemented. + // this.topStatusBar.appendChild(this.functionsSelectElement); + + this.sidebarButtonsElement = document.createElement("div"); + this.sidebarButtonsElement.id = "scripts-sidebar-buttons"; + this.topStatusBar.appendChild(this.sidebarButtonsElement); + + this.pauseButton = document.createElement("button"); + this.pauseButton.className = "status-bar-item"; + this.pauseButton.id = "scripts-pause"; + this.pauseButton.title = WebInspector.UIString("Pause script execution."); + this.pauseButton.disabled = true; + this.pauseButton.appendChild(document.createElement("img")); + this.pauseButton.addEventListener("click", this._togglePause.bind(this), false); + this.sidebarButtonsElement.appendChild(this.pauseButton); + + this.stepOverButton = document.createElement("button"); + this.stepOverButton.className = "status-bar-item"; + this.stepOverButton.id = "scripts-step-over"; + this.stepOverButton.title = WebInspector.UIString("Step over next function call."); + this.stepOverButton.disabled = true; + this.stepOverButton.addEventListener("click", this._stepOverClicked.bind(this), false); + this.stepOverButton.appendChild(document.createElement("img")); + this.sidebarButtonsElement.appendChild(this.stepOverButton); + + this.stepIntoButton = document.createElement("button"); + this.stepIntoButton.className = "status-bar-item"; + this.stepIntoButton.id = "scripts-step-into"; + this.stepIntoButton.title = WebInspector.UIString("Step into next function call."); + this.stepIntoButton.disabled = true; + this.stepIntoButton.addEventListener("click", this._stepIntoClicked.bind(this), false); + this.stepIntoButton.appendChild(document.createElement("img")); + this.sidebarButtonsElement.appendChild(this.stepIntoButton); + + this.stepOutButton = document.createElement("button"); + this.stepOutButton.className = "status-bar-item"; + this.stepOutButton.id = "scripts-step-out"; + this.stepOutButton.title = WebInspector.UIString("Step out of current function."); + this.stepOutButton.disabled = true; + this.stepOutButton.addEventListener("click", this._stepOutClicked.bind(this), false); + this.stepOutButton.appendChild(document.createElement("img")); + this.sidebarButtonsElement.appendChild(this.stepOutButton); + + this.debuggerStatusElement = document.createElement("div"); + this.debuggerStatusElement.id = "scripts-debugger-status"; + this.sidebarButtonsElement.appendChild(this.debuggerStatusElement); + + this.viewsContainerElement = document.createElement("div"); + this.viewsContainerElement.id = "script-resource-views"; + + this.sidebarElement = document.createElement("div"); + this.sidebarElement.id = "scripts-sidebar"; + + this.sidebarResizeElement = document.createElement("div"); + this.sidebarResizeElement.className = "sidebar-resizer-vertical"; + this.sidebarResizeElement.addEventListener("mousedown", this._startSidebarResizeDrag.bind(this), false); + + this.sidebarResizeWidgetElement = document.createElement("div"); + this.sidebarResizeWidgetElement.id = "scripts-sidebar-resizer-widget"; + this.sidebarResizeWidgetElement.addEventListener("mousedown", this._startSidebarResizeDrag.bind(this), false); + this.topStatusBar.appendChild(this.sidebarResizeWidgetElement); + + this.sidebarPanes = {}; + this.sidebarPanes.watchExpressions = new WebInspector.WatchExpressionsSidebarPane(); + this.sidebarPanes.callstack = new WebInspector.CallStackSidebarPane(); + this.sidebarPanes.scopechain = new WebInspector.ScopeChainSidebarPane(); + this.sidebarPanes.breakpoints = new WebInspector.BreakpointsSidebarPane(); + + for (var pane in this.sidebarPanes) + this.sidebarElement.appendChild(this.sidebarPanes[pane].element); + + this.sidebarPanes.callstack.expanded = true; + this.sidebarPanes.callstack.addEventListener("call frame selected", this._callFrameSelected, this); + + this.sidebarPanes.scopechain.expanded = true; + this.sidebarPanes.breakpoints.expanded = true; + + var panelEnablerHeading = WebInspector.UIString("You need to enable debugging before you can use the Scripts panel."); + var panelEnablerDisclaimer = WebInspector.UIString("Enabling debugging will make scripts run slower."); + var panelEnablerButton = WebInspector.UIString("Enable Debugging"); + + this.panelEnablerView = new WebInspector.PanelEnablerView("scripts", panelEnablerHeading, panelEnablerDisclaimer, panelEnablerButton); + this.panelEnablerView.addEventListener("enable clicked", this._enableDebugging, this); + + this.element.appendChild(this.panelEnablerView.element); + this.element.appendChild(this.viewsContainerElement); + this.element.appendChild(this.sidebarElement); + this.element.appendChild(this.sidebarResizeElement); + + this.enableToggleButton = new WebInspector.StatusBarButton("", "enable-toggle-status-bar-item"); + this.enableToggleButton.addEventListener("click", this._toggleDebugging.bind(this), false); + + this.pauseOnExceptionButton = new WebInspector.StatusBarButton("", "scripts-pause-on-exceptions-status-bar-item"); + this.pauseOnExceptionButton.addEventListener("click", this._togglePauseOnExceptions.bind(this), false); + + this._breakpointsURLMap = {}; + + this._shortcuts = {}; + + var isMac = InspectorController.platform().indexOf("mac-") === 0; + var platformSpecificModifier = isMac ? WebInspector.KeyboardShortcut.Modifiers.Meta : WebInspector.KeyboardShortcut.Modifiers.Ctrl; + + // Continue. + var handler = this.pauseButton.click.bind(this.pauseButton); + var shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.F8); + this._shortcuts[shortcut] = handler; + var shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.Slash, platformSpecificModifier); + this._shortcuts[shortcut] = handler; + + // Step over. + var handler = this.stepOverButton.click.bind(this.stepOverButton); + var shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.F10); + this._shortcuts[shortcut] = handler; + var shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.SingleQuote, platformSpecificModifier); + this._shortcuts[shortcut] = handler; + + // Step into. + var handler = this.stepIntoButton.click.bind(this.stepIntoButton); + var shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.F11); + this._shortcuts[shortcut] = handler; + var shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.Semicolon, platformSpecificModifier); + this._shortcuts[shortcut] = handler; + + // Step out. + var handler = this.stepOutButton.click.bind(this.stepOutButton); + var shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.F11, WebInspector.KeyboardShortcut.Modifiers.Shift); + this._shortcuts[shortcut] = handler; + var shortcut = WebInspector.KeyboardShortcut.makeKey(WebInspector.KeyboardShortcut.KeyCodes.Semicolon, WebInspector.KeyboardShortcut.Modifiers.Shift, platformSpecificModifier); + this._shortcuts[shortcut] = handler; + + this.reset(); +} + +WebInspector.ScriptsPanel.prototype = { + toolbarItemClass: "scripts", + + get toolbarItemLabel() + { + return WebInspector.UIString("Scripts"); + }, + + get statusBarItems() + { + return [this.enableToggleButton.element, this.pauseOnExceptionButton.element]; + }, + + get paused() + { + return this._paused; + }, + + show: function() + { + WebInspector.Panel.prototype.show.call(this); + this.sidebarResizeElement.style.right = (this.sidebarElement.offsetWidth - 3) + "px"; + + if (this.visibleView) { + if (this.visibleView instanceof WebInspector.ResourceView) + this.visibleView.headersVisible = false; + this.visibleView.show(this.viewsContainerElement); + } + + // Hide any views that are visible that are not this panel's current visible view. + // This can happen when a ResourceView is visible in the Resources panel then switched + // to the this panel. + for (var sourceID in this._sourceIDMap) { + var scriptOrResource = this._sourceIDMap[sourceID]; + var view = this._sourceViewForScriptOrResource(scriptOrResource); + if (!view || view === this.visibleView) + continue; + view.visible = false; + } + if (this._attachDebuggerWhenShown) { + InspectorController.enableDebugger(false); + delete this._attachDebuggerWhenShown; + } + }, + + get searchableViews() + { + var views = []; + + const visibleView = this.visibleView; + if (visibleView && visibleView.performSearch) { + visibleView.alreadySearching = true; + views.push(visibleView); + } + + for (var sourceID in this._sourceIDMap) { + var scriptOrResource = this._sourceIDMap[sourceID]; + var view = this._sourceViewForScriptOrResource(scriptOrResource); + if (!view || !view.performSearch || view.alreadySearching) + continue; + + view.alreadySearching = true; + views.push(view); + } + + for (var i = 0; i < views.length; ++i) + delete views[i].alreadySearching; + + return views; + }, + + addScript: function(sourceID, sourceURL, source, startingLine, errorLine, errorMessage) + { + var script = new WebInspector.Script(sourceID, sourceURL, source, startingLine, errorLine, errorMessage); + + if (sourceURL in WebInspector.resourceURLMap) { + var resource = WebInspector.resourceURLMap[sourceURL]; + resource.addScript(script); + } + + if (sourceURL in this._breakpointsURLMap && sourceID) { + var breakpoints = this._breakpointsURLMap[sourceURL]; + var breakpointsLength = breakpoints.length; + for (var i = 0; i < breakpointsLength; ++i) { + var breakpoint = breakpoints[i]; + if (startingLine <= breakpoint.line) { + breakpoint.sourceID = sourceID; + if (breakpoint.enabled) + InspectorController.addBreakpoint(breakpoint.sourceID, breakpoint.line, breakpoint.condition); + } + } + } + + if (sourceID) + this._sourceIDMap[sourceID] = (resource || script); + + this._addScriptToFilesMenu(script); + }, + + scriptOrResourceForID: function(id) + { + return this._sourceIDMap[id]; + }, + + addBreakpoint: function(breakpoint) + { + this.sidebarPanes.breakpoints.addBreakpoint(breakpoint); + + var sourceFrame; + if (breakpoint.url) { + if (!(breakpoint.url in this._breakpointsURLMap)) + this._breakpointsURLMap[breakpoint.url] = []; + this._breakpointsURLMap[breakpoint.url].unshift(breakpoint); + + if (breakpoint.url in WebInspector.resourceURLMap) { + var resource = WebInspector.resourceURLMap[breakpoint.url]; + sourceFrame = this._sourceFrameForScriptOrResource(resource); + } + } + + if (breakpoint.sourceID && !sourceFrame) { + var object = this._sourceIDMap[breakpoint.sourceID] + sourceFrame = this._sourceFrameForScriptOrResource(object); + } + + if (sourceFrame) + sourceFrame.addBreakpoint(breakpoint); + }, + + removeBreakpoint: function(breakpoint) + { + this.sidebarPanes.breakpoints.removeBreakpoint(breakpoint); + + var sourceFrame; + if (breakpoint.url && breakpoint.url in this._breakpointsURLMap) { + var breakpoints = this._breakpointsURLMap[breakpoint.url]; + breakpoints.remove(breakpoint); + if (!breakpoints.length) + delete this._breakpointsURLMap[breakpoint.url]; + + if (breakpoint.url in WebInspector.resourceURLMap) { + var resource = WebInspector.resourceURLMap[breakpoint.url]; + sourceFrame = this._sourceFrameForScriptOrResource(resource); + } + } + + if (breakpoint.sourceID && !sourceFrame) { + var object = this._sourceIDMap[breakpoint.sourceID] + sourceFrame = this._sourceFrameForScriptOrResource(object); + } + + if (sourceFrame) + sourceFrame.removeBreakpoint(breakpoint); + }, + + evaluateInSelectedCallFrame: function(code, updateInterface, callback) + { + var selectedCallFrame = this.sidebarPanes.callstack.selectedCallFrame; + if (!this._paused || !selectedCallFrame) + return; + + if (typeof updateInterface === "undefined") + updateInterface = true; + + var self = this; + function updatingCallbackWrapper(result, exception) + { + callback(result, exception); + if (updateInterface) + self.sidebarPanes.scopechain.update(selectedCallFrame); + } + this.doEvalInCallFrame(selectedCallFrame, code, updatingCallbackWrapper); + }, + + doEvalInCallFrame: function(callFrame, code, callback) + { + function evalCallback(result) + { + if (result) + callback(result.value, result.isException); + } + InjectedScriptAccess.evaluateInCallFrame(callFrame.id, code, evalCallback); + }, + + variablesInSelectedCallFrame: function() + { + var selectedCallFrame = this.sidebarPanes.callstack.selectedCallFrame; + if (!this._paused || !selectedCallFrame) + return {}; + + var result = {}; + var scopeChain = selectedCallFrame.scopeChain; + for (var i = 0; i < scopeChain.length; ++i) { + var scopeObjectProperties = scopeChain[i].properties; + for (var j = 0; j < scopeObjectProperties.length; ++j) + result[scopeObjectProperties[j]] = true; + } + return result; + }, + + debuggerPaused: function(callFrames) + { + this._paused = true; + this._waitingToPause = false; + this._stepping = false; + + this._updateDebuggerButtons(); + + this.sidebarPanes.callstack.update(callFrames, this._sourceIDMap); + this.sidebarPanes.callstack.selectedCallFrame = callFrames[0]; + + WebInspector.currentPanel = this; + window.focus(); + }, + + debuggerResumed: function() + { + this._paused = false; + this._waitingToPause = false; + this._stepping = false; + + this._clearInterface(); + }, + + attachDebuggerWhenShown: function() + { + if (this.element.parentElement) { + InspectorController.enableDebugger(false); + } else { + this._attachDebuggerWhenShown = true; + } + }, + + debuggerWasEnabled: function() + { + this.reset(); + }, + + debuggerWasDisabled: function() + { + this.reset(); + }, + + reset: function() + { + this.visibleView = null; + + delete this.currentQuery; + this.searchCanceled(); + + if (!InspectorController.debuggerEnabled()) { + this._paused = false; + this._waitingToPause = false; + this._stepping = false; + } + + this._clearInterface(); + + this._backForwardList = []; + this._currentBackForwardIndex = -1; + this._updateBackAndForwardButtons(); + + this._scriptsForURLsInFilesSelect = {}; + this.filesSelectElement.removeChildren(); + this.functionsSelectElement.removeChildren(); + this.viewsContainerElement.removeChildren(); + + if (this._sourceIDMap) { + for (var sourceID in this._sourceIDMap) { + var object = this._sourceIDMap[sourceID]; + if (object instanceof WebInspector.Resource) + object.removeAllScripts(); + } + } + + this._sourceIDMap = {}; + + this.sidebarPanes.watchExpressions.refreshExpressions(); + }, + + get visibleView() + { + return this._visibleView; + }, + + set visibleView(x) + { + if (this._visibleView === x) + return; + + if (this._visibleView) + this._visibleView.hide(); + + this._visibleView = x; + + if (x) + x.show(this.viewsContainerElement); + }, + + canShowResource: function(resource) + { + return resource && resource.scripts.length && InspectorController.debuggerEnabled(); + }, + + showScript: function(script, line) + { + this._showScriptOrResource(script, line, true); + }, + + showResource: function(resource, line) + { + this._showScriptOrResource(resource, line, true); + }, + + showView: function(view) + { + if (!view) + return; + this._showScriptOrResource((view.resource || view.script)); + }, + + handleKeyEvent: function(event) + { + var shortcut = WebInspector.KeyboardShortcut.makeKeyFromEvent(event); + var handler = this._shortcuts[shortcut]; + if (handler) { + handler(event); + event.preventDefault(); + event.handled = true; + } else { + this.sidebarPanes.callstack.handleKeyEvent(event); + } + }, + + scriptViewForScript: function(script) + { + if (!script) + return null; + if (!script._scriptView) + script._scriptView = new WebInspector.ScriptView(script); + return script._scriptView; + }, + + sourceFrameForScript: function(script) + { + var view = this.scriptViewForScript(script); + if (!view) + return null; + + // Setting up the source frame requires that we be attached. + if (!this.element.parentNode) + this.attach(); + + view.setupSourceFrameIfNeeded(); + return view.sourceFrame; + }, + + _sourceViewForScriptOrResource: function(scriptOrResource) + { + if (scriptOrResource instanceof WebInspector.Resource) { + if (!WebInspector.panels.resources) + return null; + return WebInspector.panels.resources.resourceViewForResource(scriptOrResource); + } + if (scriptOrResource instanceof WebInspector.Script) + return this.scriptViewForScript(scriptOrResource); + }, + + _sourceFrameForScriptOrResource: function(scriptOrResource) + { + if (scriptOrResource instanceof WebInspector.Resource) { + if (!WebInspector.panels.resources) + return null; + return WebInspector.panels.resources.sourceFrameForResource(scriptOrResource); + } + if (scriptOrResource instanceof WebInspector.Script) + return this.sourceFrameForScript(scriptOrResource); + }, + + _showScriptOrResource: function(scriptOrResource, line, shouldHighlightLine, fromBackForwardAction) + { + if (!scriptOrResource) + return; + + var view; + if (scriptOrResource instanceof WebInspector.Resource) { + if (!WebInspector.panels.resources) + return null; + view = WebInspector.panels.resources.resourceViewForResource(scriptOrResource); + view.headersVisible = false; + + if (scriptOrResource.url in this._breakpointsURLMap) { + var sourceFrame = this._sourceFrameForScriptOrResource(scriptOrResource); + if (sourceFrame && !sourceFrame.breakpoints.length) { + var breakpoints = this._breakpointsURLMap[scriptOrResource.url]; + var breakpointsLength = breakpoints.length; + for (var i = 0; i < breakpointsLength; ++i) + sourceFrame.addBreakpoint(breakpoints[i]); + } + } + } else if (scriptOrResource instanceof WebInspector.Script) + view = this.scriptViewForScript(scriptOrResource); + + if (!view) + return; + + if (!fromBackForwardAction) { + var oldIndex = this._currentBackForwardIndex; + if (oldIndex >= 0) + this._backForwardList.splice(oldIndex + 1, this._backForwardList.length - oldIndex); + + // Check for a previous entry of the same object in _backForwardList. + // If one is found, remove it and update _currentBackForwardIndex to match. + var previousEntryIndex = this._backForwardList.indexOf(scriptOrResource); + if (previousEntryIndex !== -1) { + this._backForwardList.splice(previousEntryIndex, 1); + --this._currentBackForwardIndex; + } + + this._backForwardList.push(scriptOrResource); + ++this._currentBackForwardIndex; + + this._updateBackAndForwardButtons(); + } + + this.visibleView = view; + + if (line) { + if (view.revealLine) + view.revealLine(line); + if (view.highlightLine && shouldHighlightLine) + view.highlightLine(line); + } + + var option; + if (scriptOrResource instanceof WebInspector.Script) { + option = scriptOrResource.filesSelectOption; + console.assert(option); + } else { + var url = scriptOrResource.url; + var script = this._scriptsForURLsInFilesSelect[url]; + if (script) + option = script.filesSelectOption; + } + + if (option) + this.filesSelectElement.selectedIndex = option.index; + }, + + _addScriptToFilesMenu: function(script) + { + if (script.resource && this._scriptsForURLsInFilesSelect[script.sourceURL]) + return; + + this._scriptsForURLsInFilesSelect[script.sourceURL] = script; + + var select = this.filesSelectElement; + + var option = document.createElement("option"); + option.representedObject = (script.resource || script); + option.text = (script.sourceURL ? WebInspector.displayNameForURL(script.sourceURL) : WebInspector.UIString("(program)")); + + function optionCompare(a, b) + { + var aTitle = a.text.toLowerCase(); + var bTitle = b.text.toLowerCase(); + if (aTitle < bTitle) + return -1; + else if (aTitle > bTitle) + return 1; + + var aSourceID = a.representedObject.sourceID; + var bSourceID = b.representedObject.sourceID; + if (aSourceID < bSourceID) + return -1; + else if (aSourceID > bSourceID) + return 1; + return 0; + } + + var insertionIndex = insertionIndexForObjectInListSortedByFunction(option, select.childNodes, optionCompare); + if (insertionIndex < 0) + select.appendChild(option); + else + select.insertBefore(option, select.childNodes.item(insertionIndex)); + + script.filesSelectOption = option; + + // Call _showScriptOrResource if the option we just appended ended up being selected. + // This will happen for the first item added to the menu. + if (select.options[select.selectedIndex] === option) + this._showScriptOrResource(option.representedObject); + }, + + _clearCurrentExecutionLine: function() + { + if (this._executionSourceFrame) + this._executionSourceFrame.executionLine = 0; + delete this._executionSourceFrame; + }, + + _callFrameSelected: function() + { + this._clearCurrentExecutionLine(); + + var callStackPane = this.sidebarPanes.callstack; + var currentFrame = callStackPane.selectedCallFrame; + if (!currentFrame) + return; + + this.sidebarPanes.scopechain.update(currentFrame); + this.sidebarPanes.watchExpressions.refreshExpressions(); + + var scriptOrResource = this._sourceIDMap[currentFrame.sourceID]; + this._showScriptOrResource(scriptOrResource, currentFrame.line); + + this._executionSourceFrame = this._sourceFrameForScriptOrResource(scriptOrResource); + if (this._executionSourceFrame) + this._executionSourceFrame.executionLine = currentFrame.line; + }, + + _changeVisibleFile: function(event) + { + var select = this.filesSelectElement; + this._showScriptOrResource(select.options[select.selectedIndex].representedObject); + }, + + _startSidebarResizeDrag: function(event) + { + WebInspector.elementDragStart(this.sidebarElement, this._sidebarResizeDrag.bind(this), this._endSidebarResizeDrag.bind(this), event, "col-resize"); + + if (event.target === this.sidebarResizeWidgetElement) + this._dragOffset = (event.target.offsetWidth - (event.pageX - event.target.totalOffsetLeft)); + else + this._dragOffset = 0; + }, + + _endSidebarResizeDrag: function(event) + { + WebInspector.elementDragEnd(event); + + delete this._dragOffset; + }, + + _sidebarResizeDrag: function(event) + { + var x = event.pageX + this._dragOffset; + var newWidth = Number.constrain(window.innerWidth - x, Preferences.minScriptsSidebarWidth, window.innerWidth * 0.66); + + this.sidebarElement.style.width = newWidth + "px"; + this.sidebarButtonsElement.style.width = newWidth + "px"; + this.viewsContainerElement.style.right = newWidth + "px"; + this.sidebarResizeWidgetElement.style.right = newWidth + "px"; + this.sidebarResizeElement.style.right = (newWidth - 3) + "px"; + + event.preventDefault(); + }, + + _updatePauseOnExceptionsButton: function() + { + if (InspectorController.pauseOnExceptions()) { + this.pauseOnExceptionButton.title = WebInspector.UIString("Don't pause on exceptions."); + this.pauseOnExceptionButton.toggled = true; + } else { + this.pauseOnExceptionButton.title = WebInspector.UIString("Pause on exceptions."); + this.pauseOnExceptionButton.toggled = false; + } + }, + + _updateDebuggerButtons: function() + { + if (InspectorController.debuggerEnabled()) { + this.enableToggleButton.title = WebInspector.UIString("Debugging enabled. Click to disable."); + this.enableToggleButton.toggled = true; + this.pauseOnExceptionButton.visible = true; + this.panelEnablerView.visible = false; + } else { + this.enableToggleButton.title = WebInspector.UIString("Debugging disabled. Click to enable."); + this.enableToggleButton.toggled = false; + this.pauseOnExceptionButton.visible = false; + this.panelEnablerView.visible = true; + } + + this._updatePauseOnExceptionsButton(); + + if (this._paused) { + this.pauseButton.addStyleClass("paused"); + + this.pauseButton.disabled = false; + this.stepOverButton.disabled = false; + this.stepIntoButton.disabled = false; + this.stepOutButton.disabled = false; + + this.debuggerStatusElement.textContent = WebInspector.UIString("Paused"); + } else { + this.pauseButton.removeStyleClass("paused"); + + this.pauseButton.disabled = this._waitingToPause; + this.stepOverButton.disabled = true; + this.stepIntoButton.disabled = true; + this.stepOutButton.disabled = true; + + if (this._waitingToPause) + this.debuggerStatusElement.textContent = WebInspector.UIString("Pausing"); + else if (this._stepping) + this.debuggerStatusElement.textContent = WebInspector.UIString("Stepping"); + else + this.debuggerStatusElement.textContent = ""; + } + }, + + _updateBackAndForwardButtons: function() + { + this.backButton.disabled = this._currentBackForwardIndex <= 0; + this.forwardButton.disabled = this._currentBackForwardIndex >= (this._backForwardList.length - 1); + }, + + _clearInterface: function() + { + this.sidebarPanes.callstack.update(null); + this.sidebarPanes.scopechain.update(null); + + this._clearCurrentExecutionLine(); + this._updateDebuggerButtons(); + }, + + _goBack: function() + { + if (this._currentBackForwardIndex <= 0) { + console.error("Can't go back from index " + this._currentBackForwardIndex); + return; + } + + this._showScriptOrResource(this._backForwardList[--this._currentBackForwardIndex], null, false, true); + this._updateBackAndForwardButtons(); + }, + + _goForward: function() + { + if (this._currentBackForwardIndex >= this._backForwardList.length - 1) { + console.error("Can't go forward from index " + this._currentBackForwardIndex); + return; + } + + this._showScriptOrResource(this._backForwardList[++this._currentBackForwardIndex], null, false, true); + this._updateBackAndForwardButtons(); + }, + + _enableDebugging: function() + { + if (InspectorController.debuggerEnabled()) + return; + this._toggleDebugging(this.panelEnablerView.alwaysEnabled); + }, + + _toggleDebugging: function(optionalAlways) + { + this._paused = false; + this._waitingToPause = false; + this._stepping = false; + + if (InspectorController.debuggerEnabled()) + InspectorController.disableDebugger(true); + else + InspectorController.enableDebugger(!!optionalAlways); + }, + + _togglePauseOnExceptions: function() + { + InspectorController.setPauseOnExceptions(!InspectorController.pauseOnExceptions()); + this._updatePauseOnExceptionsButton(); + }, + + _togglePause: function() + { + if (this._paused) { + this._paused = false; + this._waitingToPause = false; + InspectorController.resumeDebugger(); + } else { + this._stepping = false; + this._waitingToPause = true; + InspectorController.pauseInDebugger(); + } + + this._clearInterface(); + }, + + _stepOverClicked: function() + { + this._paused = false; + this._stepping = true; + + this._clearInterface(); + + InspectorController.stepOverStatementInDebugger(); + }, + + _stepIntoClicked: function() + { + this._paused = false; + this._stepping = true; + + this._clearInterface(); + + InspectorController.stepIntoStatementInDebugger(); + }, + + _stepOutClicked: function() + { + this._paused = false; + this._stepping = true; + + this._clearInterface(); + + InspectorController.stepOutOfFunctionInDebugger(); + } +} + +WebInspector.ScriptsPanel.prototype.__proto__ = WebInspector.Panel.prototype; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/SidebarPane.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/SidebarPane.js new file mode 100644 index 0000000..af9e5f9 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/SidebarPane.js @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.SidebarPane = function(title) +{ + this.element = document.createElement("div"); + this.element.className = "pane"; + + this.titleElement = document.createElement("div"); + this.titleElement.className = "title"; + this.titleElement.addEventListener("click", this.toggleExpanded.bind(this), false); + + this.bodyElement = document.createElement("div"); + this.bodyElement.className = "body"; + + this.element.appendChild(this.titleElement); + this.element.appendChild(this.bodyElement); + + this.title = title; + this.growbarVisible = false; + this.expanded = false; +} + +WebInspector.SidebarPane.prototype = { + get title() + { + return this._title; + }, + + set title(x) + { + if (this._title === x) + return; + this._title = x; + this.titleElement.textContent = x; + }, + + get growbarVisible() + { + return this._growbarVisible; + }, + + set growbarVisible(x) + { + if (this._growbarVisible === x) + return; + + this._growbarVisible = x; + + if (x && !this._growbarElement) { + this._growbarElement = document.createElement("div"); + this._growbarElement.className = "growbar"; + this.element.appendChild(this._growbarElement); + } else if (!x && this._growbarElement) { + if (this._growbarElement.parentNode) + this._growbarElement.parentNode(this._growbarElement); + delete this._growbarElement; + } + }, + + get expanded() + { + return this._expanded; + }, + + set expanded(x) + { + if (x) + this.expand(); + else + this.collapse(); + }, + + expand: function() + { + if (this._expanded) + return; + this._expanded = true; + this.element.addStyleClass("expanded"); + if (this.onexpand) + this.onexpand(this); + }, + + collapse: function() + { + if (!this._expanded) + return; + this._expanded = false; + this.element.removeStyleClass("expanded"); + if (this.oncollapse) + this.oncollapse(this); + }, + + toggleExpanded: function() + { + this.expanded = !this.expanded; + } +} + +WebInspector.SidebarPane.prototype.__proto__ = WebInspector.Object.prototype; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/SidebarTreeElement.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/SidebarTreeElement.js new file mode 100644 index 0000000..c08b0ef --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/SidebarTreeElement.js @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.SidebarSectionTreeElement = function(title, representedObject, hasChildren) +{ + TreeElement.call(this, title.escapeHTML(), representedObject || {}, hasChildren); +} + +WebInspector.SidebarSectionTreeElement.prototype = { + selectable: false, + + get smallChildren() + { + return this._smallChildren; + }, + + set smallChildren(x) + { + if (this._smallChildren === x) + return; + + this._smallChildren = x; + + if (this._smallChildren) + this._childrenListNode.addStyleClass("small"); + else + this._childrenListNode.removeStyleClass("small"); + }, + + onattach: function() + { + this._listItemNode.addStyleClass("sidebar-tree-section"); + }, + + onreveal: function() + { + if (this.listItemElement) + this.listItemElement.scrollIntoViewIfNeeded(false); + } +} + +WebInspector.SidebarSectionTreeElement.prototype.__proto__ = TreeElement.prototype; + +WebInspector.SidebarTreeElement = function(className, title, subtitle, representedObject, hasChildren) +{ + TreeElement.call(this, "", representedObject || {}, hasChildren); + + if (hasChildren) { + this.disclosureButton = document.createElement("button"); + this.disclosureButton.className = "disclosure-button"; + } + + if (!this.iconElement) { + this.iconElement = document.createElement("img"); + this.iconElement.className = "icon"; + } + + this.statusElement = document.createElement("div"); + this.statusElement.className = "status"; + + this.titlesElement = document.createElement("div"); + this.titlesElement.className = "titles"; + + this.titleElement = document.createElement("span"); + this.titleElement.className = "title"; + this.titlesElement.appendChild(this.titleElement); + + this.subtitleElement = document.createElement("span"); + this.subtitleElement.className = "subtitle"; + this.titlesElement.appendChild(this.subtitleElement); + + this.className = className; + this.mainTitle = title; + this.subtitle = subtitle; +} + +WebInspector.SidebarTreeElement.prototype = { + get small() + { + return this._small; + }, + + set small(x) + { + this._small = x; + + if (this._listItemNode) { + if (this._small) + this._listItemNode.addStyleClass("small"); + else + this._listItemNode.removeStyleClass("small"); + } + }, + + get mainTitle() + { + return this._mainTitle; + }, + + set mainTitle(x) + { + this._mainTitle = x; + this.refreshTitles(); + }, + + get subtitle() + { + return this._subtitle; + }, + + set subtitle(x) + { + this._subtitle = x; + this.refreshTitles(); + }, + + get bubbleText() + { + return this._bubbleText; + }, + + set bubbleText(x) + { + if (!this.bubbleElement) { + this.bubbleElement = document.createElement("div"); + this.bubbleElement.className = "bubble"; + this.statusElement.appendChild(this.bubbleElement); + } + + this._bubbleText = x; + this.bubbleElement.textContent = x; + }, + + refreshTitles: function() + { + var mainTitle = this.mainTitle; + if (this.titleElement.textContent !== mainTitle) + this.titleElement.textContent = mainTitle; + + var subtitle = this.subtitle; + if (subtitle) { + if (this.subtitleElement.textContent !== subtitle) + this.subtitleElement.textContent = subtitle; + this.titlesElement.removeStyleClass("no-subtitle"); + } else + this.titlesElement.addStyleClass("no-subtitle"); + }, + + isEventWithinDisclosureTriangle: function(event) + { + return event.target === this.disclosureButton; + }, + + onattach: function() + { + this._listItemNode.addStyleClass("sidebar-tree-item"); + + if (this.className) + this._listItemNode.addStyleClass(this.className); + + if (this.small) + this._listItemNode.addStyleClass("small"); + + if (this.hasChildren && this.disclosureButton) + this._listItemNode.appendChild(this.disclosureButton); + + this._listItemNode.appendChild(this.iconElement); + this._listItemNode.appendChild(this.statusElement); + this._listItemNode.appendChild(this.titlesElement); + }, + + onreveal: function() + { + if (this._listItemNode) + this._listItemNode.scrollIntoViewIfNeeded(false); + } +} + +WebInspector.SidebarTreeElement.prototype.__proto__ = TreeElement.prototype; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/SourceFrame.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/SourceFrame.js new file mode 100644 index 0000000..e364cb2 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/SourceFrame.js @@ -0,0 +1,906 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.SourceFrame = function(element, addBreakpointDelegate) +{ + this.messages = []; + this.breakpoints = []; + this._shortcuts = {}; + + this.addBreakpointDelegate = addBreakpointDelegate; + + this.element = element || document.createElement("iframe"); + this.element.addStyleClass("source-view-frame"); + this.element.setAttribute("viewsource", "true"); + + this.element.addEventListener("load", this._loaded.bind(this), false); +} + +WebInspector.SourceFrame.prototype = { + get executionLine() + { + return this._executionLine; + }, + + set executionLine(x) + { + if (this._executionLine === x) + return; + + var previousLine = this._executionLine; + this._executionLine = x; + + this._updateExecutionLine(previousLine); + }, + + get autoSizesToFitContentHeight() + { + return this._autoSizesToFitContentHeight; + }, + + set autoSizesToFitContentHeight(x) + { + if (this._autoSizesToFitContentHeight === x) + return; + + this._autoSizesToFitContentHeight = x; + + if (this._autoSizesToFitContentHeight) { + this._windowResizeListener = this._windowResized.bind(this); + window.addEventListener("resize", this._windowResizeListener, false); + this.sizeToFitContentHeight(); + } else { + this.element.style.removeProperty("height"); + if (this.element.contentDocument) + this.element.contentDocument.body.removeStyleClass("webkit-height-sized-to-fit"); + window.removeEventListener("resize", this._windowResizeListener, false); + delete this._windowResizeListener; + } + }, + + sourceRow: function(lineNumber) + { + if (!lineNumber || !this.element.contentDocument) + return; + + var table = this.element.contentDocument.getElementsByTagName("table")[0]; + if (!table) + return; + + var rows = table.rows; + + // Line numbers are a 1-based index, but the rows collection is 0-based. + --lineNumber; + + return rows[lineNumber]; + }, + + lineNumberForSourceRow: function(sourceRow) + { + // Line numbers are a 1-based index, but the rows collection is 0-based. + var lineNumber = 0; + while (sourceRow) { + ++lineNumber; + sourceRow = sourceRow.previousSibling; + } + + return lineNumber; + }, + + revealLine: function(lineNumber) + { + if (!this._isContentLoaded()) { + this._lineNumberToReveal = lineNumber; + return; + } + + var row = this.sourceRow(lineNumber); + if (row) + row.scrollIntoViewIfNeeded(true); + }, + + addBreakpoint: function(breakpoint) + { + this.breakpoints.push(breakpoint); + breakpoint.addEventListener("enabled", this._breakpointEnableChanged, this); + breakpoint.addEventListener("disabled", this._breakpointEnableChanged, this); + this._addBreakpointToSource(breakpoint); + }, + + removeBreakpoint: function(breakpoint) + { + this.breakpoints.remove(breakpoint); + breakpoint.removeEventListener("enabled", null, this); + breakpoint.removeEventListener("disabled", null, this); + this._removeBreakpointFromSource(breakpoint); + }, + + addMessage: function(msg) + { + // Don't add the message if there is no message or valid line or if the msg isn't an error or warning. + if (!msg.message || msg.line <= 0 || !msg.isErrorOrWarning()) + return; + this.messages.push(msg); + this._addMessageToSource(msg); + }, + + clearMessages: function() + { + this.messages = []; + + if (!this.element.contentDocument) + return; + + var bubbles = this.element.contentDocument.querySelectorAll(".webkit-html-message-bubble"); + if (!bubbles) + return; + + for (var i = 0; i < bubbles.length; ++i) { + var bubble = bubbles[i]; + bubble.parentNode.removeChild(bubble); + } + }, + + sizeToFitContentHeight: function() + { + if (this.element.contentDocument) { + this.element.style.setProperty("height", this.element.contentDocument.body.offsetHeight + "px"); + this.element.contentDocument.body.addStyleClass("webkit-height-sized-to-fit"); + } + }, + + _highlightLineEnds: function(event) + { + event.target.parentNode.removeStyleClass("webkit-highlighted-line"); + }, + + highlightLine: function(lineNumber) + { + if (!this._isContentLoaded()) { + this._lineNumberToHighlight = lineNumber; + return; + } + + var sourceRow = this.sourceRow(lineNumber); + if (!sourceRow) + return; + var line = sourceRow.getElementsByClassName('webkit-line-content')[0]; + // Trick to reset the animation if the user clicks on the same link + // Using a timeout to avoid coalesced style updates + line.style.setProperty("-webkit-animation-name", "none"); + setTimeout(function () { + line.style.removeProperty("-webkit-animation-name"); + sourceRow.addStyleClass("webkit-highlighted-line"); + }, 0); + }, + + _loaded: function() + { + WebInspector.addMainEventListeners(this.element.contentDocument); + this.element.contentDocument.addEventListener("contextmenu", this._documentContextMenu.bind(this), true); + this.element.contentDocument.addEventListener("mousedown", this._documentMouseDown.bind(this), true); + this.element.contentDocument.addEventListener("keydown", this._documentKeyDown.bind(this), true); + this.element.contentDocument.addEventListener("keyup", WebInspector.documentKeyUp.bind(WebInspector), true); + this.element.contentDocument.addEventListener("webkitAnimationEnd", this._highlightLineEnds.bind(this), false); + + // Register 'eval' shortcut. + var isMac = InspectorController.platform().indexOf("mac-") === 0; + var platformSpecificModifier = isMac ? WebInspector.KeyboardShortcut.Modifiers.Meta : WebInspector.KeyboardShortcut.Modifiers.Ctrl; + var shortcut = WebInspector.KeyboardShortcut.makeKey(69 /* 'E' */, platformSpecificModifier | WebInspector.KeyboardShortcut.Modifiers.Shift); + this._shortcuts[shortcut] = this._evalSelectionInCallFrame.bind(this); + + var headElement = this.element.contentDocument.getElementsByTagName("head")[0]; + if (!headElement) { + headElement = this.element.contentDocument.createElement("head"); + this.element.contentDocument.documentElement.insertBefore(headElement, this.element.contentDocument.documentElement.firstChild); + } + + var styleElement = this.element.contentDocument.createElement("style"); + headElement.appendChild(styleElement); + + // Add these style rules here since they are specific to the Inspector. They also behave oddly and not + // all properties apply if added to view-source.css (becuase it is a user agent sheet.) + var styleText = ".webkit-line-number { background-repeat: no-repeat; background-position: right 1px; }\n"; + styleText += ".webkit-execution-line .webkit-line-number { color: transparent; background-image: -webkit-canvas(program-counter); }\n"; + + styleText += ".webkit-breakpoint .webkit-line-number { color: white; background-image: -webkit-canvas(breakpoint); }\n"; + styleText += ".webkit-breakpoint-disabled .webkit-line-number { color: white; background-image: -webkit-canvas(breakpoint-disabled); }\n"; + styleText += ".webkit-breakpoint.webkit-execution-line .webkit-line-number { color: transparent; background-image: -webkit-canvas(breakpoint-program-counter); }\n"; + styleText += ".webkit-breakpoint-disabled.webkit-execution-line .webkit-line-number { color: transparent; background-image: -webkit-canvas(breakpoint-disabled-program-counter); }\n"; + + styleText += ".webkit-breakpoint.webkit-breakpoint-conditional .webkit-line-number { color: white; background-image: -webkit-canvas(breakpoint-conditional); }\n"; + styleText += ".webkit-breakpoint-disabled.webkit-breakpoint-conditional .webkit-line-number { color: white; background-image: -webkit-canvas(breakpoint-disabled-conditional); }\n"; + styleText += ".webkit-breakpoint.webkit-breakpoint-conditional.webkit-execution-line .webkit-line-number { color: transparent; background-image: -webkit-canvas(breakpoint-conditional-program-counter); }\n"; + styleText += ".webkit-breakpoint-disabled.webkit-breakpoint-conditional.webkit-execution-line .webkit-line-number { color: transparent; background-image: -webkit-canvas(breakpoint-disabled-conditional-program-counter); }\n"; + + styleText += ".webkit-execution-line .webkit-line-content { background-color: rgb(171, 191, 254); outline: 1px solid rgb(64, 115, 244); }\n"; + styleText += ".webkit-height-sized-to-fit { overflow-y: hidden }\n"; + styleText += ".webkit-line-content { background-color: white; }\n"; + styleText += "@-webkit-keyframes fadeout {from {background-color: rgb(255, 255, 120);} to { background-color: white;}}\n"; + styleText += ".webkit-highlighted-line .webkit-line-content { background-color: rgb(255, 255, 120); -webkit-animation: 'fadeout' 2s 500ms}\n"; + styleText += ".webkit-javascript-comment { color: rgb(0, 116, 0); }\n"; + styleText += ".webkit-javascript-keyword { color: rgb(170, 13, 145); }\n"; + styleText += ".webkit-javascript-number { color: rgb(28, 0, 207); }\n"; + styleText += ".webkit-javascript-string, .webkit-javascript-regexp { color: rgb(196, 26, 22); }\n"; + + // TODO: Move these styles into inspector.css once https://bugs.webkit.org/show_bug.cgi?id=28913 is fixed and popup moved into the top frame. + styleText += ".popup-content { position: absolute; z-index: 10000; padding: 4px; background-color: rgb(203, 226, 255); -webkit-border-radius: 7px; border: 2px solid rgb(169, 172, 203); }"; + styleText += ".popup-glasspane { position: absolute; top: 0; left: 0; height: 100%; width: 100%; opacity: 0; z-index: 9900; }"; + styleText += ".popup-message { background-color: transparent; font-family: Lucida Grande, sans-serif; font-weight: normal; font-size: 11px; text-align: left; text-shadow: none; color: rgb(85, 85, 85); cursor: default; margin: 0 0 2px 0; }"; + styleText += ".popup-content.breakpoint-condition { width: 90%; }"; + styleText += ".popup-content input#bp-condition { font-family: monospace; margin: 0; border: 1px inset rgb(190, 190, 190) !important; width: 100%; box-shadow: none !important; outline: none !important; -webkit-user-modify: read-write; }"; + // This class is already in inspector.css + styleText += ".hidden { display: none !important; }"; + + styleElement.textContent = styleText; + + this._needsProgramCounterImage = true; + this._needsBreakpointImages = true; + + this.element.contentWindow.Element.prototype.addStyleClass = Element.prototype.addStyleClass; + this.element.contentWindow.Element.prototype.removeStyleClass = Element.prototype.removeStyleClass; + this.element.contentWindow.Element.prototype.positionAt = Element.prototype.positionAt; + this.element.contentWindow.Element.prototype.removeMatchingStyleClasses = Element.prototype.removeMatchingStyleClasses; + this.element.contentWindow.Element.prototype.hasStyleClass = Element.prototype.hasStyleClass; + this.element.contentWindow.Element.prototype.pageOffsetRelativeToWindow = Element.prototype.pageOffsetRelativeToWindow; + this.element.contentWindow.Element.prototype.__defineGetter__("totalOffsetLeft", Element.prototype.__lookupGetter__("totalOffsetLeft")); + this.element.contentWindow.Element.prototype.__defineGetter__("totalOffsetTop", Element.prototype.__lookupGetter__("totalOffsetTop")); + this.element.contentWindow.Node.prototype.enclosingNodeOrSelfWithNodeName = Node.prototype.enclosingNodeOrSelfWithNodeName; + this.element.contentWindow.Node.prototype.enclosingNodeOrSelfWithNodeNameInArray = Node.prototype.enclosingNodeOrSelfWithNodeNameInArray; + + this._addExistingMessagesToSource(); + this._addExistingBreakpointsToSource(); + this._updateExecutionLine(); + if (this._executionLine) + this.revealLine(this._executionLine); + + if (this.autoSizesToFitContentHeight) + this.sizeToFitContentHeight(); + + if (this._lineNumberToReveal) { + this.revealLine(this._lineNumberToReveal); + delete this._lineNumberToReveal; + } + + if (this._lineNumberToHighlight) { + this.highlightLine(this._lineNumberToHighlight); + delete this._lineNumberToHighlight; + } + + this.dispatchEventToListeners("content loaded"); + }, + + _isContentLoaded: function() { + var doc = this.element.contentDocument; + return doc && doc.getElementsByTagName("table")[0]; + }, + + _windowResized: function(event) + { + if (!this._autoSizesToFitContentHeight) + return; + this.sizeToFitContentHeight(); + }, + + _documentContextMenu: function(event) + { + if (!event.target.hasStyleClass("webkit-line-number")) + return; + var sourceRow = event.target.enclosingNodeOrSelfWithNodeName("tr"); + if (!sourceRow._breakpointObject && this.addBreakpointDelegate) + this.addBreakpointDelegate(this.lineNumberForSourceRow(sourceRow)); + + var breakpoint = sourceRow._breakpointObject; + if (!breakpoint) + return; + + this._editBreakpointCondition(event.target, sourceRow, breakpoint); + event.preventDefault(); + }, + + _documentMouseDown: function(event) + { + if (!event.target.hasStyleClass("webkit-line-number")) + return; + if (event.button != 0 || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) + return; + var sourceRow = event.target.enclosingNodeOrSelfWithNodeName("tr"); + if (sourceRow._breakpointObject && sourceRow._breakpointObject.enabled) + sourceRow._breakpointObject.enabled = false; + else if (sourceRow._breakpointObject) + WebInspector.panels.scripts.removeBreakpoint(sourceRow._breakpointObject); + else if (this.addBreakpointDelegate) + this.addBreakpointDelegate(this.lineNumberForSourceRow(sourceRow)); + }, + + _editBreakpointCondition: function(eventTarget, sourceRow, breakpoint) + { + // TODO: Migrate the popup to the top-level document and remove the blur listener from conditionElement once https://bugs.webkit.org/show_bug.cgi?id=28913 is fixed. + var popupDocument = this.element.contentDocument; + this._showBreakpointConditionPopup(eventTarget, breakpoint.line, popupDocument); + + function committed(element, newText) + { + breakpoint.condition = newText; + if (breakpoint.condition) + sourceRow.addStyleClass("webkit-breakpoint-conditional"); + else + sourceRow.removeStyleClass("webkit-breakpoint-conditional"); + dismissed.call(this); + } + + function dismissed() + { + this._popup.hide(); + delete this._conditionEditorElement; + } + + var dismissedHandler = dismissed.bind(this); + this._conditionEditorElement.addEventListener("blur", dismissedHandler, false); + + WebInspector.startEditing(this._conditionEditorElement, committed.bind(this), dismissedHandler); + this._conditionEditorElement.value = breakpoint.condition; + this._conditionEditorElement.select(); + }, + + _showBreakpointConditionPopup: function(clickedElement, lineNumber, popupDocument) + { + var popupContentElement = this._createPopupElement(lineNumber, popupDocument); + var lineElement = clickedElement.enclosingNodeOrSelfWithNodeName("td").nextSibling; + if (this._popup) { + this._popup.hide(); + this._popup.element = popupContentElement; + } else { + this._popup = new WebInspector.Popup(popupContentElement); + this._popup.autoHide = true; + } + this._popup.anchor = lineElement; + this._popup.show(); + }, + + _createPopupElement: function(lineNumber, popupDocument) + { + var popupContentElement = popupDocument.createElement("div"); + popupContentElement.className = "popup-content breakpoint-condition"; + + var labelElement = document.createElement("label"); + labelElement.className = "popup-message"; + labelElement.htmlFor = "bp-condition"; + labelElement.appendChild(document.createTextNode(WebInspector.UIString("The breakpoint on line %d will stop only if this expression is true:", lineNumber))); + popupContentElement.appendChild(labelElement); + + var editorElement = document.createElement("input"); + editorElement.id = "bp-condition"; + editorElement.type = "text" + popupContentElement.appendChild(editorElement); + this._conditionEditorElement = editorElement; + + return popupContentElement; + }, + + _documentKeyDown: function(event) + { + var shortcut = WebInspector.KeyboardShortcut.makeKeyFromEvent(event); + var handler = this._shortcuts[shortcut]; + if (handler) { + handler(event); + event.preventDefault(); + } else { + WebInspector.documentKeyDown(event); + } + }, + + _evalSelectionInCallFrame: function(event) + { + if (!WebInspector.panels.scripts || !WebInspector.panels.scripts.paused) + return; + + var selection = this.element.contentWindow.getSelection(); + if (!selection.rangeCount) + return; + + var expression = selection.getRangeAt(0).toString().trimWhitespace(); + WebInspector.panels.scripts.evaluateInSelectedCallFrame(expression, false, function(result, exception) { + WebInspector.showConsole(); + var commandMessage = new WebInspector.ConsoleCommand(expression); + WebInspector.console.addMessage(commandMessage); + WebInspector.console.addMessage(new WebInspector.ConsoleCommandResult(result, exception, commandMessage)); + }); + }, + + _breakpointEnableChanged: function(event) + { + var breakpoint = event.target; + var sourceRow = this.sourceRow(breakpoint.line); + if (!sourceRow) + return; + + sourceRow.addStyleClass("webkit-breakpoint"); + + if (breakpoint.enabled) + sourceRow.removeStyleClass("webkit-breakpoint-disabled"); + else + sourceRow.addStyleClass("webkit-breakpoint-disabled"); + }, + + _updateExecutionLine: function(previousLine) + { + if (previousLine) { + var sourceRow = this.sourceRow(previousLine); + if (sourceRow) + sourceRow.removeStyleClass("webkit-execution-line"); + } + + if (!this._executionLine) + return; + + this._drawProgramCounterImageIfNeeded(); + + var sourceRow = this.sourceRow(this._executionLine); + if (sourceRow) + sourceRow.addStyleClass("webkit-execution-line"); + }, + + _addExistingBreakpointsToSource: function() + { + var length = this.breakpoints.length; + for (var i = 0; i < length; ++i) + this._addBreakpointToSource(this.breakpoints[i]); + }, + + _addBreakpointToSource: function(breakpoint) + { + var sourceRow = this.sourceRow(breakpoint.line); + if (!sourceRow) + return; + + breakpoint.sourceText = sourceRow.getElementsByClassName('webkit-line-content')[0].textContent; + + this._drawBreakpointImagesIfNeeded(); + + sourceRow._breakpointObject = breakpoint; + + sourceRow.addStyleClass("webkit-breakpoint"); + if (!breakpoint.enabled) + sourceRow.addStyleClass("webkit-breakpoint-disabled"); + if (breakpoint.condition) + sourceRow.addStyleClass("webkit-breakpoint-conditional"); + }, + + _removeBreakpointFromSource: function(breakpoint) + { + var sourceRow = this.sourceRow(breakpoint.line); + if (!sourceRow) + return; + + delete sourceRow._breakpointObject; + + sourceRow.removeStyleClass("webkit-breakpoint"); + sourceRow.removeStyleClass("webkit-breakpoint-disabled"); + sourceRow.removeStyleClass("webkit-breakpoint-conditional"); + }, + + _incrementMessageRepeatCount: function(msg, repeatDelta) + { + if (!msg._resourceMessageLineElement) + return; + + if (!msg._resourceMessageRepeatCountElement) { + var repeatedElement = document.createElement("span"); + msg._resourceMessageLineElement.appendChild(repeatedElement); + msg._resourceMessageRepeatCountElement = repeatedElement; + } + + msg.repeatCount += repeatDelta; + msg._resourceMessageRepeatCountElement.textContent = WebInspector.UIString(" (repeated %d times)", msg.repeatCount); + }, + + _addExistingMessagesToSource: function() + { + var length = this.messages.length; + for (var i = 0; i < length; ++i) + this._addMessageToSource(this.messages[i]); + }, + + _addMessageToSource: function(msg) + { + var row = this.sourceRow(msg.line); + if (!row) + return; + + var cell = row.cells[1]; + if (!cell) + return; + + var messageBubbleElement = cell.lastChild; + if (!messageBubbleElement || messageBubbleElement.nodeType !== Node.ELEMENT_NODE || !messageBubbleElement.hasStyleClass("webkit-html-message-bubble")) { + messageBubbleElement = this.element.contentDocument.createElement("div"); + messageBubbleElement.className = "webkit-html-message-bubble"; + cell.appendChild(messageBubbleElement); + } + + if (!row.messages) + row.messages = []; + + for (var i = 0; i < row.messages.length; ++i) { + if (row.messages[i].isEqual(msg, true)) { + this._incrementMessageRepeatCount(row.messages[i], msg.repeatDelta); + return; + } + } + + row.messages.push(msg); + + var imageURL; + switch (msg.level) { + case WebInspector.ConsoleMessage.MessageLevel.Error: + messageBubbleElement.addStyleClass("webkit-html-error-message"); + imageURL = "Images/errorIcon.png"; + break; + case WebInspector.ConsoleMessage.MessageLevel.Warning: + messageBubbleElement.addStyleClass("webkit-html-warning-message"); + imageURL = "Images/warningIcon.png"; + break; + } + + var messageLineElement = this.element.contentDocument.createElement("div"); + messageLineElement.className = "webkit-html-message-line"; + messageBubbleElement.appendChild(messageLineElement); + + // Create the image element in the Inspector's document so we can use relative image URLs. + var image = document.createElement("img"); + image.src = imageURL; + image.className = "webkit-html-message-icon"; + + // Adopt the image element since it wasn't created in element's contentDocument. + image = this.element.contentDocument.adoptNode(image); + messageLineElement.appendChild(image); + messageLineElement.appendChild(this.element.contentDocument.createTextNode(msg.message)); + + msg._resourceMessageLineElement = messageLineElement; + }, + + _drawProgramCounterInContext: function(ctx, glow) + { + if (glow) + ctx.save(); + + ctx.beginPath(); + ctx.moveTo(17, 2); + ctx.lineTo(19, 2); + ctx.lineTo(19, 0); + ctx.lineTo(21, 0); + ctx.lineTo(26, 5.5); + ctx.lineTo(21, 11); + ctx.lineTo(19, 11); + ctx.lineTo(19, 9); + ctx.lineTo(17, 9); + ctx.closePath(); + ctx.fillStyle = "rgb(142, 5, 4)"; + + if (glow) { + ctx.shadowBlur = 4; + ctx.shadowColor = "rgb(255, 255, 255)"; + ctx.shadowOffsetX = -1; + ctx.shadowOffsetY = 0; + } + + ctx.fill(); + ctx.fill(); // Fill twice to get a good shadow and darker anti-aliased pixels. + + if (glow) + ctx.restore(); + }, + + _drawProgramCounterImageIfNeeded: function() + { + if (!this._needsProgramCounterImage || !this.element.contentDocument) + return; + + var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "program-counter", 26, 11); + ctx.clearRect(0, 0, 26, 11); + this._drawProgramCounterInContext(ctx, true); + + delete this._needsProgramCounterImage; + }, + + _drawBreakpointImagesIfNeeded: function(conditional) + { + if (!this._needsBreakpointImages || !this.element.contentDocument) + return; + + function drawBreakpoint(ctx, disabled, conditional) + { + ctx.beginPath(); + ctx.moveTo(0, 2); + ctx.lineTo(2, 0); + ctx.lineTo(21, 0); + ctx.lineTo(26, 5.5); + ctx.lineTo(21, 11); + ctx.lineTo(2, 11); + ctx.lineTo(0, 9); + ctx.closePath(); + ctx.fillStyle = conditional ? "rgb(217, 142, 1)" : "rgb(1, 142, 217)"; + ctx.strokeStyle = conditional ? "rgb(205, 103, 0)" : "rgb(0, 103, 205)"; + ctx.lineWidth = 3; + ctx.fill(); + ctx.save(); + ctx.clip(); + ctx.stroke(); + ctx.restore(); + + if (!disabled) + return; + + ctx.save(); + ctx.globalCompositeOperation = "destination-out"; + ctx.fillStyle = "rgba(0, 0, 0, 0.5)"; + ctx.fillRect(0, 0, 26, 11); + ctx.restore(); + } + + + // Unconditional breakpoints. + + var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "breakpoint", 26, 11); + ctx.clearRect(0, 0, 26, 11); + drawBreakpoint(ctx); + + var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "breakpoint-program-counter", 26, 11); + ctx.clearRect(0, 0, 26, 11); + drawBreakpoint(ctx); + ctx.clearRect(20, 0, 6, 11); + this._drawProgramCounterInContext(ctx, true); + + var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "breakpoint-disabled", 26, 11); + ctx.clearRect(0, 0, 26, 11); + drawBreakpoint(ctx, true); + + var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "breakpoint-disabled-program-counter", 26, 11); + ctx.clearRect(0, 0, 26, 11); + drawBreakpoint(ctx, true); + ctx.clearRect(20, 0, 6, 11); + this._drawProgramCounterInContext(ctx, true); + + + // Conditional breakpoints. + + var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "breakpoint-conditional", 26, 11); + ctx.clearRect(0, 0, 26, 11); + drawBreakpoint(ctx, false, true); + + var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "breakpoint-conditional-program-counter", 26, 11); + ctx.clearRect(0, 0, 26, 11); + drawBreakpoint(ctx, false, true); + ctx.clearRect(20, 0, 6, 11); + this._drawProgramCounterInContext(ctx, true); + + var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "breakpoint-disabled-conditional", 26, 11); + ctx.clearRect(0, 0, 26, 11); + drawBreakpoint(ctx, true, true); + + var ctx = this.element.contentDocument.getCSSCanvasContext("2d", "breakpoint-disabled-conditional-program-counter", 26, 11); + ctx.clearRect(0, 0, 26, 11); + drawBreakpoint(ctx, true, true); + ctx.clearRect(20, 0, 6, 11); + this._drawProgramCounterInContext(ctx, true); + + delete this._needsBreakpointImages; + }, + + syntaxHighlightJavascript: function() + { + var table = this.element.contentDocument.getElementsByTagName("table")[0]; + if (!table) + return; + + function deleteContinueFlags(cell) + { + if (!cell) + return; + delete cell._commentContinues; + delete cell._singleQuoteStringContinues; + delete cell._doubleQuoteStringContinues; + delete cell._regexpContinues; + } + + function createSpan(content, className) + { + var span = document.createElement("span"); + span.className = className; + span.appendChild(document.createTextNode(content)); + return span; + } + + function generateFinder(regex, matchNumber, className) + { + return function(str) { + var match = regex.exec(str); + if (!match) + return null; + previousMatchLength = match[matchNumber].length; + return createSpan(match[matchNumber], className); + }; + } + + var findNumber = generateFinder(/^(-?(\d+\.?\d*([eE][+-]\d+)?|0[xX]\h+|Infinity)|NaN)(?:\W|$)/, 1, "webkit-javascript-number"); + var findKeyword = generateFinder(/^(null|true|false|break|case|catch|const|default|finally|for|instanceof|new|var|continue|function|return|void|delete|if|this|do|while|else|in|switch|throw|try|typeof|with|debugger|class|enum|export|extends|import|super|get|set)(?:\W|$)/, 1, "webkit-javascript-keyword"); + var findSingleLineString = generateFinder(/^"(?:[^"\\]|\\.)*"|^'([^'\\]|\\.)*'/, 0, "webkit-javascript-string"); // " this quote keeps Xcode happy + var findMultilineCommentStart = generateFinder(/^\/\*.*$/, 0, "webkit-javascript-comment"); + var findMultilineCommentEnd = generateFinder(/^.*?\*\//, 0, "webkit-javascript-comment"); + var findMultilineSingleQuoteStringStart = generateFinder(/^'(?:[^'\\]|\\.)*\\$/, 0, "webkit-javascript-string"); + var findMultilineSingleQuoteStringEnd = generateFinder(/^(?:[^'\\]|\\.)*?'/, 0, "webkit-javascript-string"); + var findMultilineDoubleQuoteStringStart = generateFinder(/^"(?:[^"\\]|\\.)*\\$/, 0, "webkit-javascript-string"); + var findMultilineDoubleQuoteStringEnd = generateFinder(/^(?:[^"\\]|\\.)*?"/, 0, "webkit-javascript-string"); + var findMultilineRegExpEnd = generateFinder(/^(?:[^\/\\]|\\.)*?\/([gim]{0,3})/, 0, "webkit-javascript-regexp"); + var findSingleLineComment = generateFinder(/^\/\/.*|^\/\*.*?\*\//, 0, "webkit-javascript-comment"); + + function findMultilineRegExpStart(str) + { + var match = /^\/(?:[^\/\\]|\\.)*\\$/.exec(str); + if (!match || !/\\|\$|\.[\?\*\+]|[^\|]\|[^\|]/.test(match[0])) + return null; + var node = createSpan(match[0], "webkit-javascript-regexp"); + previousMatchLength = match[0].length; + return node; + } + + function findSingleLineRegExp(str) + { + var match = /^(\/(?:[^\/\\]|\\.)*\/([gim]{0,3}))(.?)/.exec(str); + if (!match || !(match[2].length > 0 || /\\|\$|\.[\?\*\+]|[^\|]\|[^\|]/.test(match[1]) || /\.|;|,/.test(match[3]))) + return null; + var node = createSpan(match[1], "webkit-javascript-regexp"); + previousMatchLength = match[1].length; + return node; + } + + function syntaxHighlightJavascriptLine(line, prevLine) + { + var messageBubble = line.lastChild; + if (messageBubble && messageBubble.nodeType === Node.ELEMENT_NODE && messageBubble.hasStyleClass("webkit-html-message-bubble")) + line.removeChild(messageBubble); + else + messageBubble = null; + + var code = line.textContent; + + while (line.firstChild) + line.removeChild(line.firstChild); + + var token; + var tmp = 0; + var i = 0; + previousMatchLength = 0; + + if (prevLine) { + if (prevLine._commentContinues) { + if (!(token = findMultilineCommentEnd(code))) { + token = createSpan(code, "webkit-javascript-comment"); + line._commentContinues = true; + } + } else if (prevLine._singleQuoteStringContinues) { + if (!(token = findMultilineSingleQuoteStringEnd(code))) { + token = createSpan(code, "webkit-javascript-string"); + line._singleQuoteStringContinues = true; + } + } else if (prevLine._doubleQuoteStringContinues) { + if (!(token = findMultilineDoubleQuoteStringEnd(code))) { + token = createSpan(code, "webkit-javascript-string"); + line._doubleQuoteStringContinues = true; + } + } else if (prevLine._regexpContinues) { + if (!(token = findMultilineRegExpEnd(code))) { + token = createSpan(code, "webkit-javascript-regexp"); + line._regexpContinues = true; + } + } + if (token) { + i += previousMatchLength ? previousMatchLength : code.length; + tmp = i; + line.appendChild(token); + } + } + + for ( ; i < code.length; ++i) { + var codeFragment = code.substr(i); + var prevChar = code[i - 1]; + token = findSingleLineComment(codeFragment); + if (!token) { + if ((token = findMultilineCommentStart(codeFragment))) + line._commentContinues = true; + else if (!prevChar || /^\W/.test(prevChar)) { + token = findNumber(codeFragment, code[i - 1]) || + findKeyword(codeFragment, code[i - 1]) || + findSingleLineString(codeFragment) || + findSingleLineRegExp(codeFragment); + if (!token) { + if (token = findMultilineSingleQuoteStringStart(codeFragment)) + line._singleQuoteStringContinues = true; + else if (token = findMultilineDoubleQuoteStringStart(codeFragment)) + line._doubleQuoteStringContinues = true; + else if (token = findMultilineRegExpStart(codeFragment)) + line._regexpContinues = true; + } + } + } + + if (token) { + if (tmp !== i) + line.appendChild(document.createTextNode(code.substring(tmp, i))); + line.appendChild(token); + i += previousMatchLength - 1; + tmp = i + 1; + } + } + + if (tmp < code.length) + line.appendChild(document.createTextNode(code.substring(tmp, i))); + + if (messageBubble) + line.appendChild(messageBubble); + } + + var i = 0; + var rows = table.rows; + var rowsLength = rows.length; + var previousCell = null; + var previousMatchLength = 0; + var sourceFrame = this; + + // Split up the work into chunks so we don't block the + // UI thread while processing. + + function processChunk() + { + for (var end = Math.min(i + 10, rowsLength); i < end; ++i) { + var row = rows[i]; + if (!row) + continue; + var cell = row.cells[1]; + if (!cell) + continue; + syntaxHighlightJavascriptLine(cell, previousCell); + if (i < (end - 1)) + deleteContinueFlags(previousCell); + previousCell = cell; + } + + if (i >= rowsLength && processChunkInterval) { + deleteContinueFlags(previousCell); + clearInterval(processChunkInterval); + + sourceFrame.dispatchEventToListeners("syntax highlighting complete"); + } + } + + processChunk(); + + var processChunkInterval = setInterval(processChunk, 25); + } +} + +WebInspector.SourceFrame.prototype.__proto__ = WebInspector.Object.prototype; + diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/SourceView.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/SourceView.js new file mode 100644 index 0000000..97a5bd5 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/SourceView.js @@ -0,0 +1,311 @@ +/* + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.SourceView = function(resource) +{ + // Set the sourceFrame first since WebInspector.ResourceView will set headersVisible + // and our override of headersVisible needs the sourceFrame. + this.sourceFrame = new WebInspector.SourceFrame(null, this._addBreakpoint.bind(this)); + + WebInspector.ResourceView.call(this, resource); + + resource.addEventListener("finished", this._resourceLoadingFinished, this); + + this.element.addStyleClass("source"); + + this._frameNeedsSetup = true; + + this.contentElement.appendChild(this.sourceFrame.element); + + var gutterElement = document.createElement("div"); + gutterElement.className = "webkit-line-gutter-backdrop"; + this.element.appendChild(gutterElement); +} + +WebInspector.SourceView.prototype = { + set headersVisible(x) + { + if (x === this._headersVisible) + return; + + var superSetter = WebInspector.ResourceView.prototype.__lookupSetter__("headersVisible"); + if (superSetter) + superSetter.call(this, x); + + this.sourceFrame.autoSizesToFitContentHeight = x; + }, + + show: function(parentElement) + { + WebInspector.ResourceView.prototype.show.call(this, parentElement); + this.setupSourceFrameIfNeeded(); + }, + + hide: function() + { + WebInspector.View.prototype.hide.call(this); + this._currentSearchResultIndex = -1; + }, + + resize: function() + { + if (this.sourceFrame.autoSizesToFitContentHeight) + this.sourceFrame.sizeToFitContentHeight(); + }, + + detach: function() + { + WebInspector.ResourceView.prototype.detach.call(this); + + // FIXME: We need to mark the frame for setup on detach because the frame DOM is cleared + // when it is removed from the document. Is this a bug? + this._frameNeedsSetup = true; + this._sourceFrameSetup = false; + }, + + setupSourceFrameIfNeeded: function() + { + if (!this._frameNeedsSetup) + return; + + this.attach(); + + delete this._frameNeedsSetup; + this.sourceFrame.addEventListener("content loaded", this._contentLoaded, this); + InspectorController.addResourceSourceToFrame(this.resource.identifier, this.sourceFrame.element); + }, + + _contentLoaded: function() + { + delete this._frameNeedsSetup; + this.sourceFrame.removeEventListener("content loaded", this._contentLoaded, this); + + if (this.resource.type === WebInspector.Resource.Type.Script + || this.resource.mimeType === 'application/json' + || this.resource.mimeType === 'application/javascript' + || /\.js(on)?$/.test(this.resource.lastPathComponent) ) { + this.sourceFrame.addEventListener("syntax highlighting complete", this._syntaxHighlightingComplete, this); + this.sourceFrame.syntaxHighlightJavascript(); + } else + this._sourceFrameSetupFinished(); + }, + + _resourceLoadingFinished: function(event) + { + this._frameNeedsSetup = true; + this._sourceFrameSetup = false; + if (this.visible) + this.setupSourceFrameIfNeeded(); + this.resource.removeEventListener("finished", this._resourceLoadingFinished, this); + }, + + _addBreakpoint: function(line) + { + var sourceID = null; + var closestStartingLine = 0; + var scripts = this.resource.scripts; + for (var i = 0; i < scripts.length; ++i) { + var script = scripts[i]; + if (script.startingLine <= line && script.startingLine >= closestStartingLine) { + closestStartingLine = script.startingLine; + sourceID = script.sourceID; + } + } + + if (WebInspector.panels.scripts) { + var breakpoint = new WebInspector.Breakpoint(this.resource.url, line, sourceID); + WebInspector.panels.scripts.addBreakpoint(breakpoint); + } + }, + + // The rest of the methods in this prototype need to be generic enough to work with a ScriptView. + // The ScriptView prototype pulls these methods into it's prototype to avoid duplicate code. + + searchCanceled: function() + { + this._currentSearchResultIndex = -1; + this._searchResults = []; + delete this._delayedFindSearchMatches; + }, + + performSearch: function(query, finishedCallback) + { + // Call searchCanceled since it will reset everything we need before doing a new search. + this.searchCanceled(); + + var lineQueryRegex = /(^|\s)(?:#|line:\s*)(\d+)(\s|$)/i; + var lineQueryMatch = query.match(lineQueryRegex); + if (lineQueryMatch) { + var lineToSearch = parseInt(lineQueryMatch[2]); + + // If there was a space before and after the line query part, replace with a space. + // Otherwise replace with an empty string to eat the prefix or postfix space. + var lineQueryReplacement = (lineQueryMatch[1] && lineQueryMatch[3] ? " " : ""); + var filterlessQuery = query.replace(lineQueryRegex, lineQueryReplacement); + } + + this._searchFinishedCallback = finishedCallback; + + function findSearchMatches(query, finishedCallback) + { + if (isNaN(lineToSearch)) { + // Search the whole document since there was no line to search. + this._searchResults = (InspectorController.search(this.sourceFrame.element.contentDocument, query) || []); + } else { + var sourceRow = this.sourceFrame.sourceRow(lineToSearch); + if (sourceRow) { + if (filterlessQuery) { + // There is still a query string, so search for that string in the line. + this._searchResults = (InspectorController.search(sourceRow, filterlessQuery) || []); + } else { + // Match the whole line, since there was no remaining query string to match. + var rowRange = this.sourceFrame.element.contentDocument.createRange(); + rowRange.selectNodeContents(sourceRow); + this._searchResults = [rowRange]; + } + } + + // Attempt to search for the whole query, just incase it matches a color like "#333". + var wholeQueryMatches = InspectorController.search(this.sourceFrame.element.contentDocument, query); + if (wholeQueryMatches) + this._searchResults = this._searchResults.concat(wholeQueryMatches); + } + + if (this._searchResults) + finishedCallback(this, this._searchResults.length); + } + + if (!this._sourceFrameSetup) { + // The search is performed in _sourceFrameSetupFinished by calling _delayedFindSearchMatches. + this._delayedFindSearchMatches = findSearchMatches.bind(this, query, finishedCallback); + this.setupSourceFrameIfNeeded(); + return; + } + + findSearchMatches.call(this, query, finishedCallback); + }, + + jumpToFirstSearchResult: function() + { + if (!this._searchResults || !this._searchResults.length) + return; + this._currentSearchResultIndex = 0; + this._jumpToSearchResult(this._currentSearchResultIndex); + }, + + jumpToLastSearchResult: function() + { + if (!this._searchResults || !this._searchResults.length) + return; + this._currentSearchResultIndex = (this._searchResults.length - 1); + this._jumpToSearchResult(this._currentSearchResultIndex); + }, + + jumpToNextSearchResult: function() + { + if (!this._searchResults || !this._searchResults.length) + return; + if (++this._currentSearchResultIndex >= this._searchResults.length) + this._currentSearchResultIndex = 0; + this._jumpToSearchResult(this._currentSearchResultIndex); + }, + + jumpToPreviousSearchResult: function() + { + if (!this._searchResults || !this._searchResults.length) + return; + if (--this._currentSearchResultIndex < 0) + this._currentSearchResultIndex = (this._searchResults.length - 1); + this._jumpToSearchResult(this._currentSearchResultIndex); + }, + + showingFirstSearchResult: function() + { + return (this._currentSearchResultIndex === 0); + }, + + showingLastSearchResult: function() + { + return (this._searchResults && this._currentSearchResultIndex === (this._searchResults.length - 1)); + }, + + revealLine: function(lineNumber) + { + this.setupSourceFrameIfNeeded(); + this.sourceFrame.revealLine(lineNumber); + }, + + highlightLine: function(lineNumber) + { + this.setupSourceFrameIfNeeded(); + this.sourceFrame.highlightLine(lineNumber); + }, + + addMessage: function(msg) + { + this.sourceFrame.addMessage(msg); + }, + + clearMessages: function() + { + this.sourceFrame.clearMessages(); + }, + + _jumpToSearchResult: function(index) + { + var foundRange = this._searchResults[index]; + if (!foundRange) + return; + + var selection = this.sourceFrame.element.contentWindow.getSelection(); + selection.removeAllRanges(); + selection.addRange(foundRange); + + if (foundRange.startContainer.scrollIntoViewIfNeeded) + foundRange.startContainer.scrollIntoViewIfNeeded(true); + else if (foundRange.startContainer.parentNode) + foundRange.startContainer.parentNode.scrollIntoViewIfNeeded(true); + }, + + _sourceFrameSetupFinished: function() + { + this._sourceFrameSetup = true; + if (this._delayedFindSearchMatches) { + this._delayedFindSearchMatches(); + delete this._delayedFindSearchMatches; + } + }, + + _syntaxHighlightingComplete: function(event) + { + this._sourceFrameSetupFinished(); + this.sourceFrame.removeEventListener("syntax highlighting complete", null, this); + } +} + +WebInspector.SourceView.prototype.__proto__ = WebInspector.ResourceView.prototype; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/StatusBarButton.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/StatusBarButton.js new file mode 100644 index 0000000..5c69ed5 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/StatusBarButton.js @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2009 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.StatusBarButton = function(title, className) +{ + this.element = document.createElement("button"); + this.element.className = className + " status-bar-item"; + this.element.addEventListener("click", this._clicked.bind(this), false); + + this.glyph = document.createElement("div"); + this.glyph.className = "glyph"; + this.element.appendChild(this.glyph); + + this.glyphShadow = document.createElement("div"); + this.glyphShadow.className = "glyph shadow"; + this.element.appendChild(this.glyphShadow); + + this.title = title; + this.disabled = false; + this._toggled = false; + this._visible = true; +} + +WebInspector.StatusBarButton.prototype = { + _clicked: function() + { + this.dispatchEventToListeners("click"); + }, + + get disabled() + { + return this._disabled; + }, + + set disabled(x) + { + if (this._disabled === x) + return; + this._disabled = x; + this.element.disabled = x; + }, + + get title() + { + return this._title; + }, + + set title(x) + { + if (this._title === x) + return; + this._title = x; + this.element.title = x; + }, + + get toggled() + { + return this._toggled; + }, + + set toggled(x) + { + if (this._toggled === x) + return; + + if (x) + this.element.addStyleClass("toggled-on"); + else + this.element.removeStyleClass("toggled-on"); + this._toggled = x; + }, + + get visible() + { + return this._visible; + }, + + set visible(x) + { + if (this._visible === x) + return; + + if (x) + this.element.removeStyleClass("hidden"); + else + this.element.addStyleClass("hidden"); + this._visible = x; + } +} + +WebInspector.StatusBarButton.prototype.__proto__ = WebInspector.Object.prototype; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/StoragePanel.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/StoragePanel.js new file mode 100644 index 0000000..aed0d06 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/StoragePanel.js @@ -0,0 +1,685 @@ +/* + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2009 Joseph Pecoraro + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.StoragePanel = function(database) +{ + WebInspector.Panel.call(this); + + this.sidebarElement = document.createElement("div"); + this.sidebarElement.id = "storage-sidebar"; + this.sidebarElement.className = "sidebar"; + this.element.appendChild(this.sidebarElement); + + this.sidebarResizeElement = document.createElement("div"); + this.sidebarResizeElement.className = "sidebar-resizer-vertical"; + this.sidebarResizeElement.addEventListener("mousedown", this._startSidebarDragging.bind(this), false); + this.element.appendChild(this.sidebarResizeElement); + + this.sidebarTreeElement = document.createElement("ol"); + this.sidebarTreeElement.className = "sidebar-tree"; + this.sidebarElement.appendChild(this.sidebarTreeElement); + + this.sidebarTree = new TreeOutline(this.sidebarTreeElement); + + this.databasesListTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("DATABASES"), {}, true); + this.sidebarTree.appendChild(this.databasesListTreeElement); + this.databasesListTreeElement.expand(); + + this.localStorageListTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("LOCAL STORAGE"), {}, true); + this.sidebarTree.appendChild(this.localStorageListTreeElement); + this.localStorageListTreeElement.expand(); + + this.sessionStorageListTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("SESSION STORAGE"), {}, true); + this.sidebarTree.appendChild(this.sessionStorageListTreeElement); + this.sessionStorageListTreeElement.expand(); + + this.cookieListTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("COOKIES"), {}, true); + this.sidebarTree.appendChild(this.cookieListTreeElement); + this.cookieListTreeElement.expand(); + + this.cookieTreeElement = new WebInspector.CookieSidebarTreeElement(); + this.cookieListTreeElement.appendChild(this.cookieTreeElement); + + this.storageViews = document.createElement("div"); + this.storageViews.id = "storage-views"; + this.element.appendChild(this.storageViews); + + this.storageViewStatusBarItemsContainer = document.createElement("div"); + this.storageViewStatusBarItemsContainer.id = "storage-view-status-bar-items"; + + this.reset(); +} + +WebInspector.StoragePanel.prototype = { + toolbarItemClass: "storage", + + get toolbarItemLabel() + { + return WebInspector.UIString("Storage"); + }, + + get statusBarItems() + { + return [this.storageViewStatusBarItemsContainer]; + }, + + show: function() + { + WebInspector.Panel.prototype.show.call(this); + this._updateSidebarWidth(); + this._registerStorageEventListener(); + }, + + reset: function() + { + if (this._databases) { + var databasesLength = this._databases.length; + for (var i = 0; i < databasesLength; ++i) { + var database = this._databases[i]; + + delete database._tableViews; + delete database._queryView; + } + } + + this._databases = []; + + this._unregisterStorageEventListener(); + + if (this._domStorage) { + var domStorageLength = this._domStorage.length; + for (var i = 0; i < domStorageLength; ++i) { + var domStorage = this._domStorage[i]; + + delete domStorage._domStorageView; + } + } + + this._domStorage = []; + + delete this._cookieView; + + this.databasesListTreeElement.removeChildren(); + this.localStorageListTreeElement.removeChildren(); + this.sessionStorageListTreeElement.removeChildren(); + this.storageViews.removeChildren(); + + this.storageViewStatusBarItemsContainer.removeChildren(); + + if (this.sidebarTree.selectedTreeElement) + this.sidebarTree.selectedTreeElement.deselect(); + }, + + handleKeyEvent: function(event) + { + this.sidebarTree.handleKeyEvent(event); + }, + + addDatabase: function(database) + { + this._databases.push(database); + + var databaseTreeElement = new WebInspector.DatabaseSidebarTreeElement(database); + database._databasesTreeElement = databaseTreeElement; + this.databasesListTreeElement.appendChild(databaseTreeElement); + }, + + addDOMStorage: function(domStorage) + { + this._domStorage.push(domStorage); + var domStorageTreeElement = new WebInspector.DOMStorageSidebarTreeElement(domStorage, (domStorage.isLocalStorage ? "local-storage" : "session-storage")); + domStorage._domStorageTreeElement = domStorageTreeElement; + if (domStorage.isLocalStorage) + this.localStorageListTreeElement.appendChild(domStorageTreeElement); + else + this.sessionStorageListTreeElement.appendChild(domStorageTreeElement); + }, + + selectDatabase: function(db) + { + var database; + for (var i = 0, len = this._databases.length; i < len; ++i) { + database = this._databases[i]; + if (database.isDatabase(db)) { + this.showDatabase(database); + database._databasesTreeElement.select(); + return; + } + } + }, + + selectDOMStorage: function(s) + { + var isLocalStorage = (s === InspectorController.inspectedWindow().localStorage); + for (var i = 0, len = this._domStorage.length; i < len; ++i) { + var storage = this._domStorage[i]; + if ( isLocalStorage === storage.isLocalStorage ) { + this.showDOMStorage(storage); + storage._domStorageTreeElement.select(); + return; + } + } + }, + + showDatabase: function(database, tableName) + { + if (!database) + return; + + if (this.visibleView) + this.visibleView.hide(); + + var view; + if (tableName) { + if (!("_tableViews" in database)) + database._tableViews = {}; + view = database._tableViews[tableName]; + if (!view) { + view = new WebInspector.DatabaseTableView(database, tableName); + database._tableViews[tableName] = view; + } + } else { + view = database._queryView; + if (!view) { + view = new WebInspector.DatabaseQueryView(database); + database._queryView = view; + } + } + + view.show(this.storageViews); + + this.visibleView = view; + + this.storageViewStatusBarItemsContainer.removeChildren(); + var statusBarItems = view.statusBarItems || []; + for (var i = 0; i < statusBarItems.length; ++i) + this.storageViewStatusBarItemsContainer.appendChild(statusBarItems[i].element); + }, + + showDOMStorage: function(domStorage) + { + if (!domStorage) + return; + + if (this.visibleView) + this.visibleView.hide(); + + var view; + view = domStorage._domStorageView; + if (!view) { + view = new WebInspector.DOMStorageItemsView(domStorage); + domStorage._domStorageView = view; + } + + view.show(this.storageViews); + + this.visibleView = view; + + this.storageViewStatusBarItemsContainer.removeChildren(); + var statusBarItems = view.statusBarItems; + for (var i = 0; i < statusBarItems.length; ++i) + this.storageViewStatusBarItemsContainer.appendChild(statusBarItems[i]); + }, + + showCookies: function() + { + if (this.visibleView) + this.visibleView.hide(); + + var view = this._cookieView; + if (!view) { + view = new WebInspector.CookieItemsView(); + this._cookieView = view; + } + + view.show(this.storageViews); + + this.visibleView = view; + + this.storageViewStatusBarItemsContainer.removeChildren(); + var statusBarItems = view.statusBarItems; + for (var i = 0; i < statusBarItems.length; ++i) + this.storageViewStatusBarItemsContainer.appendChild(statusBarItems[i]); + }, + + closeVisibleView: function() + { + if (this.visibleView) + this.visibleView.hide(); + delete this.visibleView; + }, + + updateDatabaseTables: function(database) + { + if (!database || !database._databasesTreeElement) + return; + + database._databasesTreeElement.shouldRefreshChildren = true; + + if (!("_tableViews" in database)) + return; + + var tableNamesHash = {}; + var self = this; + function tableNamesCallback(tableNames) + { + var tableNamesLength = tableNames.length; + for (var i = 0; i < tableNamesLength; ++i) + tableNamesHash[tableNames[i]] = true; + + for (var tableName in database._tableViews) { + if (!(tableName in tableNamesHash)) { + if (self.visibleView === database._tableViews[tableName]) + self.closeVisibleView(); + delete database._tableViews[tableName]; + } + } + } + database.getTableNames(tableNamesCallback); + }, + + dataGridForResult: function(result) + { + if (!result.rows.length) + return null; + + var columns = {}; + + var rows = result.rows; + for (var columnIdentifier in rows.item(0)) { + var column = {}; + column.width = columnIdentifier.length; + column.title = columnIdentifier; + + columns[columnIdentifier] = column; + } + + var nodes = []; + var length = rows.length; + for (var i = 0; i < length; ++i) { + var data = {}; + + var row = rows.item(i); + for (var columnIdentifier in row) { + // FIXME: (Bug 19439) We should specially format SQL NULL here + // (which is represented by JavaScript null here, and turned + // into the string "null" by the String() function). + var text = String(row[columnIdentifier]); + data[columnIdentifier] = text; + if (text.length > columns[columnIdentifier].width) + columns[columnIdentifier].width = text.length; + } + + var node = new WebInspector.DataGridNode(data, false); + node.selectable = false; + nodes.push(node); + } + + var totalColumnWidths = 0; + for (var columnIdentifier in columns) + totalColumnWidths += columns[columnIdentifier].width; + + // Calculate the percentage width for the columns. + const minimumPrecent = 5; + var recoupPercent = 0; + for (var columnIdentifier in columns) { + var width = columns[columnIdentifier].width; + width = Math.round((width / totalColumnWidths) * 100); + if (width < minimumPrecent) { + recoupPercent += (minimumPrecent - width); + width = minimumPrecent; + } + + columns[columnIdentifier].width = width; + } + + // Enforce the minimum percentage width. + while (recoupPercent > 0) { + for (var columnIdentifier in columns) { + if (columns[columnIdentifier].width > minimumPrecent) { + --columns[columnIdentifier].width; + --recoupPercent; + if (!recoupPercent) + break; + } + } + } + + // Change the width property to a string suitable for a style width. + for (var columnIdentifier in columns) + columns[columnIdentifier].width += "%"; + + var dataGrid = new WebInspector.DataGrid(columns); + var length = nodes.length; + for (var i = 0; i < length; ++i) + dataGrid.appendChild(nodes[i]); + + return dataGrid; + }, + + dataGridForDOMStorage: function(domStorage) + { + if (!domStorage.length) + return null; + + var columns = {}; + columns[0] = {}; + columns[1] = {}; + columns[0].title = WebInspector.UIString("Key"); + columns[0].width = columns[0].title.length; + columns[1].title = WebInspector.UIString("Value"); + columns[1].width = columns[1].title.length; + + var nodes = []; + + var length = domStorage.length; + for (var index = 0; index < domStorage.length; index++) { + var data = {}; + + var key = String(domStorage.key(index)); + data[0] = key; + if (key.length > columns[0].width) + columns[0].width = key.length; + + var value = String(domStorage.getItem(key)); + data[1] = value; + if (value.length > columns[1].width) + columns[1].width = value.length; + var node = new WebInspector.DataGridNode(data, false); + node.selectable = true; + nodes.push(node); + } + + var totalColumnWidths = columns[0].width + columns[1].width; + var width = Math.round((columns[0].width * 100) / totalColumnWidths); + const minimumPrecent = 10; + if (width < minimumPrecent) + width = minimumPrecent; + if (width > 100 - minimumPrecent) + width = 100 - minimumPrecent; + columns[0].width = width; + columns[1].width = 100 - width; + columns[0].width += "%"; + columns[1].width += "%"; + + var dataGrid = new WebInspector.DOMStorageDataGrid(columns); + var length = nodes.length; + for (var i = 0; i < length; ++i) + dataGrid.appendChild(nodes[i]); + dataGrid.addCreationNode(false); + if (length > 0) + nodes[0].selected = true; + return dataGrid; + }, + + resize: function() + { + var visibleView = this.visibleView; + if (visibleView && "resize" in visibleView) + visibleView.resize(); + }, + + _registerStorageEventListener: function() + { + var inspectedWindow = InspectorController.inspectedWindow(); + if (!inspectedWindow || !inspectedWindow.document) + return; + + this._storageEventListener = InspectorController.wrapCallback(this._storageEvent.bind(this)); + inspectedWindow.addEventListener("storage", this._storageEventListener, true); + }, + + _unregisterStorageEventListener: function() + { + if (!this._storageEventListener) + return; + + var inspectedWindow = InspectorController.inspectedWindow(); + if (!inspectedWindow || !inspectedWindow.document) + return; + + inspectedWindow.removeEventListener("storage", this._storageEventListener, true); + delete this._storageEventListener; + }, + + _storageEvent: function(event) + { + if (!this._domStorage) + return; + + var isLocalStorage = (event.storageArea === InspectorController.inspectedWindow().localStorage); + var domStorageLength = this._domStorage.length; + for (var i = 0; i < domStorageLength; ++i) { + var domStorage = this._domStorage[i]; + if (isLocalStorage === domStorage.isLocalStorage) { + var view = domStorage._domStorageView; + if (this.visibleView && view === this.visibleView) + domStorage._domStorageView.update(); + } + } + }, + + _startSidebarDragging: function(event) + { + WebInspector.elementDragStart(this.sidebarResizeElement, this._sidebarDragging.bind(this), this._endSidebarDragging.bind(this), event, "col-resize"); + }, + + _sidebarDragging: function(event) + { + this._updateSidebarWidth(event.pageX); + + event.preventDefault(); + }, + + _endSidebarDragging: function(event) + { + WebInspector.elementDragEnd(event); + }, + + _updateSidebarWidth: function(width) + { + if (this.sidebarElement.offsetWidth <= 0) { + // The stylesheet hasn't loaded yet or the window is closed, + // so we can't calculate what is need. Return early. + return; + } + + if (!("_currentSidebarWidth" in this)) + this._currentSidebarWidth = this.sidebarElement.offsetWidth; + + if (typeof width === "undefined") + width = this._currentSidebarWidth; + + width = Number.constrain(width, Preferences.minSidebarWidth, window.innerWidth / 2); + + this._currentSidebarWidth = width; + + this.sidebarElement.style.width = width + "px"; + this.storageViews.style.left = width + "px"; + this.storageViewStatusBarItemsContainer.style.left = width + "px"; + this.sidebarResizeElement.style.left = (width - 3) + "px"; + + var visibleView = this.visibleView; + if (visibleView && "resize" in visibleView) + visibleView.resize(); + } +} + +WebInspector.StoragePanel.prototype.__proto__ = WebInspector.Panel.prototype; + +WebInspector.DatabaseSidebarTreeElement = function(database) +{ + this.database = database; + + WebInspector.SidebarTreeElement.call(this, "database-sidebar-tree-item", "", "", database, true); + + this.refreshTitles(); +} + +WebInspector.DatabaseSidebarTreeElement.prototype = { + onselect: function() + { + WebInspector.panels.storage.showDatabase(this.database); + }, + + oncollapse: function() + { + // Request a refresh after every collapse so the next + // expand will have an updated table list. + this.shouldRefreshChildren = true; + }, + + onpopulate: function() + { + this.removeChildren(); + + var self = this; + function tableNamesCallback(tableNames) + { + var tableNamesLength = tableNames.length; + for (var i = 0; i < tableNamesLength; ++i) + self.appendChild(new WebInspector.SidebarDatabaseTableTreeElement(self.database, tableNames[i])); + } + this.database.getTableNames(tableNamesCallback); + }, + + get mainTitle() + { + return this.database.name; + }, + + set mainTitle(x) + { + // Do nothing. + }, + + get subtitle() + { + return this.database.displayDomain; + }, + + set subtitle(x) + { + // Do nothing. + } +} + +WebInspector.DatabaseSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype; + +WebInspector.SidebarDatabaseTableTreeElement = function(database, tableName) +{ + this.database = database; + this.tableName = tableName; + + WebInspector.SidebarTreeElement.call(this, "database-table-sidebar-tree-item small", tableName, "", null, false); +} + +WebInspector.SidebarDatabaseTableTreeElement.prototype = { + onselect: function() + { + WebInspector.panels.storage.showDatabase(this.database, this.tableName); + } +} + +WebInspector.SidebarDatabaseTableTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype; + +WebInspector.DOMStorageSidebarTreeElement = function(domStorage, className) +{ + + this.domStorage = domStorage; + + WebInspector.SidebarTreeElement.call(this, "domstorage-sidebar-tree-item " + className, domStorage, "", null, false); + + this.refreshTitles(); +} + +WebInspector.DOMStorageSidebarTreeElement.prototype = { + onselect: function() + { + WebInspector.panels.storage.showDOMStorage(this.domStorage); + }, + + get mainTitle() + { + return this.domStorage.domain; + }, + + set mainTitle(x) + { + // Do nothing. + }, + + get subtitle() + { + return ""; //this.database.displayDomain; + }, + + set subtitle(x) + { + // Do nothing. + } +} + +WebInspector.DOMStorageSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype; + +WebInspector.CookieSidebarTreeElement = function() +{ + WebInspector.SidebarTreeElement.call(this, "cookie-sidebar-tree-item", null, "", null, false); + + this.refreshTitles(); +} + +WebInspector.CookieSidebarTreeElement.prototype = { + onselect: function() + { + WebInspector.panels.storage.showCookies(); + }, + + get mainTitle() + { + return WebInspector.UIString("Cookies"); + }, + + set mainTitle(x) + { + // Do nothing. + }, + + get subtitle() + { + return ""; + }, + + set subtitle(x) + { + // Do nothing. + } +} + +WebInspector.CookieSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/StylesSidebarPane.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/StylesSidebarPane.js new file mode 100644 index 0000000..6185aff --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/StylesSidebarPane.js @@ -0,0 +1,1373 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * Copyright (C) 2009 Joseph Pecoraro + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.StylesSidebarPane = function() +{ + WebInspector.SidebarPane.call(this, WebInspector.UIString("Styles")); + + this.settingsSelectElement = document.createElement("select"); + + var option = document.createElement("option"); + option.value = "hex"; + option.action = this._changeColorFormat.bind(this); + if (Preferences.colorFormat === "hex") + option.selected = true; + option.label = WebInspector.UIString("Hex Colors"); + this.settingsSelectElement.appendChild(option); + + option = document.createElement("option"); + option.value = "rgb"; + option.action = this._changeColorFormat.bind(this); + if (Preferences.colorFormat === "rgb") + option.selected = true; + option.label = WebInspector.UIString("RGB Colors"); + this.settingsSelectElement.appendChild(option); + + option = document.createElement("option"); + option.value = "hsl"; + option.action = this._changeColorFormat.bind(this); + if (Preferences.colorFormat === "hsl") + option.selected = true; + option.label = WebInspector.UIString("HSL Colors"); + this.settingsSelectElement.appendChild(option); + + this.settingsSelectElement.appendChild(document.createElement("hr")); + + option = document.createElement("option"); + option.action = this._createNewRule.bind(this); + option.label = WebInspector.UIString("New Style Rule"); + this.settingsSelectElement.appendChild(option); + + this.settingsSelectElement.addEventListener("click", function(event) { event.stopPropagation() }, false); + this.settingsSelectElement.addEventListener("change", this._changeSetting.bind(this), false); + + this.titleElement.appendChild(this.settingsSelectElement); +} + +WebInspector.StylesSidebarPane.prototype = { + update: function(node, editedSection, forceUpdate) + { + var refresh = false; + + if (forceUpdate) + delete this.node; + + if (!forceUpdate && (!node || node === this.node)) + refresh = true; + + if (node && node.nodeType === Node.TEXT_NODE && node.parentNode) + node = node.parentNode; + + if (node && node.nodeType !== Node.ELEMENT_NODE) + node = null; + + if (node) + this.node = node; + else + node = this.node; + + var body = this.bodyElement; + if (!refresh || !node) { + body.removeChildren(); + this.sections = []; + } + + if (!node) + return; + + var self = this; + function callback(styles) + { + if (!styles) + return; + node._setStyles(styles.computedStyle, styles.inlineStyle, styles.styleAttributes, styles.matchedCSSRules); + self._update(refresh, body, node, editedSection, forceUpdate); + } + + InjectedScriptAccess.getStyles(node.id, !Preferences.showUserAgentStyles, callback); + }, + + _update: function(refresh, body, node, editedSection, forceUpdate) + { + if (!refresh) { + body.removeChildren(); + this.sections = []; + } + + var styleRules = []; + + if (refresh) { + for (var i = 0; i < this.sections.length; ++i) { + var section = this.sections[i]; + if (section instanceof WebInspector.BlankStylePropertiesSection) + continue; + if (section.computedStyle) + section.styleRule.style = node.ownerDocument.defaultView.getComputedStyle(node); + var styleRule = { section: section, style: section.styleRule.style, computedStyle: section.computedStyle, rule: section.rule }; + styleRules.push(styleRule); + } + } else { + var computedStyle = node.ownerDocument.defaultView.getComputedStyle(node); + styleRules.push({ computedStyle: true, selectorText: WebInspector.UIString("Computed Style"), style: computedStyle, editable: false }); + + var nodeName = node.nodeName.toLowerCase(); + for (var i = 0; i < node.attributes.length; ++i) { + var attr = node.attributes[i]; + if (attr.style) { + var attrStyle = { style: attr.style, editable: false }; + attrStyle.subtitle = WebInspector.UIString("element’s “%s†attribute", attr.name); + attrStyle.selectorText = nodeName + "[" + attr.name; + if (attr.value.length) + attrStyle.selectorText += "=" + attr.value; + attrStyle.selectorText += "]"; + styleRules.push(attrStyle); + } + } + + // Always Show element's Style Attributes + if (node.nodeType === Node.ELEMENT_NODE) { + var inlineStyle = { selectorText: WebInspector.UIString("Style Attribute"), style: node.style, isAttribute: true }; + inlineStyle.subtitle = WebInspector.UIString("element’s “%s†attribute", "style"); + styleRules.push(inlineStyle); + } + + var matchedStyleRules = node.ownerDocument.defaultView.getMatchedCSSRules(node, "", !Preferences.showUserAgentStyles); + if (matchedStyleRules) { + // Add rules in reverse order to match the cascade order. + for (var i = (matchedStyleRules.length - 1); i >= 0; --i) { + var rule = matchedStyleRules[i]; + styleRules.push({ style: rule.style, selectorText: rule.selectorText, parentStyleSheet: rule.parentStyleSheet, rule: rule }); + } + } + } + + function deleteDisabledProperty(style, name) + { + if (!style || !name) + return; + if (style.__disabledPropertyValues) + delete style.__disabledPropertyValues[name]; + if (style.__disabledPropertyPriorities) + delete style.__disabledPropertyPriorities[name]; + if (style.__disabledProperties) + delete style.__disabledProperties[name]; + } + + var usedProperties = {}; + var disabledComputedProperties = {}; + var priorityUsed = false; + + // Walk the style rules and make a list of all used and overloaded properties. + for (var i = 0; i < styleRules.length; ++i) { + var styleRule = styleRules[i]; + if (styleRule.computedStyle) + continue; + if (styleRule.section && styleRule.section.noAffect) + continue; + + styleRule.usedProperties = {}; + + var style = styleRule.style; + for (var j = 0; j < style.length; ++j) { + var name = style[j]; + + if (!priorityUsed && style.getPropertyPriority(name).length) + priorityUsed = true; + + // If the property name is already used by another rule then this rule's + // property is overloaded, so don't add it to the rule's usedProperties. + if (!(name in usedProperties)) + styleRule.usedProperties[name] = true; + + if (name === "font") { + // The font property is not reported as a shorthand. Report finding the individual + // properties so they are visible in computed style. + // FIXME: remove this when http://bugs.webkit.org/show_bug.cgi?id=15598 is fixed. + styleRule.usedProperties["font-family"] = true; + styleRule.usedProperties["font-size"] = true; + styleRule.usedProperties["font-style"] = true; + styleRule.usedProperties["font-variant"] = true; + styleRule.usedProperties["font-weight"] = true; + styleRule.usedProperties["line-height"] = true; + } + + // Delete any disabled properties, since the property does exist. + // This prevents it from showing twice. + deleteDisabledProperty(style, name); + deleteDisabledProperty(style, style.getPropertyShorthand(name)); + } + + // Add all the properties found in this style to the used properties list. + // Do this here so only future rules are affect by properties used in this rule. + for (var name in styleRules[i].usedProperties) + usedProperties[name] = true; + + // Remember all disabled properties so they show up in computed style. + if (style.__disabledProperties) + for (var name in style.__disabledProperties) + disabledComputedProperties[name] = true; + } + + if (priorityUsed) { + // Walk the properties again and account for !important. + var foundPriorityProperties = []; + + // Walk in reverse to match the order !important overrides. + for (var i = (styleRules.length - 1); i >= 0; --i) { + if (styleRules[i].computedStyle) + continue; + + var style = styleRules[i].style; + var uniqueProperties = style.uniqueStyleProperties; + for (var j = 0; j < uniqueProperties.length; ++j) { + var name = uniqueProperties[j]; + if (style.getPropertyPriority(name).length) { + if (!(name in foundPriorityProperties)) + styleRules[i].usedProperties[name] = true; + else + delete styleRules[i].usedProperties[name]; + foundPriorityProperties[name] = true; + } else if (name in foundPriorityProperties) + delete styleRules[i].usedProperties[name]; + } + } + } + + if (refresh) { + // Walk the style rules and update the sections with new overloaded and used properties. + for (var i = 0; i < styleRules.length; ++i) { + var styleRule = styleRules[i]; + var section = styleRule.section; + if (styleRule.computedStyle) + section.disabledComputedProperties = disabledComputedProperties; + section._usedProperties = (styleRule.usedProperties || usedProperties); + section.update((section === editedSection) || styleRule.computedStyle); + } + } else { + // Make a property section for each style rule. + for (var i = 0; i < styleRules.length; ++i) { + var styleRule = styleRules[i]; + var subtitle = styleRule.subtitle; + delete styleRule.subtitle; + + var computedStyle = styleRule.computedStyle; + delete styleRule.computedStyle; + + var ruleUsedProperties = styleRule.usedProperties; + delete styleRule.usedProperties; + + var editable = styleRule.editable; + delete styleRule.editable; + + var isAttribute = styleRule.isAttribute; + delete styleRule.isAttribute; + + // Default editable to true if it was omitted. + if (typeof editable === "undefined") + editable = true; + + var section = new WebInspector.StylePropertiesSection(styleRule, subtitle, computedStyle, (ruleUsedProperties || usedProperties), editable); + if (computedStyle) + section.disabledComputedProperties = disabledComputedProperties; + section.pane = this; + + if (Preferences.styleRulesExpandedState && section.identifier in Preferences.styleRulesExpandedState) + section.expanded = Preferences.styleRulesExpandedState[section.identifier]; + else if (computedStyle) + section.collapse(true); + else if (isAttribute && styleRule.style.length === 0) + section.collapse(true); + else + section.expand(true); + + body.appendChild(section.element); + this.sections.push(section); + } + } + }, + + _changeSetting: function(event) + { + var options = this.settingsSelectElement.options; + var selectedOption = options[this.settingsSelectElement.selectedIndex]; + selectedOption.action(event); + + // Select the correct color format setting again, since it needs to be selected. + var selectedIndex = 0; + for (var i = 0; i < options.length; ++i) { + if (options[i].value === Preferences.colorFormat) { + selectedIndex = i; + break; + } + } + + this.settingsSelectElement.selectedIndex = selectedIndex; + }, + + _changeColorFormat: function(event) + { + var selectedOption = this.settingsSelectElement[this.settingsSelectElement.selectedIndex]; + Preferences.colorFormat = selectedOption.value; + + InspectorController.setSetting("color-format", Preferences.colorFormat); + + for (var i = 0; i < this.sections.length; ++i) + this.sections[i].update(true); + }, + + _createNewRule: function(event) + { + this.addBlankSection().startEditingSelector(); + }, + + addBlankSection: function() + { + var blankSection = new WebInspector.BlankStylePropertiesSection(this.appropriateSelectorForNode()); + blankSection.pane = this; + + var elementStyleSection = this.sections[1]; + this.bodyElement.insertBefore(blankSection.element, elementStyleSection.element.nextSibling); + + this.sections.splice(2, 0, blankSection); + + return blankSection; + }, + + removeSection: function(section) + { + var index = this.sections.indexOf(section); + if (index === -1) + return; + this.sections.splice(index, 1); + if (section.element.parentNode) + section.element.parentNode.removeChild(section.element); + }, + + appropriateSelectorForNode: function() + { + var node = this.node; + if (!node) + return ""; + + var id = node.getAttribute("id"); + if (id) + return "#" + id; + + var className = node.getAttribute("class"); + if (className) + return "." + className.replace(/\s+/, "."); + + var nodeName = node.nodeName.toLowerCase(); + if (nodeName === "input" && node.getAttribute("type")) + return nodeName + "[type=\"" + node.getAttribute("type") + "\"]"; + + return nodeName; + } +} + +WebInspector.StylesSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype; + +WebInspector.StylePropertiesSection = function(styleRule, subtitle, computedStyle, usedProperties, editable) +{ + WebInspector.PropertiesSection.call(this, styleRule.selectorText); + + this.titleElement.addEventListener("click", function(e) { e.stopPropagation(); }, false); + this.titleElement.addEventListener("dblclick", this._dblclickSelector.bind(this), false); + this.element.addEventListener("dblclick", this._dblclickEmptySpace.bind(this), false); + + this.styleRule = styleRule; + this.rule = this.styleRule.rule; + this.computedStyle = computedStyle; + this.editable = (editable && !computedStyle); + + // Prevent editing the user agent and user rules. + var isUserAgent = this.rule && this.rule.isUserAgent; + var isUser = this.rule && this.rule.isUser; + var isViaInspector = this.rule && this.rule.isViaInspector; + + if (isUserAgent || isUser) + this.editable = false; + + this._usedProperties = usedProperties; + + if (computedStyle) { + this.element.addStyleClass("computed-style"); + + if (Preferences.showInheritedComputedStyleProperties) + this.element.addStyleClass("show-inherited"); + + var showInheritedLabel = document.createElement("label"); + var showInheritedInput = document.createElement("input"); + showInheritedInput.type = "checkbox"; + showInheritedInput.checked = Preferences.showInheritedComputedStyleProperties; + + var computedStyleSection = this; + var showInheritedToggleFunction = function(event) { + Preferences.showInheritedComputedStyleProperties = showInheritedInput.checked; + if (Preferences.showInheritedComputedStyleProperties) + computedStyleSection.element.addStyleClass("show-inherited"); + else + computedStyleSection.element.removeStyleClass("show-inherited"); + event.stopPropagation(); + }; + + showInheritedLabel.addEventListener("click", showInheritedToggleFunction, false); + + showInheritedLabel.appendChild(showInheritedInput); + showInheritedLabel.appendChild(document.createTextNode(WebInspector.UIString("Show inherited"))); + this.subtitleElement.appendChild(showInheritedLabel); + } else { + if (!subtitle) { + if (this.styleRule.parentStyleSheet && this.styleRule.parentStyleSheet.href) { + var url = this.styleRule.parentStyleSheet.href; + subtitle = WebInspector.linkifyURL(url, WebInspector.displayNameForURL(url)); + this.subtitleElement.addStyleClass("file"); + } else if (isUserAgent) + subtitle = WebInspector.UIString("user agent stylesheet"); + else if (isUser) + subtitle = WebInspector.UIString("user stylesheet"); + else if (isViaInspector) + subtitle = WebInspector.UIString("via inspector"); + else + subtitle = WebInspector.UIString("inline stylesheet"); + } + + this.subtitle = subtitle; + } + + this.identifier = styleRule.selectorText; + if (this.subtitle) + this.identifier += ":" + this.subtitleElement.textContent; +} + +WebInspector.StylePropertiesSection.prototype = { + get usedProperties() + { + return this._usedProperties || {}; + }, + + set usedProperties(x) + { + this._usedProperties = x; + this.update(); + }, + + expand: function(dontRememberState) + { + WebInspector.PropertiesSection.prototype.expand.call(this); + if (dontRememberState) + return; + + if (!Preferences.styleRulesExpandedState) + Preferences.styleRulesExpandedState = {}; + Preferences.styleRulesExpandedState[this.identifier] = true; + }, + + collapse: function(dontRememberState) + { + WebInspector.PropertiesSection.prototype.collapse.call(this); + if (dontRememberState) + return; + + if (!Preferences.styleRulesExpandedState) + Preferences.styleRulesExpandedState = {}; + Preferences.styleRulesExpandedState[this.identifier] = false; + }, + + isPropertyInherited: function(property) + { + if (!this.computedStyle || !this._usedProperties || this.noAffect) + return false; + // These properties should always show for Computed Style. + var alwaysShowComputedProperties = { "display": true, "height": true, "width": true }; + return !(property in this.usedProperties) && !(property in alwaysShowComputedProperties) && !(property in this.disabledComputedProperties); + }, + + isPropertyOverloaded: function(property, shorthand) + { + if (this.computedStyle || !this._usedProperties || this.noAffect) + return false; + + var used = (property in this.usedProperties); + if (used || !shorthand) + return !used; + + // Find out if any of the individual longhand properties of the shorthand + // are used, if none are then the shorthand is overloaded too. + var longhandProperties = this.styleRule.style.getLonghandProperties(property); + for (var j = 0; j < longhandProperties.length; ++j) { + var individualProperty = longhandProperties[j]; + if (individualProperty in this.usedProperties) + return false; + } + + return true; + }, + + isInspectorStylesheet: function() + { + return (this.styleRule.parentStyleSheet === WebInspector.panels.elements.stylesheet); + }, + + update: function(full) + { + if (full || this.computedStyle) { + this.propertiesTreeOutline.removeChildren(); + this.populated = false; + } else { + var child = this.propertiesTreeOutline.children[0]; + while (child) { + child.overloaded = this.isPropertyOverloaded(child.name, child.shorthand); + child = child.traverseNextTreeElement(false, null, true); + } + } + + if (this._afterUpdate) { + this._afterUpdate(this); + delete this._afterUpdate; + } + }, + + onpopulate: function() + { + var style = this.styleRule.style; + + var foundShorthands = {}; + var uniqueProperties = style.uniqueStyleProperties; + var disabledProperties = style.__disabledPropertyValues || {}; + + for (var name in disabledProperties) + uniqueProperties.push(name); + + uniqueProperties.sort(); + + for (var i = 0; i < uniqueProperties.length; ++i) { + var name = uniqueProperties[i]; + var disabled = name in disabledProperties; + if (!disabled && this.disabledComputedProperties && !(name in this.usedProperties) && name in this.disabledComputedProperties) + disabled = true; + + var shorthand = !disabled ? style.getPropertyShorthand(name) : null; + + if (shorthand && shorthand in foundShorthands) + continue; + + if (shorthand) { + foundShorthands[shorthand] = true; + name = shorthand; + } + + var isShorthand = (shorthand ? true : false); + var inherited = this.isPropertyInherited(name); + var overloaded = this.isPropertyOverloaded(name, isShorthand); + + var item = new WebInspector.StylePropertyTreeElement(this.styleRule, style, name, isShorthand, inherited, overloaded, disabled); + this.propertiesTreeOutline.appendChild(item); + } + }, + + findTreeElementWithName: function(name) + { + var treeElement = this.propertiesTreeOutline.children[0]; + while (treeElement) { + if (treeElement.name === name) + return treeElement; + treeElement = treeElement.traverseNextTreeElement(true, null, true); + } + return null; + }, + + addNewBlankProperty: function() + { + var item = new WebInspector.StylePropertyTreeElement(this.styleRule, this.styleRule.style, "", false, false, false, false); + this.propertiesTreeOutline.appendChild(item); + item.listItemElement.textContent = ""; + item._newProperty = true; + return item; + }, + + _dblclickEmptySpace: function(event) + { + this.expand(); + this.addNewBlankProperty().startEditing(); + }, + + _dblclickSelector: function(event) + { + if (!this.editable) + return; + + if (!this.rule && this.propertiesTreeOutline.children.length === 0) { + this.expand(); + this.addNewBlankProperty().startEditing(); + return; + } + + if (!this.rule) + return; + + this.startEditingSelector(); + event.stopPropagation(); + }, + + startEditingSelector: function() + { + var element = this.titleElement; + if (WebInspector.isBeingEdited(element)) + return; + + WebInspector.startEditing(this.titleElement, this.editingSelectorCommitted.bind(this), this.editingSelectorCancelled.bind(this), null); + window.getSelection().setBaseAndExtent(element, 0, element, 1); + }, + + editingSelectorCommitted: function(element, newContent, oldContent, context, moveDirection) + { + function moveToNextIfNeeded() { + if (!moveDirection || moveDirection !== "forward") + return; + + this.expand(); + if (this.propertiesTreeOutline.children.length === 0) + this.addNewBlankProperty().startEditing(); + else { + var item = this.propertiesTreeOutline.children[0] + item.startEditing(item.valueElement); + } + } + + if (newContent === oldContent) + return moveToNextIfNeeded.call(this); + + var self = this; + function callback(result) + { + if (!result) { + // Invalid Syntax for a Selector + moveToNextIfNeeded.call(self); + return; + } + + var newRulePayload = result[0]; + var doesAffectSelectedNode = result[1]; + if (!doesAffectSelectedNode) { + self.noAffect = true; + self.element.addStyleClass("no-affect"); + } else { + delete self.noAffect; + self.element.removeStyleClass("no-affect"); + } + + var newRule = WebInspector.CSSStyleDeclaration.parseRule(newRulePayload); + self.rule = newRule; + self.styleRule = { section: self, style: newRule.style, selectorText: newRule.selectorText, parentStyleSheet: newRule.parentStyleSheet, rule: newRule }; + + var oldIdentifier = this.identifier; + self.identifier = newRule.selectorText + ":" + self.subtitleElement.textContent; + + self.pane.update(); + + WebInspector.panels.elements.renameSelector(oldIdentifier, this.identifier, oldContent, newContent); + + moveToNextIfNeeded.call(self); + } + + InjectedScriptAccess.applyStyleRuleText(this.rule.id, newContent, this.pane.node.id, callback); + }, + + editingSelectorCancelled: function() + { + // Do nothing, this is overridden by BlankStylePropertiesSection. + } +} + +WebInspector.StylePropertiesSection.prototype.__proto__ = WebInspector.PropertiesSection.prototype; + +WebInspector.BlankStylePropertiesSection = function(defaultSelectorText) +{ + WebInspector.StylePropertiesSection.call(this, {selectorText: defaultSelectorText, rule: {isViaInspector: true}}, "", false, {}, false); + + this.element.addStyleClass("blank-section"); +} + +WebInspector.BlankStylePropertiesSection.prototype = { + expand: function() + { + // Do nothing, blank sections are not expandable. + }, + + editingSelectorCommitted: function(element, newContent, oldContent, context) + { + var self = this; + function callback(result) + { + if (!result) { + // Invalid Syntax for a Selector + self.editingSelectorCancelled(); + return; + } + + var rule = result[0]; + var doesSelectorAffectSelectedNode = result[1]; + + var styleRule = WebInspector.CSSStyleDeclaration.parseRule(rule); + styleRule.rule = rule; + + self.makeNormal(styleRule); + + if (!doesSelectorAffectSelectedNode) { + self.noAffect = true; + self.element.addStyleClass("no-affect"); + } + + self.subtitleElement.textContent = WebInspector.UIString("via inspector"); + self.expand(); + + self.addNewBlankProperty().startEditing(); + } + + InjectedScriptAccess.addStyleSelector(newContent, this.pane.node.id, callback); + }, + + editingSelectorCancelled: function() + { + this.pane.removeSection(this); + }, + + makeNormal: function(styleRule) + { + this.element.removeStyleClass("blank-section"); + + this.styleRule = styleRule; + this.rule = styleRule.rule; + this.computedStyle = false; + this.editable = true; + this.identifier = styleRule.selectorText + ":via inspector"; + + this.__proto__ = WebInspector.StylePropertiesSection.prototype; + } +} + +WebInspector.BlankStylePropertiesSection.prototype.__proto__ = WebInspector.StylePropertiesSection.prototype; + +WebInspector.StylePropertyTreeElement = function(styleRule, style, name, shorthand, inherited, overloaded, disabled) +{ + this._styleRule = styleRule; + this.style = style; + this.name = name; + this.shorthand = shorthand; + this._inherited = inherited; + this._overloaded = overloaded; + this._disabled = disabled; + + // Pass an empty title, the title gets made later in onattach. + TreeElement.call(this, "", null, shorthand); +} + +WebInspector.StylePropertyTreeElement.prototype = { + get inherited() + { + return this._inherited; + }, + + set inherited(x) + { + if (x === this._inherited) + return; + this._inherited = x; + this.updateState(); + }, + + get overloaded() + { + return this._overloaded; + }, + + set overloaded(x) + { + if (x === this._overloaded) + return; + this._overloaded = x; + this.updateState(); + }, + + get disabled() + { + return this._disabled; + }, + + set disabled(x) + { + if (x === this._disabled) + return; + this._disabled = x; + this.updateState(); + }, + + get priority() + { + if (this.disabled && this.style.__disabledPropertyPriorities && this.name in this.style.__disabledPropertyPriorities) + return this.style.__disabledPropertyPriorities[this.name]; + return (this.shorthand ? this.style.getShorthandPriority(this.name) : this.style.getPropertyPriority(this.name)); + }, + + get value() + { + if (this.disabled && this.style.__disabledPropertyValues && this.name in this.style.__disabledPropertyValues) + return this.style.__disabledPropertyValues[this.name]; + return (this.shorthand ? this.style.getShorthandValue(this.name) : this.style.getPropertyValue(this.name)); + }, + + onattach: function() + { + this.updateTitle(); + }, + + updateTitle: function() + { + var priority = this.priority; + var value = this.value; + + if (priority && !priority.length) + delete priority; + if (priority) + priority = "!" + priority; + + this.updateState(); + + var enabledCheckboxElement = document.createElement("input"); + enabledCheckboxElement.className = "enabled-button"; + enabledCheckboxElement.type = "checkbox"; + enabledCheckboxElement.checked = !this.disabled; + enabledCheckboxElement.addEventListener("change", this.toggleEnabled.bind(this), false); + + var nameElement = document.createElement("span"); + nameElement.className = "name"; + nameElement.textContent = this.name; + this.nameElement = nameElement; + + var valueElement = document.createElement("span"); + valueElement.className = "value"; + this.valueElement = valueElement; + + if (value) { + function processValue(regex, processor, nextProcessor, valueText) + { + var container = document.createDocumentFragment(); + + var items = valueText.replace(regex, "\0$1\0").split("\0"); + for (var i = 0; i < items.length; ++i) { + if ((i % 2) === 0) { + if (nextProcessor) + container.appendChild(nextProcessor(items[i])); + else + container.appendChild(document.createTextNode(items[i])); + } else { + var processedNode = processor(items[i]); + if (processedNode) + container.appendChild(processedNode); + } + } + + return container; + } + + function linkifyURL(url) + { + var container = document.createDocumentFragment(); + container.appendChild(document.createTextNode("url(")); + container.appendChild(WebInspector.linkifyURLAsNode(url, url, null, (url in WebInspector.resourceURLMap))); + container.appendChild(document.createTextNode(")")); + return container; + } + + function processColor(text) + { + try { + var color = new WebInspector.Color(text); + } catch (e) { + return document.createTextNode(text); + } + + var swatchElement = document.createElement("span"); + swatchElement.title = WebInspector.UIString("Click to change color format"); + swatchElement.className = "swatch"; + swatchElement.style.setProperty("background-color", text); + + swatchElement.addEventListener("click", changeColorDisplay, false); + swatchElement.addEventListener("dblclick", function(event) { event.stopPropagation() }, false); + + var format; + if (Preferences.showColorNicknames && color.nickname) + format = "nickname"; + else if (Preferences.colorFormat === "rgb") + format = (color.simple ? "rgb" : "rgba"); + else if (Preferences.colorFormat === "hsl") + format = (color.simple ? "hsl" : "hsla"); + else if (color.simple) + format = (color.hasShortHex() ? "shorthex" : "hex"); + else + format = "rgba"; + + var colorValueElement = document.createElement("span"); + colorValueElement.textContent = color.toString(format); + + function changeColorDisplay(event) + { + switch (format) { + case "rgb": + format = "hsl"; + break; + + case "shorthex": + format = "hex"; + break; + + case "hex": + format = "rgb"; + break; + + case "nickname": + if (color.simple) { + if (color.hasShortHex()) + format = "shorthex"; + else + format = "hex"; + break; + } + + format = "rgba"; + break; + + case "hsl": + if (color.nickname) + format = "nickname"; + else if (color.hasShortHex()) + format = "shorthex"; + else + format = "hex"; + break; + + case "rgba": + format = "hsla"; + break; + + case "hsla": + if (color.nickname) + format = "nickname"; + else + format = "rgba"; + break; + } + + colorValueElement.textContent = color.toString(format); + } + + var container = document.createDocumentFragment(); + container.appendChild(swatchElement); + container.appendChild(colorValueElement); + return container; + } + + var colorRegex = /((?:rgb|hsl)a?\([^)]+\)|#[0-9a-fA-F]{6}|#[0-9a-fA-F]{3}|\b\w+\b)/g; + var colorProcessor = processValue.bind(window, colorRegex, processColor, null); + + valueElement.appendChild(processValue(/url\(([^)]+)\)/g, linkifyURL, colorProcessor, value)); + } + + if (priority) { + var priorityElement = document.createElement("span"); + priorityElement.className = "priority"; + priorityElement.textContent = priority; + } + + this.listItemElement.removeChildren(); + + // Append the checkbox for root elements of an editable section. + if (this.treeOutline.section && this.treeOutline.section.editable && this.parent.root) + this.listItemElement.appendChild(enabledCheckboxElement); + this.listItemElement.appendChild(nameElement); + this.listItemElement.appendChild(document.createTextNode(": ")); + this.listItemElement.appendChild(valueElement); + + if (priorityElement) { + this.listItemElement.appendChild(document.createTextNode(" ")); + this.listItemElement.appendChild(priorityElement); + } + + this.listItemElement.appendChild(document.createTextNode(";")); + + this.tooltip = this.name + ": " + valueElement.textContent + (priority ? " " + priority : ""); + }, + + updateAll: function(updateAllRules) + { + if (updateAllRules && this.treeOutline.section && this.treeOutline.section.pane) + this.treeOutline.section.pane.update(null, this.treeOutline.section); + else if (this.treeOutline.section) + this.treeOutline.section.update(true); + else + this.updateTitle(); // FIXME: this will not show new properties. But we don't hit his case yet. + }, + + toggleEnabled: function(event) + { + var disabled = !event.target.checked; + + var self = this; + function callback(newPayload) + { + if (!newPayload) + return; + + self.style = WebInspector.CSSStyleDeclaration.parseStyle(newPayload); + self._styleRule.style = self.style; + + // Set the disabled property here, since the code above replies on it not changing + // until after the value and priority are retrieved. + self.disabled = disabled; + + if (self.treeOutline.section && self.treeOutline.section.pane) + self.treeOutline.section.pane.dispatchEventToListeners("style property toggled"); + + self.updateAll(true); + } + + InjectedScriptAccess.toggleStyleEnabled(this.style.id, this.name, disabled, callback); + }, + + updateState: function() + { + if (!this.listItemElement) + return; + + if (this.style.isPropertyImplicit(this.name) || this.value === "initial") + this.listItemElement.addStyleClass("implicit"); + else + this.listItemElement.removeStyleClass("implicit"); + + if (this.inherited) + this.listItemElement.addStyleClass("inherited"); + else + this.listItemElement.removeStyleClass("inherited"); + + if (this.overloaded) + this.listItemElement.addStyleClass("overloaded"); + else + this.listItemElement.removeStyleClass("overloaded"); + + if (this.disabled) + this.listItemElement.addStyleClass("disabled"); + else + this.listItemElement.removeStyleClass("disabled"); + }, + + onpopulate: function() + { + // Only populate once and if this property is a shorthand. + if (this.children.length || !this.shorthand) + return; + + var longhandProperties = this.style.getLonghandProperties(this.name); + for (var i = 0; i < longhandProperties.length; ++i) { + var name = longhandProperties[i]; + + if (this.treeOutline.section) { + var inherited = this.treeOutline.section.isPropertyInherited(name); + var overloaded = this.treeOutline.section.isPropertyOverloaded(name); + } + + var item = new WebInspector.StylePropertyTreeElement(this._styleRule, this.style, name, false, inherited, overloaded); + this.appendChild(item); + } + }, + + ondblclick: function(element, event) + { + this.startEditing(event.target); + event.stopPropagation(); + }, + + startEditing: function(selectElement) + { + // FIXME: we don't allow editing of longhand properties under a shorthand right now. + if (this.parent.shorthand) + return; + + if (WebInspector.isBeingEdited(this.listItemElement) || (this.treeOutline.section && !this.treeOutline.section.editable)) + return; + + var context = { expanded: this.expanded, hasChildren: this.hasChildren }; + + // Lie about our children to prevent expanding on double click and to collapse shorthands. + this.hasChildren = false; + + if (!selectElement) + selectElement = this.listItemElement; + + this.listItemElement.handleKeyEvent = this.editingKeyDown.bind(this); + + WebInspector.startEditing(this.listItemElement, this.editingCommitted.bind(this), this.editingCancelled.bind(this), context); + window.getSelection().setBaseAndExtent(selectElement, 0, selectElement, 1); + }, + + editingKeyDown: function(event) + { + var arrowKeyPressed = (event.keyIdentifier === "Up" || event.keyIdentifier === "Down"); + var pageKeyPressed = (event.keyIdentifier === "PageUp" || event.keyIdentifier === "PageDown"); + if (!arrowKeyPressed && !pageKeyPressed) + return; + + var selection = window.getSelection(); + if (!selection.rangeCount) + return; + + var selectionRange = selection.getRangeAt(0); + if (selectionRange.commonAncestorContainer !== this.listItemElement && !selectionRange.commonAncestorContainer.isDescendant(this.listItemElement)) + return; + + const styleValueDelimeters = " \t\n\"':;,/()"; + var wordRange = selectionRange.startContainer.rangeOfWord(selectionRange.startOffset, styleValueDelimeters, this.listItemElement); + var wordString = wordRange.toString(); + var replacementString = wordString; + + var matches = /(.*?)(-?\d+(?:\.\d+)?)(.*)/.exec(wordString); + if (matches && matches.length) { + var prefix = matches[1]; + var number = parseFloat(matches[2]); + var suffix = matches[3]; + + // If the number is near zero or the number is one and the direction will take it near zero. + var numberNearZero = (number < 1 && number > -1); + if (number === 1 && event.keyIdentifier === "Down") + numberNearZero = true; + else if (number === -1 && event.keyIdentifier === "Up") + numberNearZero = true; + + if (numberNearZero && event.altKey && arrowKeyPressed) { + if (event.keyIdentifier === "Down") + number = Math.ceil(number - 1); + else + number = Math.floor(number + 1); + } else { + // Jump by 10 when shift is down or jump by 0.1 when near zero or Alt/Option is down. + // Also jump by 10 for page up and down, or by 100 if shift is held with a page key. + var changeAmount = 1; + if (event.shiftKey && pageKeyPressed) + changeAmount = 100; + else if (event.shiftKey || pageKeyPressed) + changeAmount = 10; + else if (event.altKey || numberNearZero) + changeAmount = 0.1; + + if (event.keyIdentifier === "Down" || event.keyIdentifier === "PageDown") + changeAmount *= -1; + + // Make the new number and constrain it to a precision of 6, this matches numbers the engine returns. + // Use the Number constructor to forget the fixed precision, so 1.100000 will print as 1.1. + number = Number((number + changeAmount).toFixed(6)); + } + + replacementString = prefix + number + suffix; + } else { + // FIXME: this should cycle through known keywords for the current property name. + return; + } + + var replacementTextNode = document.createTextNode(replacementString); + + wordRange.deleteContents(); + wordRange.insertNode(replacementTextNode); + + var finalSelectionRange = document.createRange(); + finalSelectionRange.setStart(replacementTextNode, 0); + finalSelectionRange.setEnd(replacementTextNode, replacementString.length); + + selection.removeAllRanges(); + selection.addRange(finalSelectionRange); + + event.preventDefault(); + event.handled = true; + + if (!this.originalCSSText) { + // Remember the rule's original CSS text, so it can be restored + // if the editing is canceled and before each apply. + this.originalCSSText = this.style.styleTextWithShorthands(); + } else { + // Restore the original CSS text before applying user changes. This is needed to prevent + // new properties from sticking around if the user adds one, then removes it. + InjectedScriptAccess.setStyleText(this.style.id, this.originalCSSText); + } + + this.applyStyleText(this.listItemElement.textContent); + }, + + editingEnded: function(context) + { + this.hasChildren = context.hasChildren; + if (context.expanded) + this.expand(); + delete this.listItemElement.handleKeyEvent; + delete this.originalCSSText; + }, + + editingCancelled: function(element, context) + { + if (this._newProperty) + this.treeOutline.removeChild(this); + else if (this.originalCSSText) { + InjectedScriptAccess.setStyleText(this.style.id, this.originalCSSText); + + if (this.treeOutline.section && this.treeOutline.section.pane) + this.treeOutline.section.pane.dispatchEventToListeners("style edited"); + + this.updateAll(); + } else + this.updateTitle(); + + this.editingEnded(context); + }, + + editingCommitted: function(element, userInput, previousContent, context, moveDirection) + { + this.editingEnded(context); + + // Determine where to move to before making changes + var newProperty, moveToPropertyName, moveToSelector; + var moveTo = (moveDirection === "forward" ? this.nextSibling : this.previousSibling); + if (moveTo) + moveToPropertyName = moveTo.name; + else if (moveDirection === "forward") + newProperty = true; + else if (moveDirection === "backward" && this.treeOutline.section.rule) + moveToSelector = true; + + // Make the Changes and trigger the moveToNextCallback after updating + var blankInput = /^\s*$/.test(userInput); + if (userInput !== previousContent || (this._newProperty && blankInput)) { // only if something changed, or adding a new style and it was blank + this.treeOutline.section._afterUpdate = moveToNextCallback.bind(this, this._newProperty, !blankInput); + this.applyStyleText(userInput, true); + } else + moveToNextCallback(this._newProperty, false, this.treeOutline.section, false); + + // The Callback to start editing the next property + function moveToNextCallback(alreadyNew, valueChanged, section) + { + if (!moveDirection) + return; + + // User just tabbed through without changes + if (moveTo && moveTo.parent) { + moveTo.startEditing(moveTo.valueElement); + return; + } + + // User has made a change then tabbed, wiping all the original treeElements, + // recalculate the new treeElement for the same property we were going to edit next + if (moveTo && !moveTo.parent) { + var treeElement = section.findTreeElementWithName(moveToPropertyName); + if (treeElement) + treeElement.startEditing(treeElement.valueElement); + return; + } + + // Create a new attribute in this section + if (newProperty) { + if (alreadyNew && !valueChanged) + return; + + var item = section.addNewBlankProperty(); + item.startEditing(); + return; + } + + if (moveToSelector) + section.startEditingSelector(); + } + }, + + applyStyleText: function(styleText, updateInterface) + { + var section = this.treeOutline.section; + var elementsPanel = WebInspector.panels.elements; + var styleTextLength = styleText.trimWhitespace().length; + if (!styleTextLength && updateInterface) { + if (this._newProperty) { + // The user deleted everything, so remove the tree element and update. + this.parent.removeChild(this); + return; + } else { + delete section._afterUpdate; + } + } + + var self = this; + function callback(result) + { + if (!result) { + // The user typed something, but it didn't parse. Just abort and restore + // the original title for this property. If this was a new attribute and + // we couldn't parse, then just remove it. + if (self._newProperty) { + self.parent.removeChild(self); + return; + } + if (updateInterface) + self.updateTitle(); + return; + } + + var newPayload = result[0]; + var changedProperties = result[1]; + elementsPanel.removeStyleChange(section.identifier, self.style, self.name); + + if (!styleTextLength) { + // Do remove ourselves from UI when the property removal is confirmed. + self.parent.removeChild(self); + } else { + self.style = WebInspector.CSSStyleDeclaration.parseStyle(newPayload); + for (var i = 0; i < changedProperties.length; ++i) + elementsPanel.addStyleChange(section.identifier, self.style, changedProperties[i]); + self._styleRule.style = self.style; + } + + if (section && section.pane) + section.pane.dispatchEventToListeners("style edited"); + + if (updateInterface) + self.updateAll(true); + + if (!self.rule) + WebInspector.panels.elements.treeOutline.update(); + } + + InjectedScriptAccess.applyStyleText(this.style.id, styleText.trimWhitespace(), this.name, callback); + } +} + +WebInspector.StylePropertyTreeElement.prototype.__proto__ = TreeElement.prototype; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/SummaryBar.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/SummaryBar.js new file mode 100644 index 0000000..bbf2b1a --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/SummaryBar.js @@ -0,0 +1,364 @@ +/* + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2008, 2009 Anthony Ricaud <rik@webkit.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.SummaryBar = function(categories) +{ + this.categories = categories; + + this.element = document.createElement("div"); + this.element.className = "summary-bar"; + + this.graphElement = document.createElement("canvas"); + this.graphElement.setAttribute("width", "450"); + this.graphElement.setAttribute("height", "38"); + this.graphElement.className = "summary-graph"; + this.element.appendChild(this.graphElement); + + this.legendElement = document.createElement("div"); + this.legendElement.className = "summary-graph-legend"; + this.element.appendChild(this.legendElement); +} + +WebInspector.SummaryBar.prototype = { + + get calculator() { + return this._calculator; + }, + + set calculator(x) { + this._calculator = x; + }, + + reset: function() + { + this.legendElement.removeChildren(); + this._drawSummaryGraph(); + }, + + update: function(data) + { + var graphInfo = this.calculator.computeSummaryValues(data); + + var fillSegments = []; + + this.legendElement.removeChildren(); + + for (var category in this.categories) { + var size = graphInfo.categoryValues[category]; + if (!size) + continue; + + var color = this.categories[category].color; + var colorString = "rgb(" + color.r + ", " + color.g + ", " + color.b + ")"; + + var fillSegment = {color: colorString, value: size}; + fillSegments.push(fillSegment); + + var legendLabel = this._makeLegendElement(this.categories[category].title, this.calculator.formatValue(size), colorString); + this.legendElement.appendChild(legendLabel); + } + + if (graphInfo.total) { + var totalLegendLabel = this._makeLegendElement(WebInspector.UIString("Total"), this.calculator.formatValue(graphInfo.total)); + totalLegendLabel.addStyleClass("total"); + this.legendElement.appendChild(totalLegendLabel); + } + + this._drawSummaryGraph(fillSegments); + }, + + _drawSwatch: function(canvas, color) + { + var ctx = canvas.getContext("2d"); + + function drawSwatchSquare() { + ctx.fillStyle = color; + ctx.fillRect(0, 0, 13, 13); + + var gradient = ctx.createLinearGradient(0, 0, 13, 13); + gradient.addColorStop(0.0, "rgba(255, 255, 255, 0.2)"); + gradient.addColorStop(1.0, "rgba(255, 255, 255, 0.0)"); + + ctx.fillStyle = gradient; + ctx.fillRect(0, 0, 13, 13); + + gradient = ctx.createLinearGradient(13, 13, 0, 0); + gradient.addColorStop(0.0, "rgba(0, 0, 0, 0.2)"); + gradient.addColorStop(1.0, "rgba(0, 0, 0, 0.0)"); + + ctx.fillStyle = gradient; + ctx.fillRect(0, 0, 13, 13); + + ctx.strokeStyle = "rgba(0, 0, 0, 0.6)"; + ctx.strokeRect(0.5, 0.5, 12, 12); + } + + ctx.clearRect(0, 0, 13, 24); + + drawSwatchSquare(); + + ctx.save(); + + ctx.translate(0, 25); + ctx.scale(1, -1); + + drawSwatchSquare(); + + ctx.restore(); + + this._fadeOutRect(ctx, 0, 13, 13, 13, 0.5, 0.0); + }, + + _drawSummaryGraph: function(segments) + { + if (!segments || !segments.length) { + segments = [{color: "white", value: 1}]; + this._showingEmptySummaryGraph = true; + } else + delete this._showingEmptySummaryGraph; + + // Calculate the total of all segments. + var total = 0; + for (var i = 0; i < segments.length; ++i) + total += segments[i].value; + + // Calculate the percentage of each segment, rounded to the nearest percent. + var percents = segments.map(function(s) { return Math.max(Math.round(100 * s.value / total), 1) }); + + // Calculate the total percentage. + var percentTotal = 0; + for (var i = 0; i < percents.length; ++i) + percentTotal += percents[i]; + + // Make sure our percentage total is not greater-than 100, it can be greater + // if we rounded up for a few segments. + while (percentTotal > 100) { + for (var i = 0; i < percents.length && percentTotal > 100; ++i) { + if (percents[i] > 1) { + --percents[i]; + --percentTotal; + } + } + } + + // Make sure our percentage total is not less-than 100, it can be less + // if we rounded down for a few segments. + while (percentTotal < 100) { + for (var i = 0; i < percents.length && percentTotal < 100; ++i) { + ++percents[i]; + ++percentTotal; + } + } + + var ctx = this.graphElement.getContext("2d"); + + var x = 0; + var y = 0; + var w = 450; + var h = 19; + var r = (h / 2); + + function drawPillShadow() + { + // This draws a line with a shadow that is offset away from the line. The line is stroked + // twice with different X shadow offsets to give more feathered edges. Later we erase the + // line with destination-out 100% transparent black, leaving only the shadow. This only + // works if nothing has been drawn into the canvas yet. + + ctx.beginPath(); + ctx.moveTo(x + 4, y + h - 3 - 0.5); + ctx.lineTo(x + w - 4, y + h - 3 - 0.5); + ctx.closePath(); + + ctx.save(); + + ctx.shadowBlur = 2; + ctx.shadowColor = "rgba(0, 0, 0, 0.5)"; + ctx.shadowOffsetX = 3; + ctx.shadowOffsetY = 5; + + ctx.strokeStyle = "white"; + ctx.lineWidth = 1; + + ctx.stroke(); + + ctx.shadowOffsetX = -3; + + ctx.stroke(); + + ctx.restore(); + + ctx.save(); + + ctx.globalCompositeOperation = "destination-out"; + ctx.strokeStyle = "rgba(0, 0, 0, 1)"; + ctx.lineWidth = 1; + + ctx.stroke(); + + ctx.restore(); + } + + function drawPill() + { + // Make a rounded rect path. + ctx.beginPath(); + ctx.moveTo(x, y + r); + ctx.lineTo(x, y + h - r); + ctx.quadraticCurveTo(x, y + h, x + r, y + h); + ctx.lineTo(x + w - r, y + h); + ctx.quadraticCurveTo(x + w, y + h, x + w, y + h - r); + ctx.lineTo(x + w, y + r); + ctx.quadraticCurveTo(x + w, y, x + w - r, y); + ctx.lineTo(x + r, y); + ctx.quadraticCurveTo(x, y, x, y + r); + ctx.closePath(); + + // Clip to the rounded rect path. + ctx.save(); + ctx.clip(); + + // Fill the segments with the associated color. + var previousSegmentsWidth = 0; + for (var i = 0; i < segments.length; ++i) { + var segmentWidth = Math.round(w * percents[i] / 100); + ctx.fillStyle = segments[i].color; + ctx.fillRect(x + previousSegmentsWidth, y, segmentWidth, h); + previousSegmentsWidth += segmentWidth; + } + + // Draw the segment divider lines. + ctx.lineWidth = 1; + for (var i = 1; i < 20; ++i) { + ctx.beginPath(); + ctx.moveTo(x + (i * Math.round(w / 20)) + 0.5, y); + ctx.lineTo(x + (i * Math.round(w / 20)) + 0.5, y + h); + ctx.closePath(); + + ctx.strokeStyle = "rgba(0, 0, 0, 0.2)"; + ctx.stroke(); + + ctx.beginPath(); + ctx.moveTo(x + (i * Math.round(w / 20)) + 1.5, y); + ctx.lineTo(x + (i * Math.round(w / 20)) + 1.5, y + h); + ctx.closePath(); + + ctx.strokeStyle = "rgba(255, 255, 255, 0.2)"; + ctx.stroke(); + } + + // Draw the pill shading. + var lightGradient = ctx.createLinearGradient(x, y, x, y + (h / 1.5)); + lightGradient.addColorStop(0.0, "rgba(220, 220, 220, 0.6)"); + lightGradient.addColorStop(0.4, "rgba(220, 220, 220, 0.2)"); + lightGradient.addColorStop(1.0, "rgba(255, 255, 255, 0.0)"); + + var darkGradient = ctx.createLinearGradient(x, y + (h / 3), x, y + h); + darkGradient.addColorStop(0.0, "rgba(0, 0, 0, 0.0)"); + darkGradient.addColorStop(0.8, "rgba(0, 0, 0, 0.2)"); + darkGradient.addColorStop(1.0, "rgba(0, 0, 0, 0.5)"); + + ctx.fillStyle = darkGradient; + ctx.fillRect(x, y, w, h); + + ctx.fillStyle = lightGradient; + ctx.fillRect(x, y, w, h); + + ctx.restore(); + } + + ctx.clearRect(x, y, w, (h * 2)); + + drawPillShadow(); + drawPill(); + + ctx.save(); + + ctx.translate(0, (h * 2) + 1); + ctx.scale(1, -1); + + drawPill(); + + ctx.restore(); + + this._fadeOutRect(ctx, x, y + h + 1, w, h, 0.5, 0.0); + }, + + _fadeOutRect: function(ctx, x, y, w, h, a1, a2) + { + ctx.save(); + + var gradient = ctx.createLinearGradient(x, y, x, y + h); + gradient.addColorStop(0.0, "rgba(0, 0, 0, " + (1.0 - a1) + ")"); + gradient.addColorStop(0.8, "rgba(0, 0, 0, " + (1.0 - a2) + ")"); + gradient.addColorStop(1.0, "rgba(0, 0, 0, 1.0)"); + + ctx.globalCompositeOperation = "destination-out"; + + ctx.fillStyle = gradient; + ctx.fillRect(x, y, w, h); + + ctx.restore(); + }, + + _makeLegendElement: function(label, value, color) + { + var legendElement = document.createElement("label"); + legendElement.className = "summary-graph-legend-item"; + + if (color) { + var swatch = document.createElement("canvas"); + swatch.className = "summary-graph-legend-swatch"; + swatch.setAttribute("width", "13"); + swatch.setAttribute("height", "24"); + + legendElement.appendChild(swatch); + + this._drawSwatch(swatch, color); + } + + var labelElement = document.createElement("div"); + labelElement.className = "summary-graph-legend-label"; + legendElement.appendChild(labelElement); + + var headerElement = document.createElement("div"); + headerElement.className = "summary-graph-legend-header"; + headerElement.textContent = label; + labelElement.appendChild(headerElement); + + var valueElement = document.createElement("div"); + valueElement.className = "summary-graph-legend-value"; + valueElement.textContent = value; + labelElement.appendChild(valueElement); + + return legendElement; + } +} + +WebInspector.SummaryBar.prototype.__proto__ = WebInspector.Object.prototype; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/TextPrompt.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/TextPrompt.js new file mode 100644 index 0000000..5ff774f --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/TextPrompt.js @@ -0,0 +1,319 @@ +/* + * Copyright (C) 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.TextPrompt = function(element, completions, stopCharacters) +{ + this.element = element; + this.completions = completions; + this.completionStopCharacters = stopCharacters; + this.history = []; + this.historyOffset = 0; +} + +WebInspector.TextPrompt.prototype = { + get text() + { + return this.element.textContent; + }, + + set text(x) + { + if (!x) { + // Append a break element instead of setting textContent to make sure the selection is inside the prompt. + this.element.removeChildren(); + this.element.appendChild(document.createElement("br")); + } else + this.element.textContent = x; + + this.moveCaretToEndOfPrompt(); + }, + + handleKeyEvent: function(event) + { + switch (event.keyIdentifier) { + case "Up": + this._upKeyPressed(event); + break; + case "Down": + this._downKeyPressed(event); + break; + case "U+0009": // Tab + this._tabKeyPressed(event); + break; + case "Right": + case "End": + if (!this.acceptAutoComplete()) + this.autoCompleteSoon(); + break; + default: + this.clearAutoComplete(); + this.autoCompleteSoon(); + break; + } + }, + + acceptAutoComplete: function() + { + if (!this.autoCompleteElement || !this.autoCompleteElement.parentNode) + return false; + + var text = this.autoCompleteElement.textContent; + var textNode = document.createTextNode(text); + this.autoCompleteElement.parentNode.replaceChild(textNode, this.autoCompleteElement); + delete this.autoCompleteElement; + + var finalSelectionRange = document.createRange(); + finalSelectionRange.setStart(textNode, text.length); + finalSelectionRange.setEnd(textNode, text.length); + + var selection = window.getSelection(); + selection.removeAllRanges(); + selection.addRange(finalSelectionRange); + + return true; + }, + + clearAutoComplete: function(includeTimeout) + { + if (includeTimeout && "_completeTimeout" in this) { + clearTimeout(this._completeTimeout); + delete this._completeTimeout; + } + + if (!this.autoCompleteElement) + return; + + if (this.autoCompleteElement.parentNode) + this.autoCompleteElement.parentNode.removeChild(this.autoCompleteElement); + delete this.autoCompleteElement; + + if (!this._userEnteredRange || !this._userEnteredText) + return; + + this._userEnteredRange.deleteContents(); + + var userTextNode = document.createTextNode(this._userEnteredText); + this._userEnteredRange.insertNode(userTextNode); + + var selectionRange = document.createRange(); + selectionRange.setStart(userTextNode, this._userEnteredText.length); + selectionRange.setEnd(userTextNode, this._userEnteredText.length); + + var selection = window.getSelection(); + selection.removeAllRanges(); + selection.addRange(selectionRange); + + delete this._userEnteredRange; + delete this._userEnteredText; + }, + + autoCompleteSoon: function() + { + if (!("_completeTimeout" in this)) + this._completeTimeout = setTimeout(this.complete.bind(this, true), 250); + }, + + complete: function(auto) + { + this.clearAutoComplete(true); + var selection = window.getSelection(); + if (!selection.rangeCount) + return; + + var selectionRange = selection.getRangeAt(0); + if (!selectionRange.commonAncestorContainer.isDescendant(this.element)) + return; + if (auto && !this.isCaretAtEndOfPrompt()) + return; + var wordPrefixRange = selectionRange.startContainer.rangeOfWord(selectionRange.startOffset, this.completionStopCharacters, this.element, "backward"); + this.completions(wordPrefixRange, auto, this._completionsReady.bind(this, selection, auto, wordPrefixRange)); + }, + + _completionsReady: function(selection, auto, originalWordPrefixRange, completions) + { + if (!completions || !completions.length) + return; + + var selectionRange = selection.getRangeAt(0); + + var fullWordRange = document.createRange(); + fullWordRange.setStart(originalWordPrefixRange.startContainer, originalWordPrefixRange.startOffset); + fullWordRange.setEnd(selectionRange.endContainer, selectionRange.endOffset); + + if (originalWordPrefixRange.toString() + selectionRange.toString() != fullWordRange.toString()) + return; + + if (completions.length === 1 || selection.isCollapsed || auto) { + var completionText = completions[0]; + } else { + var currentText = fullWordRange.toString(); + + var foundIndex = null; + for (var i = 0; i < completions.length; ++i) { + if (completions[i] === currentText) + foundIndex = i; + } + + if (foundIndex === null || (foundIndex + 1) >= completions.length) + var completionText = completions[0]; + else + var completionText = completions[foundIndex + 1]; + } + + var wordPrefixLength = originalWordPrefixRange.toString().length; + + this._userEnteredRange = fullWordRange; + this._userEnteredText = fullWordRange.toString(); + + fullWordRange.deleteContents(); + + var finalSelectionRange = document.createRange(); + + if (auto) { + var prefixText = completionText.substring(0, wordPrefixLength); + var suffixText = completionText.substring(wordPrefixLength); + + var prefixTextNode = document.createTextNode(prefixText); + fullWordRange.insertNode(prefixTextNode); + + this.autoCompleteElement = document.createElement("span"); + this.autoCompleteElement.className = "auto-complete-text"; + this.autoCompleteElement.textContent = suffixText; + + prefixTextNode.parentNode.insertBefore(this.autoCompleteElement, prefixTextNode.nextSibling); + + finalSelectionRange.setStart(prefixTextNode, wordPrefixLength); + finalSelectionRange.setEnd(prefixTextNode, wordPrefixLength); + } else { + var completionTextNode = document.createTextNode(completionText); + fullWordRange.insertNode(completionTextNode); + + if (completions.length > 1) + finalSelectionRange.setStart(completionTextNode, wordPrefixLength); + else + finalSelectionRange.setStart(completionTextNode, completionText.length); + + finalSelectionRange.setEnd(completionTextNode, completionText.length); + } + + selection.removeAllRanges(); + selection.addRange(finalSelectionRange); + }, + + isCaretInsidePrompt: function() + { + return this.element.isInsertionCaretInside(); + }, + + isCaretAtEndOfPrompt: function() + { + var selection = window.getSelection(); + if (!selection.rangeCount || !selection.isCollapsed) + return false; + + var selectionRange = selection.getRangeAt(0); + var node = selectionRange.startContainer; + if (node !== this.element && !node.isDescendant(this.element)) + return false; + + if (node.nodeType === Node.TEXT_NODE && selectionRange.startOffset < node.nodeValue.length) + return false; + + var foundNextText = false; + while (node) { + if (node.nodeType === Node.TEXT_NODE && node.nodeValue.length) { + if (foundNextText) + return false; + foundNextText = true; + } + + node = node.traverseNextNode(false, this.element); + } + + return true; + }, + + moveCaretToEndOfPrompt: function() + { + var selection = window.getSelection(); + var selectionRange = document.createRange(); + + var offset = this.element.childNodes.length; + selectionRange.setStart(this.element, offset); + selectionRange.setEnd(this.element, offset); + + selection.removeAllRanges(); + selection.addRange(selectionRange); + }, + + _tabKeyPressed: function(event) + { + event.preventDefault(); + event.stopPropagation(); + + this.complete(); + }, + + _upKeyPressed: function(event) + { + event.preventDefault(); + event.stopPropagation(); + + if (this.historyOffset == this.history.length) + return; + + this.clearAutoComplete(true); + + if (this.historyOffset == 0) + this.tempSavedCommand = this.text; + + ++this.historyOffset; + this.text = this.history[this.history.length - this.historyOffset]; + }, + + _downKeyPressed: function(event) + { + event.preventDefault(); + event.stopPropagation(); + + if (this.historyOffset == 0) + return; + + this.clearAutoComplete(true); + + --this.historyOffset; + + if (this.historyOffset == 0) { + this.text = this.tempSavedCommand; + delete this.tempSavedCommand; + return; + } + + this.text = this.history[this.history.length - this.historyOffset]; + } +} diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/TimelineAgent.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/TimelineAgent.js new file mode 100644 index 0000000..6d18732 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/TimelineAgent.js @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2009 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.TimelineAgent = function() { + // Not implemented. +} + +// Must be kept in sync with TimelineItem.h +WebInspector.TimelineAgent.ItemType = { + DOMDispatch : 0, + Layout : 1, + RecalculateStyles : 2, + Paint : 3, + ParseHTML : 4 +}; + +WebInspector.addItemToTimeline = function(record) { + // Not implemented. +} + +WebInspector.timelineWasEnabled = function() { + // Not implemented. +} + +WebInspector.timelineWasDisabled = function() { + // Not implemented. +} diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/TopDownProfileDataGridTree.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/TopDownProfileDataGridTree.js new file mode 100644 index 0000000..b9d8b94 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/TopDownProfileDataGridTree.js @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2009 280 North Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.TopDownProfileDataGridNode = function(/*ProfileView*/ profileView, /*ProfileNode*/ profileNode, /*TopDownProfileDataGridTree*/ owningTree) +{ + var hasChildren = (profileNode.children && profileNode.children.length); + + WebInspector.ProfileDataGridNode.call(this, profileView, profileNode, owningTree, hasChildren); + + this._remainingChildren = profileNode.children; +} + +WebInspector.TopDownProfileDataGridNode.prototype = { + _populate: function(event) + { + var children = this._remainingChildren; + var childrenLength = children.length; + + for (var i = 0; i < childrenLength; ++i) + this.appendChild(new WebInspector.TopDownProfileDataGridNode(this.profileView, children[i], this.tree)); + + if (this.removeEventListener) + this.removeEventListener("populate", this._populate, this); + + this._remainingChildren = null; + }, + + _exclude: function(aCallUID) + { + if (this._remainingChildren) + this._populate(); + + this._save(); + + var children = this.children; + var index = this.children.length; + + while (index--) + children[index]._exclude(aCallUID); + + var child = this.childrenByCallUID[aCallUID]; + + if (child) + this._merge(child, true); + } +} + +WebInspector.TopDownProfileDataGridNode.prototype.__proto__ = WebInspector.ProfileDataGridNode.prototype; + +WebInspector.TopDownProfileDataGridTree = function(/*ProfileView*/ profileView, /*ProfileNode*/ profileNode) +{ + WebInspector.ProfileDataGridTree.call(this, profileView, profileNode); + + this._remainingChildren = profileNode.children; + + WebInspector.TopDownProfileDataGridNode.prototype._populate.call(this); +} + +WebInspector.TopDownProfileDataGridTree.prototype = { + focus: function(/*ProfileDataGridNode*/ profileDataGrideNode) + { + if (!profileDataGrideNode) + return; + + this._save(); + + this.children = [profileDataGrideNode]; + this.totalTime = profileDataGrideNode.totalTime; + }, + + exclude: function(/*ProfileDataGridNode*/ profileDataGrideNode) + { + if (!profileDataGrideNode) + return; + + this._save(); + + var excludedCallUID = profileDataGrideNode.callUID; + + WebInspector.TopDownProfileDataGridNode.prototype._exclude.call(this, excludedCallUID); + + if (this.lastComparator) + this.sort(this.lastComparator, true); + }, + + _merge: WebInspector.TopDownProfileDataGridNode.prototype._merge +} + +WebInspector.TopDownProfileDataGridTree.prototype.__proto__ = WebInspector.ProfileDataGridTree.prototype; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/View.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/View.js new file mode 100644 index 0000000..632a61ae --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/View.js @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.View = function(element) +{ + this.element = element || document.createElement("div"); + this._visible = false; +} + +WebInspector.View.prototype = { + get visible() + { + return this._visible; + }, + + set visible(x) + { + if (this._visible === x) + return; + + if (x) + this.show(); + else + this.hide(); + }, + + show: function(parentElement) + { + this._visible = true; + if (parentElement && parentElement !== this.element.parentNode) { + this.detach(); + parentElement.appendChild(this.element); + } + if (!this.element.parentNode && this.attach) + this.attach(); + this.element.addStyleClass("visible"); + }, + + hide: function() + { + this.element.removeStyleClass("visible"); + this._visible = false; + }, + + detach: function() + { + if (this.element.parentNode) + this.element.parentNode.removeChild(this.element); + } +} + +WebInspector.View.prototype.__proto__ = WebInspector.Object.prototype; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/WatchExpressionsSidebarPane.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/WatchExpressionsSidebarPane.js new file mode 100644 index 0000000..b568939 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/WatchExpressionsSidebarPane.js @@ -0,0 +1,274 @@ +/* + * Copyright (C) IBM Corp. 2009 All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of IBM Corp. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.WatchExpressionsSidebarPane = function() +{ + WebInspector.SidebarPane.call(this, WebInspector.UIString("Watch Expressions")); + + this.section = new WebInspector.WatchExpressionsSection(); + + this.bodyElement.appendChild(this.section.element); + + var addElement = document.createElement("button"); + addElement.setAttribute("type", "button"); + addElement.textContent = WebInspector.UIString("Add"); + addElement.addEventListener("click", this.section.addExpression.bind(this.section), false); + + var refreshElement = document.createElement("button"); + refreshElement.setAttribute("type", "button"); + refreshElement.textContent = WebInspector.UIString("Refresh"); + refreshElement.addEventListener("click", this.section.update.bind(this.section), false); + + var centerElement = document.createElement("div"); + centerElement.addStyleClass("watch-expressions-buttons-container"); + centerElement.appendChild(addElement); + centerElement.appendChild(refreshElement); + this.bodyElement.appendChild(centerElement); + + this.expanded = this.section.loadSavedExpressions().length > 0; + this.onexpand = this.refreshExpressions.bind(this); +} + +WebInspector.WatchExpressionsSidebarPane.prototype = { + refreshExpressions: function() + { + this.section.update(); + } +} + +WebInspector.WatchExpressionsSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype; + +WebInspector.WatchExpressionsSection = function() +{ + WebInspector.ObjectPropertiesSection.call(this); + + this.watchExpressions = this.loadSavedExpressions(); + + this.headerElement.className = "hidden"; + this.editable = true; + this.expanded = true; + this.propertiesElement.addStyleClass("watch-expressions"); +} + +WebInspector.WatchExpressionsSection.NewWatchExpression = "\xA0"; + +WebInspector.WatchExpressionsSection.prototype = { + update: function() + { + function appendResult(expression, watchIndex, result, exception) + { + // The null check catches some other cases, like null itself, and NaN + if ((typeof result !== "object") || (result == null)) + result = new WebInspector.ObjectProxy(null, [], 0, String(result), false); + + var property = new WebInspector.ObjectPropertyProxy(expression, result); + property.watchIndex = watchIndex; + + // For newly added, empty expressions, set description to "", + // since otherwise you get DOMWindow + if (property.name === WebInspector.WatchExpressionsSection.NewWatchExpression) + property.value.description = ""; + + // To clarify what's going on here: + // In the outer function, we calculate the number of properties + // that we're going to be updating, and set that in the + // propertyCount variable. + // In this function, we test to see when we are processing the + // last property, and then call the superclass's updateProperties() + // method to get all the properties refreshed at once. + properties.push(property); + + if (properties.length == propertyCount) + this.updateProperties(properties, WebInspector.WatchExpressionTreeElement, WebInspector.WatchExpressionsSection.CompareProperties); + } + + var properties = []; + + // Count the properties, so we known when to call this.updateProperties() + // in appendResult() + var propertyCount = 0; + for (var i = 0; i < this.watchExpressions.length; ++i) { + if (!this.watchExpressions[i]) + continue; + ++propertyCount; + } + + // Now process all the expressions, since we have the actual count, + // which is checked in the appendResult inner function. + for (var i = 0; i < this.watchExpressions.length; ++i) { + var expression = this.watchExpressions[i]; + if (!expression) + continue; + + WebInspector.console.evalInInspectedWindow("(" + expression + ")", appendResult.bind(this, expression, i)); + } + + // note this is setting the expansion of the tree, not the section; + // with no expressions, and expanded tree, we get some extra vertical + // white space + // FIXME: should change to use header buttons instead of the buttons + // at the bottom of the section, then we can add a "No Watch Expressions + // element when there are no watch expressions, and this issue should + // go away. + this.expanded = (propertyCount != 0); + }, + + addExpression: function() + { + this.watchExpressions.push(WebInspector.WatchExpressionsSection.NewWatchExpression); + this.update(); + + // After update(), the new empty expression to be edited + // will be in the tree, but we have to find it. + treeElement = this.findAddedTreeElement(); + if (treeElement) + treeElement.startEditing(); + }, + + updateExpression: function(element, value) + { + this.watchExpressions[element.property.watchIndex] = value; + this.saveExpressions(); + this.update(); + }, + + findAddedTreeElement: function() + { + var children = this.propertiesTreeOutline.children; + for (var i = 0; i < children.length; ++i) + if (children[i].property.name === WebInspector.WatchExpressionsSection.NewWatchExpression) + return children[i]; + }, + + loadSavedExpressions: function() + { + var json = InspectorController.setting("watchExpressions"); + if (!json) + return []; + + try { + json = JSON.parse(json); + } catch(e) { + return []; + } + + return json.expressions || []; + }, + + saveExpressions: function() + { + var toSave = []; + for (var i = 0; i < this.watchExpressions.length; i++) + if (this.watchExpressions[i]) + toSave.push(this.watchExpressions[i]); + + var json = JSON.stringify({expressions: toSave}); + InspectorController.setSetting("watchExpressions", json); + + return toSave.length; + } +} + +WebInspector.WatchExpressionsSection.prototype.__proto__ = WebInspector.ObjectPropertiesSection.prototype; + +WebInspector.WatchExpressionsSection.CompareProperties = function(propertyA, propertyB) +{ + if (propertyA.watchIndex == propertyB.watchIndex) + return 0; + else if (propertyA.watchIndex < propertyB.watchIndex) + return -1; + else + return 1; +} + +WebInspector.WatchExpressionTreeElement = function(property) +{ + WebInspector.ObjectPropertyTreeElement.call(this, property); +} + +WebInspector.WatchExpressionTreeElement.prototype = { + update: function() + { + WebInspector.ObjectPropertyTreeElement.prototype.update.call(this); + + var deleteButton = document.createElement("input"); + deleteButton.type = "button"; + deleteButton.title = WebInspector.UIString("Delete watch expression."); + deleteButton.addStyleClass("enabled-button"); + deleteButton.addStyleClass("delete-button"); + deleteButton.addEventListener("click", this._deleteButtonClicked.bind(this), false); + + this.listItemElement.insertBefore(deleteButton, this.listItemElement.firstChild); + }, + + _deleteButtonClicked: function() + { + this.treeOutline.section.updateExpression(this, null); + }, + + startEditing: function() + { + if (WebInspector.isBeingEdited(this.nameElement) || !this.treeOutline.section.editable) + return; + + this.nameElement.textContent = this.property.name.trimWhitespace(); + + var context = { expanded: this.expanded }; + + // collapse temporarily, if required + this.hasChildren = false; + + this.listItemElement.addStyleClass("editing-sub-part"); + + WebInspector.startEditing(this.nameElement, this.editingCommitted.bind(this), this.editingCancelled.bind(this), context); + }, + + editingCancelled: function(element, context) + { + if (!this.nameElement.textContent) + this.treeOutline.section.updateExpression(this, null); + + this.update(); + this.editingEnded(context); + }, + + applyExpression: function(expression, updateInterface) + { + expression = expression.trimWhitespace(); + + if (!expression) + expression = null; + + this.property.name = expression; + this.treeOutline.section.updateExpression(this, expression); + } +} + +WebInspector.WatchExpressionTreeElement.prototype.__proto__ = WebInspector.ObjectPropertyTreeElement.prototype; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/base.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/base.js new file mode 100644 index 0000000..1a76aee --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/base.js @@ -0,0 +1,1015 @@ +// Copyright 2006 Google Inc. +// All Rights Reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in +// the documentation and/or other materials provided with the +// distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +// NOTE: This file has been changed from the one on doctype. The following +// changes were made: +// - Removed goog.globalEval because it calls eval() which is not allowed from +// inside v8 extensions. If we ever need to use globalEval, we will need to +// find a way to work around this problem. +// - Remove Function.prototype.apply() emulation for the same reason. This one +// is useless anyway because V8 supports apply() natively. + +/** + * @fileoverview Bootstrap for the Google JS Library + */ + +/** + * @define {boolean} Overridden to true by the compiler when + * --mark_as_compiled is specified. + */ +var COMPILED = true; + + +/** + * Base namespace for the Google JS library. Checks to see goog is + * already defined in the current scope before assigning to prevent + * clobbering if base.js is loaded more than once. + */ +var goog = {}; // Check to see if already defined in current scope + + +/** + * Reference to the global context. In most cases this will be 'window'. + */ +goog.global = this; + + +/** + * Indicates whether or not we can call 'eval' directly to eval code in the + * global scope. Set to a Boolean by the first call to goog.globalEval (which + * empirically tests whether eval works for globals). @see goog.globalEval + * @type {boolean?} + * @private + */ +goog.evalWorksForGlobals_ = null; + + +/** + * Creates object stubs for a namespace. When present in a file, goog.provide + * also indicates that the file defines the indicated object. + * @param {string} name name of the object that this file defines. + */ +goog.provide = function(name) { + if (!COMPILED) { + // Ensure that the same namespace isn't provided twice. This is intended + // to teach new developers that 'goog.provide' is effectively a variable + // declaration. And when JSCompiler transforms goog.provide into a real + // variable declaration, the compiled JS should work the same as the raw + // JS--even when the raw JS uses goog.provide incorrectly. + if (goog.getObjectByName(name) && !goog.implicitNamespaces_[name]) { + throw 'Namespace "' + name + '" already declared.'; + } + + var namespace = name; + while ((namespace = namespace.substr(0, namespace.lastIndexOf('.')))) { + goog.implicitNamespaces_[namespace] = true; + } + } + + goog.exportPath_(name); +}; + + +if (!COMPILED) { + /** + * Namespaces implicitly defined by goog.provide. For example, + * goog.provide('goog.events.Event') implicitly declares + * that 'goog' and 'goog.events' must be namespaces. + * + * @type {Object} + * @private + */ + goog.implicitNamespaces_ = {}; +} + + +/** + * 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={}; + * Used by goog.provide and goog.exportSymbol. + * @param {string} name name of the object that this file defines. + * @param {Object} opt_object the object to expose at the end of the path. + * @private + */ +goog.exportPath_ = function(name, opt_object) { + var parts = name.split('.'); + var cur = goog.global; + var part; + + // Internet Explorer exhibits strange behavior when throwing errors from + // methods externed in this manner. See the testExportSymbolExceptions in + // base_test.html for an example. + if (!(parts[0] in cur) && cur.execScript) { + cur.execScript('var ' + parts[0]); + } + + // Parentheses added to eliminate strict JS warning in Firefox. + while ((part = parts.shift())) { + if (!parts.length && goog.isDef(opt_object)) { + // last part and we have an object; use it + cur[part] = opt_object; + } else if (cur[part]) { + cur = cur[part]; + } else { + cur = cur[part] = {}; + } + } +}; + + +/** + * Returns an object based on its fully qualified name + * @param {string} name The fully qualified name. + * @return {Object?} The object or, if not found, null. + */ +goog.getObjectByName = function(name) { + var parts = name.split('.'); + var cur = goog.global; + for (var part; part = parts.shift(); ) { + if (cur[part]) { + cur = cur[part]; + } else { + return null; + } + } + return cur; +}; + + +/** + * Globalizes a whole namespace, such as goog or goog.lang. + * + * @param {Object} obj The namespace to globalize. + * @param {Object} opt_global The object to add the properties to. + * @deprecated Properties may be explicitly exported to the global scope, but + * this should no longer be done in bulk. + */ +goog.globalize = function(obj, opt_global) { + var global = opt_global || goog.global; + for (var x in obj) { + global[x] = obj[x]; + } +}; + + +/** + * Adds a dependency from a file to the files it requires. + * @param {string} relPath The path to the js file. + * @param {Array} provides An array of strings with the names of the objects + * this file provides. + * @param {Array} requires An array of strings with the names of the objects + * this file requires. + */ +goog.addDependency = function(relPath, provides, requires) { + if (!COMPILED) { + var provide, require; + var path = relPath.replace(/\\/g, '/'); + var deps = goog.dependencies_; + for (var i = 0; provide = provides[i]; i++) { + deps.nameToPath[provide] = path; + if (!(path in deps.pathToNames)) { + deps.pathToNames[path] = {}; + } + deps.pathToNames[path][provide] = true; + } + for (var j = 0; require = requires[j]; j++) { + if (!(path in deps.requires)) { + deps.requires[path] = {}; + } + deps.requires[path][require] = true; + } + } +}; + + +/** + * Implements a system for the dynamic resolution of dependencies + * that works in parallel with the BUILD system. + * @param {string} rule Rule to include, in the form goog.package.part. + */ +goog.require = function(rule) { + + // if the object already exists we do not need do do anything + if (!COMPILED) { + if (goog.getObjectByName(rule)) { + return; + } + var path = goog.getPathFromDeps_(rule); + if (path) { + goog.included_[path] = true; + goog.writeScripts_(); + } else { + // NOTE(nicksantos): We could throw an error, but this would break + // legacy users that depended on this failing silently. Instead, the + // compiler should warn us when there are invalid goog.require calls. + } + } +}; + + +/** + * Path for included scripts + * @type {string} + */ +goog.basePath = ''; + + +/** + * Null function used for default values of callbacks, etc. + * @type {Function} + */ +goog.nullFunction = function() {}; + + +/** + * When defining a class Foo with an abstract method bar(), you can do: + * + * Foo.prototype.bar = goog.abstractMethod + * + * Now if a subclass of Foo fails to override bar(), an error + * will be thrown when bar() is invoked. + * + * Note: This does not take the name of the function to override as + * an argument because that would make it more difficult to obfuscate + * our JavaScript code. + * + * @throws {Error} when invoked to indicate the method should be + * overridden. + */ +goog.abstractMethod = function() { + throw Error('unimplemented abstract method'); +}; + + +if (!COMPILED) { + /** + * Object used to keep track of urls that have already been added. This + * record allows the prevention of circular dependencies. + * @type {Object} + * @private + */ + goog.included_ = {}; + + + /** + * This object is used to keep track of dependencies and other data that is + * used for loading scripts + * @private + * @type {Object} + */ + goog.dependencies_ = { + pathToNames: {}, // 1 to many + nameToPath: {}, // 1 to 1 + requires: {}, // 1 to many + visited: {}, // used when resolving dependencies to prevent us from + // visiting the file twice + written: {} // used to keep track of script files we have written + }; + + + /** + * Tries to detect the base path of the base.js script that bootstraps + * Google JS Library + * @private + */ + goog.findBasePath_ = function() { + var doc = goog.global.document; + if (typeof doc == 'undefined') { + return; + } + if (goog.global.GOOG_BASE_PATH) { + goog.basePath = goog.global.GOOG_BASE_PATH; + return; + } else { + goog.global.GOOG_BASE_PATH = null; + } + var scripts = doc.getElementsByTagName('script'); + for (var script, i = 0; script = scripts[i]; i++) { + var src = script.src; + var l = src.length; + if (src.substr(l - 7) == 'base.js') { + goog.basePath = src.substr(0, l - 7); + return; + } + } + }; + + + /** + * Writes a script tag if, and only if, that script hasn't already been added + * to the document. (Must be called at execution time) + * @param {string} src Script source. + * @private + */ + goog.writeScriptTag_ = function(src) { + var doc = goog.global.document; + if (typeof doc != 'undefined' && + !goog.dependencies_.written[src]) { + goog.dependencies_.written[src] = true; + doc.write('<script type="text/javascript" src="' + + src + '"></' + 'script>'); + } + }; + + + /** + * Resolves dependencies based on the dependencies added using addDependency + * and calls writeScriptTag_ in the correct order. + * @private + */ + goog.writeScripts_ = function() { + // the scripts we need to write this time + var scripts = []; + var seenScript = {}; + var deps = goog.dependencies_; + + function visitNode(path) { + if (path in deps.written) { + return; + } + + // we have already visited this one. We can get here if we have cyclic + // dependencies + if (path in deps.visited) { + if (!(path in seenScript)) { + seenScript[path] = true; + scripts.push(path); + } + return; + } + + deps.visited[path] = true; + + if (path in deps.requires) { + for (var requireName in deps.requires[path]) { + visitNode(deps.nameToPath[requireName]); + } + } + + if (!(path in seenScript)) { + seenScript[path] = true; + scripts.push(path); + } + } + + for (var path in goog.included_) { + if (!deps.written[path]) { + visitNode(path); + } + } + + for (var i = 0; i < scripts.length; i++) { + if (scripts[i]) { + goog.writeScriptTag_(goog.basePath + scripts[i]); + } else { + throw Error('Undefined script input'); + } + } + }; + + + /** + * Looks at the dependency rules and tries to determine the script file that + * fulfills a particular rule. + * @param {string} rule In the form goog.namespace.Class or project.script. + * @return {string?} Url corresponding to the rule, or null. + * @private + */ + goog.getPathFromDeps_ = function(rule) { + if (rule in goog.dependencies_.nameToPath) { + return goog.dependencies_.nameToPath[rule]; + } else { + return null; + } + }; + + goog.findBasePath_(); + goog.writeScriptTag_(goog.basePath + 'deps.js'); +} + + + +//============================================================================== +// Language Enhancements +//============================================================================== + + +/** + * This is a "fixed" version of the typeof operator. It differs from the typeof + * operator in such a way that null returns 'null' and arrays return 'array'. + * @param {*} value The value to get the type of. + * @return {string} The name of the type. + */ +goog.typeOf = function(value) { + var s = typeof value; + if (s == 'object') { + if (value) { + // We cannot use constructor == Array or instanceof Array because + // different frames have different Array objects. In IE6, if the iframe + // where the array was created is destroyed, the array loses its + // prototype. Then dereferencing val.splice here throws an exception, so + // we can't use goog.isFunction. Calling typeof directly returns 'unknown' + // so that will work. In this case, this function will return false and + // most array functions will still work because the array is still + // array-like (supports length and []) even though it has lost its + // prototype. Custom object cannot have non enumerable length and + // NodeLists don't have a slice method. + if (typeof value.length == 'number' && + typeof value.splice != 'undefined' && + !goog.propertyIsEnumerable_(value, 'length')) { + return 'array'; + } + + // IE in cross-window calls does not correctly marshal the function type + // (it appears just as an object) so we cannot use just typeof val == + // 'function'. However, if the object has a call property, it is a + // function. + if (typeof value.call != 'undefined') { + return 'function'; + } + } else { + return 'null'; + } + + // In Safari typeof nodeList returns function. We would like to return + // object for those and we can detect an invalid function by making sure that + // the function object has a call method + } else if (s == 'function' && typeof value.call == 'undefined') { + return 'object'; + } + return s; +}; + +if (Object.prototype.propertyIsEnumerable) { + /** + * Safe way to test whether a property is enumarable. It allows testing + * for enumarable on objects where 'propertyIsEnumerable' is overridden or + * does not exist (like DOM nodes in IE). + * @param {Object} object The object to test if the property is enumerable. + * @param {string} propName The property name to check for. + * @return {boolean} True if the property is enumarable. + * @private + */ + goog.propertyIsEnumerable_ = function(object, propName) { + return Object.prototype.propertyIsEnumerable.call(object, propName); + }; +} else { + /** + * Safe way to test whether a property is enumarable. It allows testing + * for enumarable on objects where 'propertyIsEnumerable' is overridden or + * does not exist (like DOM nodes in IE). + * @param {Object} object The object to test if the property is enumerable. + * @param {string} propName The property name to check for. + * @return {boolean} True if the property is enumarable. + * @private + */ + goog.propertyIsEnumerable_ = function(object, propName) { + // KJS in Safari 2 is not ECMAScript compatible and lacks crucial methods + // such as propertyIsEnumerable. We therefore use a workaround. + // Does anyone know a more efficient work around? + if (propName in object) { + for (var key in object) { + if (key == propName) { + return true; + } + } + } + return false; + }; +} + +/** + * Returns true if the specified value is not |undefined|. + * WARNING: Do not use this to test if an object has a property. Use the in + * operator instead. + * @param {*} val Variable to test. + * @return {boolean} Whether variable is defined. + */ +goog.isDef = function(val) { + return typeof val != 'undefined'; +}; + + +/** + * Returns true if the specified value is |null| + * @param {*} val Variable to test. + * @return {boolean} Whether variable is null. + */ +goog.isNull = function(val) { + return val === null; +}; + + +/** + * Returns true if the specified value is defined and not null + * @param {*} val Variable to test. + * @return {boolean} Whether variable is defined and not null. + */ +goog.isDefAndNotNull = function(val) { + return goog.isDef(val) && !goog.isNull(val); +}; + + +/** + * Returns true if the specified value is an array + * @param {*} val Variable to test. + * @return {boolean} Whether variable is an array. + */ +goog.isArray = function(val) { + return goog.typeOf(val) == 'array'; +}; + + +/** + * Returns true if the object looks like an array. To qualify as array like + * the value needs to be either a NodeList or an object with a Number length + * property. + * @param {*} val Variable to test. + * @return {boolean} Whether variable is an array. + */ +goog.isArrayLike = function(val) { + var type = goog.typeOf(val); + return type == 'array' || type == 'object' && typeof val.length == 'number'; +}; + + +/** + * Returns true if the object looks like a Date. To qualify as Date-like + * the value needs to be an object and have a getFullYear() function. + * @param {*} val Variable to test. + * @return {boolean} Whether variable is a like a Date. + */ +goog.isDateLike = function(val) { + return goog.isObject(val) && typeof val.getFullYear == 'function'; +}; + + +/** + * Returns true if the specified value is a string + * @param {*} val Variable to test. + * @return {boolean} Whether variable is a string. + */ +goog.isString = function(val) { + return typeof val == 'string'; +}; + + +/** + * Returns true if the specified value is a boolean + * @param {*} val Variable to test. + * @return {boolean} Whether variable is boolean. + */ +goog.isBoolean = function(val) { + return typeof val == 'boolean'; +}; + + +/** + * Returns true if the specified value is a number + * @param {*} val Variable to test. + * @return {boolean} Whether variable is a number. + */ +goog.isNumber = function(val) { + return typeof val == 'number'; +}; + + +/** + * Returns true if the specified value is a function + * @param {*} val Variable to test. + * @return {boolean} Whether variable is a function. + */ +goog.isFunction = function(val) { + return goog.typeOf(val) == 'function'; +}; + + +/** + * Returns true if the specified value is an object. This includes arrays + * and functions. + * @param {*} val Variable to test. + * @return {boolean} Whether variable is an object. + */ +goog.isObject = function(val) { + var type = goog.typeOf(val); + return type == 'object' || type == 'array' || type == 'function'; +}; + + +/** + * Adds a hash code field to an object. The hash code is unique for the + * given object. + * @param {Object} obj The object to get the hash code for. + * @return {number} The hash code for the object. + */ +goog.getHashCode = function(obj) { + // In IE, DOM nodes do not extend Object so they do not have this method. + // we need to check hasOwnProperty because the proto might have this set. + + if (obj.hasOwnProperty && obj.hasOwnProperty(goog.HASH_CODE_PROPERTY_)) { + return obj[goog.HASH_CODE_PROPERTY_]; + } + if (!obj[goog.HASH_CODE_PROPERTY_]) { + obj[goog.HASH_CODE_PROPERTY_] = ++goog.hashCodeCounter_; + } + return obj[goog.HASH_CODE_PROPERTY_]; +}; + + +/** + * Removes the hash code field from an object. + * @param {Object} obj The object to remove the field from. + */ +goog.removeHashCode = function(obj) { + // DOM nodes in IE are not instance of Object and throws exception + // for delete. Instead we try to use removeAttribute + if ('removeAttribute' in obj) { + obj.removeAttribute(goog.HASH_CODE_PROPERTY_); + } + /** @preserveTry */ + try { + delete obj[goog.HASH_CODE_PROPERTY_]; + } catch (ex) { + } +}; + + +/** + * {String} Name for hash code property + * @private + */ +goog.HASH_CODE_PROPERTY_ = 'goog_hashCode_'; + + +/** + * @type {number} Counter for hash codes. + * @private + */ +goog.hashCodeCounter_ = 0; + + +/** + * Clone an object/array (recursively) + * @param {Object} proto Object to clone. + * @return {Object} Clone of x;. + */ +goog.cloneObject = function(proto) { + var type = goog.typeOf(proto); + if (type == 'object' || type == 'array') { + if (proto.clone) { + return proto.clone(); + } + var clone = type == 'array' ? [] : {}; + for (var key in proto) { + clone[key] = goog.cloneObject(proto[key]); + } + return clone; + } + + return proto; +}; + + +/** + * Partially applies this function to a particular 'this object' and zero or + * more arguments. The result is a new function with some arguments of the first + * function pre-filled and the value of |this| 'pre-specified'.<br><br> + * + * Remaining arguments specified at call-time are appended to the pre- + * specified ones.<br><br> + * + * Also see: {@link #partial}.<br><br> + * + * Note that bind and partial are optimized such that repeated calls to it do + * not create more than one function object, so there is no additional cost for + * something like:<br> + * + * <pre>var g = bind(f, obj); + * var h = partial(g, 1, 2, 3); + * var k = partial(h, a, b, c);</pre> + * + * Usage: + * <pre>var barMethBound = bind(myFunction, myObj, 'arg1', 'arg2'); + * barMethBound('arg3', 'arg4');</pre> + * + * @param {Function} fn A function to partially apply. + * @param {Object} self Specifies the object which |this| should point to + * when the function is run. If the value is null or undefined, it will + * default to the global object. + * @param {Object} var_args Additional arguments that are partially + * applied to the function. + * + * @return {Function} A partially-applied form of the function bind() was + * invoked as a method of. + */ +goog.bind = function(fn, self, var_args) { + var boundArgs = fn.boundArgs_; + + if (arguments.length > 2) { + var args = Array.prototype.slice.call(arguments, 2); + if (boundArgs) { + args.unshift.apply(args, boundArgs); + } + boundArgs = args; + } + + self = fn.boundSelf_ || self; + fn = fn.boundFn_ || fn; + + var newfn; + var context = self || goog.global; + + if (boundArgs) { + newfn = function() { + // Combine the static args and the new args into one big array + var args = Array.prototype.slice.call(arguments); + args.unshift.apply(args, boundArgs); + return fn.apply(context, args); + } + } else { + newfn = function() { + return fn.apply(context, arguments); + } + } + + newfn.boundArgs_ = boundArgs; + newfn.boundSelf_ = self; + newfn.boundFn_ = fn; + + return newfn; +}; + + +/** + * Like bind(), except that a 'this object' is not required. Useful when the + * target function is already bound. + * + * Usage: + * var g = partial(f, arg1, arg2); + * g(arg3, arg4); + * + * @param {Function} fn A function to partially apply. + * @param {Object} var_args Additional arguments that are partially + * applied to fn. + * @return {Function} A partially-applied form of the function bind() was + * invoked as a method of. + */ +goog.partial = function(fn, var_args) { + var args = Array.prototype.slice.call(arguments, 1); + args.unshift(fn, null); + return goog.bind.apply(null, args); +}; + + +/** + * Copies all the members of a source object to a target object. + * This is deprecated. Use goog.object.extend instead. + * @param {Object} target Target. + * @param {Object} source Source. + * @deprecated + */ +goog.mixin = function(target, source) { + for (var x in source) { + target[x] = source[x]; + } + + // For IE the for-in-loop does not contain any properties that are not + // enumerable on the prototype object (for example, isPrototypeOf from + // Object.prototype) but also it will not include 'replace' on objects that + // extend String and change 'replace' (not that it is common for anyone to + // extend anything except Object). +}; + + +/** + * A simple wrapper for new Date().getTime(). + * + * @return {number} An integer value representing the number of milliseconds + * between midnight, January 1, 1970 and the current time. + */ +goog.now = Date.now || (function() { + return new Date().getTime(); +}); + + +/** + * Abstract implementation of goog.getMsg for use with localized messages + * @param {string} str Translatable string, places holders in the form.{$foo} + * @param {Object} opt_values Map of place holder name to value. + */ +goog.getMsg = function(str, opt_values) { + var values = opt_values || {}; + for (var key in values) { + str = str.replace(new RegExp('\\{\\$' + key + '\\}', 'gi'), values[key]); + } + return str; +}; + + +/** + * Exposes an unobfuscated global namespace path for the given object. + * Note that fields of the exported object *will* be obfuscated, + * unless they are exported in turn via this function or + * goog.exportProperty + * + * <p>Also handy for making public items that are defined in anonymous + * closures. + * + * ex. goog.exportSymbol('Foo', Foo); + * + * ex. goog.exportSymbol('public.path.Foo.staticFunction', + * Foo.staticFunction); + * public.path.Foo.staticFunction(); + * + * ex. goog.exportSymbol('public.path.Foo.prototype.myMethod', + * Foo.prototype.myMethod); + * new public.path.Foo().myMethod(); + * + * @param {string} publicPath Unobfuscated name to export. + * @param {Object} object Object the name should point to. + */ +goog.exportSymbol = function(publicPath, object) { + goog.exportPath_(publicPath, object); +}; + + +/** + * Exports a property unobfuscated into the object's namespace. + * ex. goog.exportProperty(Foo, 'staticFunction', Foo.staticFunction); + * ex. goog.exportProperty(Foo.prototype, 'myMethod', Foo.prototype.myMethod); + * @param {Object} object Object whose static property is being exported. + * @param {string} publicName Unobfuscated name to export. + * @param {Object} symbol Object the name should point to. + */ +goog.exportProperty = function(object, publicName, symbol) { + object[publicName] = symbol; +}; + + + +//============================================================================== +// Extending Function +//============================================================================== + + +/** + * An alias to the {@link goog.bind()} global function. + * + * Usage: + * var g = f.bind(obj, arg1, arg2); + * g(arg3, arg4); + * + * @param {Object} self Specifies the object to which |this| should point + * when the function is run. If the value is null or undefined, it will + * default to the global object. + * @param {Object} var_args Additional arguments that are partially + * applied to fn. + * @return {Function} A partially-applied form of the Function on which bind() + * was invoked as a method. + * @deprecated + */ +Function.prototype.bind = function(self, var_args) { + if (arguments.length > 1) { + var args = Array.prototype.slice.call(arguments, 1); + args.unshift(this, self); + return goog.bind.apply(null, args); + } else { + return goog.bind(this, self); + } +}; + + +/** + * An alias to the {@link goog.partial()} global function. + * + * Usage: + * var g = f.partial(arg1, arg2); + * g(arg3, arg4); + * + * @param {Object} var_args Additional arguments that are partially + * applied to fn. + * @return {Function} A partially-applied form of the function partial() was + * invoked as a method of. + * @deprecated + */ +Function.prototype.partial = function(var_args) { + var args = Array.prototype.slice.call(arguments); + args.unshift(this, null); + return goog.bind.apply(null, args); +}; + + +/** + * Inherit the prototype methods from one constructor into another. + * + * Usage: + * <pre> + * function ParentClass(a, b) { } + * ParentClass.prototype.foo = function(a) { } + * + * function ChildClass(a, b, c) { + * ParentClass.call(this, a, b); + * } + * + * ChildClass.inherits(ParentClass); + * + * var child = new ChildClass('a', 'b', 'see'); + * child.foo(); // works + * </pre> + * + * In addition, a superclass' implementation of a method can be invoked + * as follows: + * + * <pre> + * ChildClass.prototype.foo = function(a) { + * ChildClass.superClass_.foo.call(this, a); + * // other code + * }; + * </pre> + * + * @param {Function} parentCtor Parent class. + */ +Function.prototype.inherits = function(parentCtor) { + goog.inherits(this, parentCtor); +}; + + +/** + * Static variant of Function.prototype.inherits. + * @param {Function} childCtor Child class. + * @param {Function} parentCtor Parent class. + */ +goog.inherits = function(childCtor, parentCtor) { + /** @constructor */ + function tempCtor() {}; + tempCtor.prototype = parentCtor.prototype; + childCtor.superClass_ = parentCtor.prototype; + childCtor.prototype = new tempCtor(); + childCtor.prototype.constructor = childCtor; +}; + + +/** + * Mixes in an object's properties and methods into the callee's prototype. + * Basically mixin based inheritance, thus providing an alternative method for + * adding properties and methods to a class' prototype. + * + * <pre> + * function X() {} + * X.mixin({ + * one: 1, + * two: 2, + * three: 3, + * doit: function() { return this.one + this.two + this.three; } + * }); + * + * function Y() { } + * Y.mixin(X.prototype); + * Y.prototype.four = 15; + * Y.prototype.doit2 = function() { return this.doit() + this.four; } + * }); + * + * // or + * + * function Y() { } + * Y.inherits(X); + * Y.mixin({ + * one: 10, + * four: 15, + * doit2: function() { return this.doit() + this.four; } + * }); + * </pre> + * + * @param {Object} source from which to copy properties. + * @see goog.mixin + * @deprecated + */ +Function.prototype.mixin = function(source) { + goog.mixin(this.prototype, source); +}; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/codemap.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/codemap.js new file mode 100644 index 0000000..404127f --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/codemap.js @@ -0,0 +1,258 @@ +// Copyright 2009 the V8 project authors. All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +// Initlialize namespaces +var devtools = devtools || {}; +devtools.profiler = devtools.profiler || {}; + + +/** + * Constructs a mapper that maps addresses into code entries. + * + * @constructor + */ +devtools.profiler.CodeMap = function() { + /** + * Dynamic code entries. Used for JIT compiled code. + */ + this.dynamics_ = new goog.structs.SplayTree(); + + /** + * Name generator for entries having duplicate names. + */ + this.dynamicsNameGen_ = new devtools.profiler.CodeMap.NameGenerator(); + + /** + * Static code entries. Used for statically compiled code. + */ + this.statics_ = new goog.structs.SplayTree(); + + /** + * Libraries entries. Used for the whole static code libraries. + */ + this.libraries_ = new goog.structs.SplayTree(); + + /** + * Map of memory pages occupied with static code. + */ + this.pages_ = []; +}; + + +/** + * The number of alignment bits in a page address. + */ +devtools.profiler.CodeMap.PAGE_ALIGNMENT = 12; + + +/** + * Page size in bytes. + */ +devtools.profiler.CodeMap.PAGE_SIZE = + 1 << devtools.profiler.CodeMap.PAGE_ALIGNMENT; + + +/** + * Adds a dynamic (i.e. moveable and discardable) code entry. + * + * @param {number} start The starting address. + * @param {devtools.profiler.CodeMap.CodeEntry} codeEntry Code entry object. + */ +devtools.profiler.CodeMap.prototype.addCode = function(start, codeEntry) { + this.dynamics_.insert(start, codeEntry); +}; + + +/** + * Moves a dynamic code entry. Throws an exception if there is no dynamic + * code entry with the specified starting address. + * + * @param {number} from The starting address of the entry being moved. + * @param {number} to The destination address. + */ +devtools.profiler.CodeMap.prototype.moveCode = function(from, to) { + var removedNode = this.dynamics_.remove(from); + this.dynamics_.insert(to, removedNode.value); +}; + + +/** + * Discards a dynamic code entry. Throws an exception if there is no dynamic + * code entry with the specified starting address. + * + * @param {number} start The starting address of the entry being deleted. + */ +devtools.profiler.CodeMap.prototype.deleteCode = function(start) { + var removedNode = this.dynamics_.remove(start); +}; + + +/** + * Adds a library entry. + * + * @param {number} start The starting address. + * @param {devtools.profiler.CodeMap.CodeEntry} codeEntry Code entry object. + */ +devtools.profiler.CodeMap.prototype.addLibrary = function( + start, codeEntry) { + this.markPages_(start, start + codeEntry.size); + this.libraries_.insert(start, codeEntry); +}; + + +/** + * Adds a static code entry. + * + * @param {number} start The starting address. + * @param {devtools.profiler.CodeMap.CodeEntry} codeEntry Code entry object. + */ +devtools.profiler.CodeMap.prototype.addStaticCode = function( + start, codeEntry) { + this.statics_.insert(start, codeEntry); +}; + + +/** + * @private + */ +devtools.profiler.CodeMap.prototype.markPages_ = function(start, end) { + for (var addr = start; addr <= end; + addr += devtools.profiler.CodeMap.PAGE_SIZE) { + this.pages_[addr >>> devtools.profiler.CodeMap.PAGE_ALIGNMENT] = 1; + } +}; + + +/** + * @private + */ +devtools.profiler.CodeMap.prototype.isAddressBelongsTo_ = function(addr, node) { + return addr >= node.key && addr < (node.key + node.value.size); +}; + + +/** + * @private + */ +devtools.profiler.CodeMap.prototype.findInTree_ = function(tree, addr) { + var node = tree.findGreatestLessThan(addr); + return node && this.isAddressBelongsTo_(addr, node) ? node.value : null; +}; + + +/** + * Finds a code entry that contains the specified address. Both static and + * dynamic code entries are considered. + * + * @param {number} addr Address. + */ +devtools.profiler.CodeMap.prototype.findEntry = function(addr) { + var pageAddr = addr >>> devtools.profiler.CodeMap.PAGE_ALIGNMENT; + if (pageAddr in this.pages_) { + // Static code entries can contain "holes" of unnamed code. + // In this case, the whole library is assigned to this address. + return this.findInTree_(this.statics_, addr) || + this.findInTree_(this.libraries_, addr); + } + var min = this.dynamics_.findMin(); + var max = this.dynamics_.findMax(); + if (max != null && addr < (max.key + max.value.size) && addr >= min.key) { + var dynaEntry = this.findInTree_(this.dynamics_, addr); + if (dynaEntry == null) return null; + // Dedupe entry name. + if (!dynaEntry.nameUpdated_) { + dynaEntry.name = this.dynamicsNameGen_.getName(dynaEntry.name); + dynaEntry.nameUpdated_ = true; + } + return dynaEntry; + } + return null; +}; + + +/** + * Returns an array of all dynamic code entries. + */ +devtools.profiler.CodeMap.prototype.getAllDynamicEntries = function() { + return this.dynamics_.exportValues(); +}; + + +/** + * Returns an array of all static code entries. + */ +devtools.profiler.CodeMap.prototype.getAllStaticEntries = function() { + return this.statics_.exportValues(); +}; + + +/** + * Returns an array of all libraries entries. + */ +devtools.profiler.CodeMap.prototype.getAllLibrariesEntries = function() { + return this.libraries_.exportValues(); +}; + + +/** + * Creates a code entry object. + * + * @param {number} size Code entry size in bytes. + * @param {string} opt_name Code entry name. + * @constructor + */ +devtools.profiler.CodeMap.CodeEntry = function(size, opt_name) { + this.size = size; + this.name = opt_name || ''; + this.nameUpdated_ = false; +}; + + +devtools.profiler.CodeMap.CodeEntry.prototype.getName = function() { + return this.name; +}; + + +devtools.profiler.CodeMap.CodeEntry.prototype.toString = function() { + return this.name + ': ' + this.size.toString(16); +}; + + +devtools.profiler.CodeMap.NameGenerator = function() { + this.knownNames_ = []; +}; + + +devtools.profiler.CodeMap.NameGenerator.prototype.getName = function(name) { + if (!(name in this.knownNames_)) { + this.knownNames_[name] = 0; + return name; + } + var count = ++this.knownNames_[name]; + return name + ' {' + count + '}'; +}; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/consarray.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/consarray.js new file mode 100644 index 0000000..c67abb7 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/consarray.js @@ -0,0 +1,93 @@ +// Copyright 2009 the V8 project authors. All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +/** + * Constructs a ConsArray object. It is used mainly for tree traversal. + * In this use case we have lots of arrays that we need to iterate + * sequentally. The internal Array implementation is horribly slow + * when concatenating on large (10K items) arrays due to memory copying. + * That's why we avoid copying memory and insead build a linked list + * of arrays to iterate through. + * + * @constructor + */ +function ConsArray() { + this.tail_ = new ConsArray.Cell(null, null); + this.currCell_ = this.tail_; + this.currCellPos_ = 0; +}; + + +/** + * Concatenates another array for iterating. Empty arrays are ignored. + * This operation can be safely performed during ongoing ConsArray + * iteration. + * + * @param {Array} arr Array to concatenate. + */ +ConsArray.prototype.concat = function(arr) { + if (arr.length > 0) { + this.tail_.data = arr; + this.tail_ = this.tail_.next = new ConsArray.Cell(null, null); + } +}; + + +/** + * Whether the end of iteration is reached. + */ +ConsArray.prototype.atEnd = function() { + return this.currCell_ === null || + this.currCell_.data === null || + this.currCellPos_ >= this.currCell_.data.length; +}; + + +/** + * Returns the current item, moves to the next one. + */ +ConsArray.prototype.next = function() { + var result = this.currCell_.data[this.currCellPos_++]; + if (this.currCellPos_ >= this.currCell_.data.length) { + this.currCell_ = this.currCell_.next; + this.currCellPos_ = 0; + } + return result; +}; + + +/** + * A cell object used for constructing a list in ConsArray. + * + * @constructor + */ +ConsArray.Cell = function(data, next) { + this.data = data; + this.next = next; +}; + diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/csvparser.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/csvparser.js new file mode 100644 index 0000000..9e58dea --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/csvparser.js @@ -0,0 +1,98 @@ +// Copyright 2009 the V8 project authors. All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +// Initlialize namespaces. +var devtools = devtools || {}; +devtools.profiler = devtools.profiler || {}; + + +/** + * Creates a CSV lines parser. + */ +devtools.profiler.CsvParser = function() { +}; + + +/** + * A regex for matching a trailing quote. + * @private + */ +devtools.profiler.CsvParser.TRAILING_QUOTE_RE_ = /\"$/; + + +/** + * A regex for matching a double quote. + * @private + */ +devtools.profiler.CsvParser.DOUBLE_QUOTE_RE_ = /\"\"/g; + + +/** + * Parses a line of CSV-encoded values. Returns an array of fields. + * + * @param {string} line Input line. + */ +devtools.profiler.CsvParser.prototype.parseLine = function(line) { + var insideQuotes = false; + var fields = []; + var prevPos = 0; + for (var i = 0, n = line.length; i < n; ++i) { + switch (line.charAt(i)) { + case ',': + if (!insideQuotes) { + fields.push(line.substring(prevPos, i)); + prevPos = i + 1; + } + break; + case '"': + if (!insideQuotes) { + insideQuotes = true; + // Skip the leading quote. + prevPos++; + } else { + if (i + 1 < n && line.charAt(i + 1) != '"') { + insideQuotes = false; + } else { + i++; + } + } + break; + } + } + if (n > 0) { + fields.push(line.substring(prevPos)); + } + + for (i = 0; i < fields.length; ++i) { + // Eliminate trailing quotes. + fields[i] = fields[i].replace(devtools.profiler.CsvParser.TRAILING_QUOTE_RE_, ''); + // Convert quoted quotes into single ones. + fields[i] = fields[i].replace(devtools.profiler.CsvParser.DOUBLE_QUOTE_RE_, '"'); + } + return fields; +}; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/debugger_agent.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/debugger_agent.js new file mode 100644 index 0000000..1d566eb --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/debugger_agent.js @@ -0,0 +1,1490 @@ +// Copyright (c) 2009 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 Provides communication interface to remote v8 debugger. See + * protocol decription at http://code.google.com/p/v8/wiki/DebuggerProtocol + */ +goog.provide('devtools.DebuggerAgent'); + + +/** + * @constructor + */ +devtools.DebuggerAgent = function() { + RemoteDebuggerAgent.DebuggerOutput = + goog.bind(this.handleDebuggerOutput_, this); + RemoteDebuggerAgent.SetContextId = + goog.bind(this.setContextId_, this); + RemoteDebuggerAgent.DidGetActiveProfilerModules = + goog.bind(this.didGetActiveProfilerModules_, this); + RemoteDebuggerAgent.DidGetNextLogLines = + goog.bind(this.didGetNextLogLines_, this); + + /** + * Id of the inspected page global context. It is used for filtering scripts. + * @type {number} + */ + this.contextId_ = null; + + /** + * Mapping from script id to script info. + * @type {Object} + */ + this.parsedScripts_ = null; + + /** + * Mapping from the request id to the devtools.BreakpointInfo for the + * breakpoints whose v8 ids are not set yet. These breakpoints are waiting for + * 'setbreakpoint' responses to learn their ids in the v8 debugger. + * @see #handleSetBreakpointResponse_ + * @type {Object} + */ + this.requestNumberToBreakpointInfo_ = null; + + /** + * Information on current stack frames. + * @type {Array.<devtools.CallFrame>} + */ + this.callFrames_ = []; + + /** + * Whether to stop in the debugger on the exceptions. + * @type {boolean} + */ + this.pauseOnExceptions_ = true; + + /** + * Mapping: request sequence number->callback. + * @type {Object} + */ + this.requestSeqToCallback_ = null; + + /** + * Whether the scripts list has been requested. + * @type {boolean} + */ + this.scriptsCacheInitialized_ = false; + + /** + * Whether the scripts list should be requested next time when context id is + * set. + * @type {boolean} + */ + this.requestScriptsWhenContextIdSet_ = false; + + /** + * Active profiler modules flags. + * @type {number} + */ + this.activeProfilerModules_ = + devtools.DebuggerAgent.ProfilerModules.PROFILER_MODULE_NONE; + + /** + * Profiler processor instance. + * @type {devtools.profiler.Processor} + */ + this.profilerProcessor_ = new devtools.profiler.Processor(); + + /** + * Container of all breakpoints set using resource URL. These breakpoints + * survive page reload. Breakpoints set by script id(for scripts that don't + * have URLs) are stored in ScriptInfo objects. + * @type {Object} + */ + this.urlToBreakpoints_ = {}; + + + /** + * Exception message that is shown to user while on exception break. + * @type {WebInspector.ConsoleMessage} + */ + this.currentExceptionMessage_ = null; +}; + + +/** + * A copy of the scope types from v8/src/mirror-delay.js + * @enum {number} + */ +devtools.DebuggerAgent.ScopeType = { + Global: 0, + Local: 1, + With: 2, + Closure: 3, + Catch: 4 +}; + + +/** + * A copy of enum from include/v8.h + * @enum {number} + */ +devtools.DebuggerAgent.ProfilerModules = { + PROFILER_MODULE_NONE: 0, + PROFILER_MODULE_CPU: 1, + PROFILER_MODULE_HEAP_STATS: 1 << 1, + PROFILER_MODULE_JS_CONSTRUCTORS: 1 << 2, + PROFILER_MODULE_HEAP_SNAPSHOT: 1 << 16 +}; + + +/** + * Resets debugger agent to its initial state. + */ +devtools.DebuggerAgent.prototype.reset = function() { + this.contextId_ = null; + // No need to request scripts since they all will be pushed in AfterCompile + // events. + this.requestScriptsWhenContextIdSet_ = false; + this.parsedScripts_ = {}; + this.requestNumberToBreakpointInfo_ = {}; + this.callFrames_ = []; + this.requestSeqToCallback_ = {}; + + // Profiler isn't reset because it contains no data that is + // specific for a particular V8 instance. All such data is + // managed by an agent on the Render's side. +}; + + +/** + * Initializes scripts UI. This method is called every time Scripts panel + * is shown. It will send request for context id if it's not set yet. + */ +devtools.DebuggerAgent.prototype.initUI = function() { + // There can be a number of scripts from after-compile events that are + // pending addition into the UI. + for (var scriptId in this.parsedScripts_) { + var script = this.parsedScripts_[scriptId]; + WebInspector.parsedScriptSource(scriptId, script.getUrl(), + undefined /* script source */, script.getLineOffset()); + } + + // Initialize scripts cache when Scripts panel is shown first time. + if (this.scriptsCacheInitialized_) { + return; + } + this.scriptsCacheInitialized_ = true; + if (this.contextId_) { + // We already have context id. This means that we are here from the + // very beginning of the page load cycle and hence will get all scripts + // via after-compile events. No need to request scripts for this session. + return; + } + // Script list should be requested only when current context id is known. + RemoteDebuggerAgent.GetContextId(); + this.requestScriptsWhenContextIdSet_ = true; +}; + + +/** + * Asynchronously requests the debugger for the script source. + * @param {number} scriptId Id of the script whose source should be resolved. + * @param {function(source:?string):void} callback Function that will be called + * when the source resolution is completed. 'source' parameter will be null + * if the resolution fails. + */ +devtools.DebuggerAgent.prototype.resolveScriptSource = function( + scriptId, callback) { + var script = this.parsedScripts_[scriptId]; + if (!script || script.isUnresolved()) { + callback(null); + return; + } + + var cmd = new devtools.DebugCommand('scripts', { + 'ids': [scriptId], + 'includeSource': true + }); + devtools.DebuggerAgent.sendCommand_(cmd); + // Force v8 execution so that it gets to processing the requested command. + RemoteToolsAgent.ExecuteVoidJavaScript(); + + this.requestSeqToCallback_[cmd.getSequenceNumber()] = function(msg) { + if (msg.isSuccess()) { + var scriptJson = msg.getBody()[0]; + callback(scriptJson.source); + } else { + callback(null); + } + }; +}; + + +/** + * Tells the v8 debugger to stop on as soon as possible. + */ +devtools.DebuggerAgent.prototype.pauseExecution = function() { + RemoteDebuggerAgent.DebugBreak(); +}; + + +/** + * @param {number} sourceId Id of the script fot the breakpoint. + * @param {number} line Number of the line for the breakpoint. + * @param {?string} condition The breakpoint condition. + */ +devtools.DebuggerAgent.prototype.addBreakpoint = function( + sourceId, line, condition) { + var script = this.parsedScripts_[sourceId]; + if (!script) { + return; + } + + line = devtools.DebuggerAgent.webkitToV8LineNumber_(line); + + var commandArguments; + if (script.getUrl()) { + var breakpoints = this.urlToBreakpoints_[script.getUrl()]; + if (breakpoints && breakpoints[line]) { + return; + } + if (!breakpoints) { + breakpoints = {}; + this.urlToBreakpoints_[script.getUrl()] = breakpoints; + } + + var breakpointInfo = new devtools.BreakpointInfo(line); + breakpoints[line] = breakpointInfo; + + commandArguments = { + 'groupId': this.contextId_, + 'type': 'script', + 'target': script.getUrl(), + 'line': line, + 'condition': condition + }; + } else { + var breakpointInfo = script.getBreakpointInfo(line); + if (breakpointInfo) { + return; + } + + breakpointInfo = new devtools.BreakpointInfo(line); + script.addBreakpointInfo(breakpointInfo); + + commandArguments = { + 'groupId': this.contextId_, + 'type': 'scriptId', + 'target': sourceId, + 'line': line, + 'condition': condition + }; + } + + var cmd = new devtools.DebugCommand('setbreakpoint', commandArguments); + + this.requestNumberToBreakpointInfo_[cmd.getSequenceNumber()] = breakpointInfo; + + devtools.DebuggerAgent.sendCommand_(cmd); + // Force v8 execution so that it gets to processing the requested command. + // It is necessary for being able to change a breakpoint just after it + // has been created (since we need an existing breakpoint id for that). + RemoteToolsAgent.ExecuteVoidJavaScript(); +}; + + +/** + * @param {number} sourceId Id of the script for the breakpoint. + * @param {number} line Number of the line for the breakpoint. + */ +devtools.DebuggerAgent.prototype.removeBreakpoint = function(sourceId, line) { + var script = this.parsedScripts_[sourceId]; + if (!script) { + return; + } + + line = devtools.DebuggerAgent.webkitToV8LineNumber_(line); + + var breakpointInfo; + if (script.getUrl()) { + var breakpoints = this.urlToBreakpoints_[script.getUrl()]; + breakpointInfo = breakpoints[line]; + delete breakpoints[line]; + } else { + breakpointInfo = script.getBreakpointInfo(line); + if (breakpointInfo) { + script.removeBreakpointInfo(breakpointInfo); + } + } + + if (!breakpointInfo) { + return; + } + + breakpointInfo.markAsRemoved(); + + var id = breakpointInfo.getV8Id(); + + // If we don't know id of this breakpoint in the v8 debugger we cannot send + // 'clearbreakpoint' request. In that case it will be removed in + // 'setbreakpoint' response handler when we learn the id. + if (id != -1) { + this.requestClearBreakpoint_(id); + } +}; + + +/** + * @param {number} sourceId Id of the script for the breakpoint. + * @param {number} line Number of the line for the breakpoint. + * @param {?string} condition New breakpoint condition. + */ +devtools.DebuggerAgent.prototype.updateBreakpoint = function( + sourceId, line, condition) { + var script = this.parsedScripts_[sourceId]; + if (!script) { + return; + } + + line = devtools.DebuggerAgent.webkitToV8LineNumber_(line); + + var breakpointInfo; + if (script.getUrl()) { + var breakpoints = this.urlToBreakpoints_[script.getUrl()]; + breakpointInfo = breakpoints[line]; + } else { + breakpointInfo = script.getBreakpointInfo(line); + } + + var id = breakpointInfo.getV8Id(); + + // If we don't know id of this breakpoint in the v8 debugger we cannot send + // the 'changebreakpoint' request. + if (id != -1) { + // TODO(apavlov): make use of the real values for 'enabled' and + // 'ignoreCount' when appropriate. + this.requestChangeBreakpoint_(id, true, condition, null); + } +}; + + +/** + * Tells the v8 debugger to step into the next statement. + */ +devtools.DebuggerAgent.prototype.stepIntoStatement = function() { + this.stepCommand_('in'); +}; + + +/** + * Tells the v8 debugger to step out of current function. + */ +devtools.DebuggerAgent.prototype.stepOutOfFunction = function() { + this.stepCommand_('out'); +}; + + +/** + * Tells the v8 debugger to step over the next statement. + */ +devtools.DebuggerAgent.prototype.stepOverStatement = function() { + this.stepCommand_('next'); +}; + + +/** + * Tells the v8 debugger to continue execution after it has been stopped on a + * breakpoint or an exception. + */ +devtools.DebuggerAgent.prototype.resumeExecution = function() { + this.clearExceptionMessage_(); + var cmd = new devtools.DebugCommand('continue'); + devtools.DebuggerAgent.sendCommand_(cmd); +}; + + +/** + * Creates exception message and schedules it for addition to the resource upon + * backtrace availability. + * @param {string} url Resource url. + * @param {number} line Resource line number. + * @param {string} message Exception text. + */ +devtools.DebuggerAgent.prototype.createExceptionMessage_ = function( + url, line, message) { + this.currentExceptionMessage_ = new WebInspector.ConsoleMessage( + WebInspector.ConsoleMessage.MessageSource.JS, + WebInspector.ConsoleMessage.MessageType.Log, + WebInspector.ConsoleMessage.MessageLevel.Error, + line, + url, + 0 /* group level */, + 1 /* repeat count */, + '[Exception] ' + message); +}; + + +/** + * Shows pending exception message that is created with createExceptionMessage_ + * earlier. + */ +devtools.DebuggerAgent.prototype.showPendingExceptionMessage_ = function() { + if (!this.currentExceptionMessage_) { + return; + } + var msg = this.currentExceptionMessage_; + var resource = WebInspector.resourceURLMap[msg.url]; + if (resource) { + msg.resource = resource; + WebInspector.panels.resources.addMessageToResource(resource, msg); + } else { + this.currentExceptionMessage_ = null; + } +}; + + +/** + * Clears exception message from the resource. + */ +devtools.DebuggerAgent.prototype.clearExceptionMessage_ = function() { + if (this.currentExceptionMessage_) { + var messageElement = + this.currentExceptionMessage_._resourceMessageLineElement; + var bubble = messageElement.parentElement; + bubble.removeChild(messageElement); + if (!bubble.firstChild) { + // Last message in bubble removed. + bubble.parentElement.removeChild(bubble); + } + this.currentExceptionMessage_ = null; + } +}; + + +/** + * @return {boolean} True iff the debugger will pause execution on the + * exceptions. + */ +devtools.DebuggerAgent.prototype.pauseOnExceptions = function() { + return this.pauseOnExceptions_; +}; + + +/** + * Tells whether to pause in the debugger on the exceptions or not. + * @param {boolean} value True iff execution should be stopped in the debugger + * on the exceptions. + */ +devtools.DebuggerAgent.prototype.setPauseOnExceptions = function(value) { + this.pauseOnExceptions_ = value; +}; + + +/** + * Sends 'evaluate' request to the debugger. + * @param {Object} arguments Request arguments map. + * @param {function(devtools.DebuggerMessage)} callback Callback to be called + * when response is received. + */ +devtools.DebuggerAgent.prototype.requestEvaluate = function( + arguments, callback) { + var cmd = new devtools.DebugCommand('evaluate', arguments); + devtools.DebuggerAgent.sendCommand_(cmd); + this.requestSeqToCallback_[cmd.getSequenceNumber()] = callback; +}; + + +/** + * Sends 'lookup' request for each unresolved property of the object. When + * response is received the properties will be changed with their resolved + * values. + * @param {Object} object Object whose properties should be resolved. + * @param {function(devtools.DebuggerMessage)} Callback to be called when all + * children are resolved. + * @param {boolean} noIntrinsic Whether intrinsic properties should be included. + */ +devtools.DebuggerAgent.prototype.resolveChildren = function(object, callback, + noIntrinsic) { + if ('handle' in object) { + var result = []; + devtools.DebuggerAgent.formatObjectProperties_(object, result, + noIntrinsic); + callback(result); + } else { + this.requestLookup_([object.ref], function(msg) { + var result = []; + if (msg.isSuccess()) { + var handleToObject = msg.getBody(); + var resolved = handleToObject[object.ref]; + devtools.DebuggerAgent.formatObjectProperties_(resolved, result, + noIntrinsic); + callback(result); + } else { + callback([]); + } + }); + } +}; + + +/** + * Sends 'scope' request for the scope object to resolve its variables. + * @param {Object} scope Scope to be resolved. + * @param {function(Array.<WebInspector.ObjectPropertyProxy>)} callback + * Callback to be called when all scope variables are resolved. + */ +devtools.DebuggerAgent.prototype.resolveScope = function(scope, callback) { + var cmd = new devtools.DebugCommand('scope', { + 'frameNumber': scope.frameNumber, + 'number': scope.index, + 'compactFormat': true + }); + devtools.DebuggerAgent.sendCommand_(cmd); + this.requestSeqToCallback_[cmd.getSequenceNumber()] = function(msg) { + var result = []; + if (msg.isSuccess()) { + var scopeObjectJson = msg.getBody().object; + devtools.DebuggerAgent.formatObjectProperties_(scopeObjectJson, result, + true /* no intrinsic */); + } + callback(result); + }; +}; + + +/** + * Sets up callbacks that deal with profiles processing. + */ +devtools.DebuggerAgent.prototype.setupProfilerProcessorCallbacks = function() { + // A temporary icon indicating that the profile is being processed. + var processingIcon = new WebInspector.SidebarTreeElement( + 'profile-sidebar-tree-item', + WebInspector.UIString('Processing...'), + '', null, false); + var profilesSidebar = WebInspector.panels.profiles.sidebarTree; + + this.profilerProcessor_.setCallbacks( + function onProfileProcessingStarted() { + // Set visually empty string. Subtitle hiding is done via styles + // manipulation which doesn't play well with dynamic append / removal. + processingIcon.subtitle = ' '; + profilesSidebar.appendChild(processingIcon); + }, + function onProfileProcessingStatus(ticksCount) { + processingIcon.subtitle = + WebInspector.UIString('%d ticks processed', ticksCount); + }, + function onProfileProcessingFinished(profile) { + profilesSidebar.removeChild(processingIcon); + WebInspector.addProfile(profile); + // If no profile is currently shown, show the new one. + var profilesPanel = WebInspector.panels.profiles; + if (!profilesPanel.visibleView) { + profilesPanel.showProfile(profile); + } + } + ); +}; + + +/** + * Initializes profiling state. + */ +devtools.DebuggerAgent.prototype.initializeProfiling = function() { + this.setupProfilerProcessorCallbacks(); + RemoteDebuggerAgent.GetActiveProfilerModules(); +}; + + +/** + * Starts profiling. + * @param {number} modules List of modules to enable. + */ +devtools.DebuggerAgent.prototype.startProfiling = function(modules) { + RemoteDebuggerAgent.StartProfiling(modules); + if (modules & + devtools.DebuggerAgent.ProfilerModules.PROFILER_MODULE_HEAP_SNAPSHOT) { + // Active modules will not change, instead, a snapshot will be logged. + RemoteDebuggerAgent.GetNextLogLines(); + } else { + RemoteDebuggerAgent.GetActiveProfilerModules(); + } +}; + + +/** + * Stops profiling. + */ +devtools.DebuggerAgent.prototype.stopProfiling = function(modules) { + RemoteDebuggerAgent.StopProfiling(modules); +}; + + +/** + * @param{number} scriptId + * @return {string} Type of the context of the script with specified id. + */ +devtools.DebuggerAgent.prototype.getScriptContextType = function(scriptId) { + return this.parsedScripts_[scriptId].getContextType(); +}; + + +/** + * Removes specified breakpoint from the v8 debugger. + * @param {number} breakpointId Id of the breakpoint in the v8 debugger. + */ +devtools.DebuggerAgent.prototype.requestClearBreakpoint_ = function( + breakpointId) { + var cmd = new devtools.DebugCommand('clearbreakpoint', { + 'breakpoint': breakpointId + }); + devtools.DebuggerAgent.sendCommand_(cmd); +}; + + +/** + * Changes breakpoint parameters in the v8 debugger. + * @param {number} breakpointId Id of the breakpoint in the v8 debugger. + * @param {boolean} enabled Whether to enable the breakpoint. + * @param {?string} condition New breakpoint condition. + * @param {number} ignoreCount New ignore count for the breakpoint. + */ +devtools.DebuggerAgent.prototype.requestChangeBreakpoint_ = function( + breakpointId, enabled, condition, ignoreCount) { + var cmd = new devtools.DebugCommand('changebreakpoint', { + 'breakpoint': breakpointId, + 'enabled': enabled, + 'condition': condition, + 'ignoreCount': ignoreCount + }); + devtools.DebuggerAgent.sendCommand_(cmd); +}; + + +/** + * Sends 'backtrace' request to v8. + */ +devtools.DebuggerAgent.prototype.requestBacktrace_ = function() { + var cmd = new devtools.DebugCommand('backtrace', { + 'compactFormat':true + }); + devtools.DebuggerAgent.sendCommand_(cmd); +}; + + +/** + * Sends command to v8 debugger. + * @param {devtools.DebugCommand} cmd Command to execute. + */ +devtools.DebuggerAgent.sendCommand_ = function(cmd) { + RemoteDebuggerCommandExecutor.DebuggerCommand(cmd.toJSONProtocol()); +}; + + +/** + * Tells the v8 debugger to make the next execution step. + * @param {string} action 'in', 'out' or 'next' action. + */ +devtools.DebuggerAgent.prototype.stepCommand_ = function(action) { + this.clearExceptionMessage_(); + var cmd = new devtools.DebugCommand('continue', { + 'stepaction': action, + 'stepcount': 1 + }); + devtools.DebuggerAgent.sendCommand_(cmd); +}; + + +/** + * Sends 'lookup' request to v8. + * @param {number} handle Handle to the object to lookup. + */ +devtools.DebuggerAgent.prototype.requestLookup_ = function(handles, callback) { + var cmd = new devtools.DebugCommand('lookup', { + 'compactFormat':true, + 'handles': handles + }); + devtools.DebuggerAgent.sendCommand_(cmd); + this.requestSeqToCallback_[cmd.getSequenceNumber()] = callback; +}; + + +/** + * Sets debugger context id for scripts filtering. + * @param {number} contextId Id of the inspected page global context. + */ +devtools.DebuggerAgent.prototype.setContextId_ = function(contextId) { + this.contextId_ = contextId; + + // If it's the first time context id is set request scripts list. + if (this.requestScriptsWhenContextIdSet_) { + this.requestScriptsWhenContextIdSet_ = false; + var cmd = new devtools.DebugCommand('scripts', { + 'includeSource': false + }); + devtools.DebuggerAgent.sendCommand_(cmd); + // Force v8 execution so that it gets to processing the requested command. + RemoteToolsAgent.ExecuteVoidJavaScript(); + + var debuggerAgent = this; + this.requestSeqToCallback_[cmd.getSequenceNumber()] = function(msg) { + // Handle the response iff the context id hasn't changed since the request + // was issued. Otherwise if the context id did change all up-to-date + // scripts will be pushed in after compile events and there is no need to + // handle the response. + if (contextId == debuggerAgent.contextId_) { + debuggerAgent.handleScriptsResponse_(msg); + } + }; + } +}; + + +/** + * Handles output sent by v8 debugger. The output is either asynchronous event + * or response to a previously sent request. See protocol definitioun for more + * details on the output format. + * @param {string} output + */ +devtools.DebuggerAgent.prototype.handleDebuggerOutput_ = function(output) { + var msg; + try { + msg = new devtools.DebuggerMessage(output); + } catch(e) { + debugPrint('Failed to handle debugger reponse:\n' + e); + throw e; + } + + if (msg.getType() == 'event') { + if (msg.getEvent() == 'break') { + this.handleBreakEvent_(msg); + } else if (msg.getEvent() == 'exception') { + this.handleExceptionEvent_(msg); + } else if (msg.getEvent() == 'afterCompile') { + this.handleAfterCompileEvent_(msg); + } + } else if (msg.getType() == 'response') { + if (msg.getCommand() == 'scripts') { + this.invokeCallbackForResponse_(msg); + } else if (msg.getCommand() == 'setbreakpoint') { + this.handleSetBreakpointResponse_(msg); + } else if (msg.getCommand() == 'clearbreakpoint') { + this.handleClearBreakpointResponse_(msg); + } else if (msg.getCommand() == 'backtrace') { + this.handleBacktraceResponse_(msg); + } else if (msg.getCommand() == 'lookup') { + this.invokeCallbackForResponse_(msg); + } else if (msg.getCommand() == 'evaluate') { + this.invokeCallbackForResponse_(msg); + } else if (msg.getCommand() == 'scope') { + this.invokeCallbackForResponse_(msg); + } + } +}; + + +/** + * @param {devtools.DebuggerMessage} msg + */ +devtools.DebuggerAgent.prototype.handleBreakEvent_ = function(msg) { + // Force scrips panel to be shown first. + WebInspector.currentPanel = WebInspector.panels.scripts; + + var body = msg.getBody(); + + var line = devtools.DebuggerAgent.v8ToWwebkitLineNumber_(body.sourceLine); + this.requestBacktrace_(); +}; + + +/** + * @param {devtools.DebuggerMessage} msg + */ +devtools.DebuggerAgent.prototype.handleExceptionEvent_ = function(msg) { + // Force scrips panel to be shown first. + WebInspector.currentPanel = WebInspector.panels.scripts; + + var body = msg.getBody(); + if (this.pauseOnExceptions_) { + var body = msg.getBody(); + var line = devtools.DebuggerAgent.v8ToWwebkitLineNumber_(body.sourceLine); + this.createExceptionMessage_(body.script.name, line, body.exception.text); + this.requestBacktrace_(); + } else { + this.resumeExecution(); + } +}; + + +/** + * @param {devtools.DebuggerMessage} msg + */ +devtools.DebuggerAgent.prototype.handleScriptsResponse_ = function(msg) { + var scripts = msg.getBody(); + for (var i = 0; i < scripts.length; i++) { + var script = scripts[i]; + + // Skip scripts from other tabs. + if (!this.isScriptFromInspectedContext_(script, msg)) { + continue; + } + + // We may already have received the info in an afterCompile event. + if (script.id in this.parsedScripts_) { + continue; + } + this.addScriptInfo_(script, msg); + } +}; + + +/** + * @param {Object} script Json object representing script. + * @param {devtools.DebuggerMessage} msg Debugger response. + */ +devtools.DebuggerAgent.prototype.isScriptFromInspectedContext_ = function( + script, msg) { + if (!script.context) { + // Always ignore scripts from the utility context. + return false; + } + var context = msg.lookup(script.context.ref); + var scriptContextId = context.data; + if (!goog.isDef(scriptContextId)) { + return false; // Always ignore scripts from the utility context. + } + if (this.contextId_ === null) { + return true; + } + return (scriptContextId.value == this.contextId_); +}; + + +/** + * @param {devtools.DebuggerMessage} msg + */ +devtools.DebuggerAgent.prototype.handleSetBreakpointResponse_ = function(msg) { + var requestSeq = msg.getRequestSeq(); + var breakpointInfo = this.requestNumberToBreakpointInfo_[requestSeq]; + if (!breakpointInfo) { + // TODO(yurys): handle this case + return; + } + delete this.requestNumberToBreakpointInfo_[requestSeq]; + if (!msg.isSuccess()) { + // TODO(yurys): handle this case + return; + } + var idInV8 = msg.getBody().breakpoint; + breakpointInfo.setV8Id(idInV8); + + if (breakpointInfo.isRemoved()) { + this.requestClearBreakpoint_(idInV8); + } +}; + + +/** + * @param {devtools.DebuggerMessage} msg + */ +devtools.DebuggerAgent.prototype.handleAfterCompileEvent_ = function(msg) { + if (!this.contextId_) { + // Ignore scripts delta if main request has not been issued yet. + return; + } + var script = msg.getBody().script; + + // Ignore scripts from other tabs. + if (!this.isScriptFromInspectedContext_(script, msg)) { + return; + } + this.addScriptInfo_(script, msg); +}; + + +/** + * Handles current profiler status. + * @param {number} modules List of active (started) modules. + */ +devtools.DebuggerAgent.prototype.didGetActiveProfilerModules_ = function( + modules) { + var profModules = devtools.DebuggerAgent.ProfilerModules; + var profModuleNone = profModules.PROFILER_MODULE_NONE; + if (modules != profModuleNone && + this.activeProfilerModules_ == profModuleNone) { + // Start to query log data. + RemoteDebuggerAgent.GetNextLogLines(); + } + this.activeProfilerModules_ = modules; + // Update buttons. + WebInspector.setRecordingProfile(modules & profModules.PROFILER_MODULE_CPU); + if (modules != profModuleNone) { + // Monitor profiler state. It can stop itself on buffer fill-up. + setTimeout( + function() { RemoteDebuggerAgent.GetActiveProfilerModules(); }, 1000); + } +}; + + +/** + * Handles a portion of a profiler log retrieved by GetNextLogLines call. + * @param {string} log A portion of profiler log. + */ +devtools.DebuggerAgent.prototype.didGetNextLogLines_ = function(log) { + if (log.length > 0) { + this.profilerProcessor_.processLogChunk(log); + } else if (this.activeProfilerModules_ == + devtools.DebuggerAgent.ProfilerModules.PROFILER_MODULE_NONE) { + // No new data and profiling is stopped---suspend log reading. + return; + } + setTimeout(function() { RemoteDebuggerAgent.GetNextLogLines(); }, 500); +}; + + +/** + * Adds the script info to the local cache. This method assumes that the script + * is not in the cache yet. + * @param {Object} script Script json object from the debugger message. + * @param {devtools.DebuggerMessage} msg Debugger message containing the script + * data. + */ +devtools.DebuggerAgent.prototype.addScriptInfo_ = function(script, msg) { + var context = msg.lookup(script.context.ref); + var contextType = context.data.type; + this.parsedScripts_[script.id] = new devtools.ScriptInfo( + script.id, script.name, script.lineOffset, contextType); + if (WebInspector.panels.scripts.element.parentElement) { + // Only report script as parsed after scripts panel has been shown. + WebInspector.parsedScriptSource( + script.id, script.name, script.source, script.lineOffset); + } +}; + + +/** + * @param {devtools.DebuggerMessage} msg + */ +devtools.DebuggerAgent.prototype.handleClearBreakpointResponse_ = function( + msg) { + // Do nothing. +}; + + +/** + * Handles response to 'backtrace' command. + * @param {devtools.DebuggerMessage} msg + */ +devtools.DebuggerAgent.prototype.handleBacktraceResponse_ = function(msg) { + var frames = msg.getBody().frames; + this.callFrames_ = []; + for (var i = 0; i < frames.length; ++i) { + this.callFrames_.push(this.formatCallFrame_(frames[i])); + } + WebInspector.pausedScript(this.callFrames_); + this.showPendingExceptionMessage_(); + DevToolsHost.activateWindow(); +}; + + +/** + * Evaluates code on given callframe. + */ +devtools.DebuggerAgent.prototype.evaluateInCallFrame = function( + callFrameId, code, callback) { + var callFrame = this.callFrames_[callFrameId]; + callFrame.evaluate_(code, callback); +}; + + +/** + * Handles response to a command by invoking its callback (if any). + * @param {devtools.DebuggerMessage} msg + * @return {boolean} Whether a callback for the given message was found and + * excuted. + */ +devtools.DebuggerAgent.prototype.invokeCallbackForResponse_ = function(msg) { + var callback = this.requestSeqToCallback_[msg.getRequestSeq()]; + if (!callback) { + // It may happend if reset was called. + return false; + } + delete this.requestSeqToCallback_[msg.getRequestSeq()]; + callback(msg); + return true; +}; + + +/** + * @param {Object} stackFrame Frame json object from 'backtrace' response. + * @return {!devtools.CallFrame} Object containing information related to the + * call frame in the format expected by ScriptsPanel and its panes. + */ +devtools.DebuggerAgent.prototype.formatCallFrame_ = function(stackFrame) { + var func = stackFrame.func; + var sourceId = func.scriptId; + + // Add service script if it does not exist. + var existingScript = this.parsedScripts_[sourceId]; + if (!existingScript) { + this.parsedScripts_[sourceId] = new devtools.ScriptInfo( + sourceId, null /* name */, 0 /* line */, 'unknown' /* type */, + true /* unresolved */); + WebInspector.parsedScriptSource(sourceId, null, null, 0); + } + + var funcName = func.name || func.inferredName || '(anonymous function)'; + var line = devtools.DebuggerAgent.v8ToWwebkitLineNumber_(stackFrame.line); + + // Add basic scope chain info with scope variables. + var scopeChain = []; + var ScopeType = devtools.DebuggerAgent.ScopeType; + for (var i = 0; i < stackFrame.scopes.length; i++) { + var scope = stackFrame.scopes[i]; + scope.frameNumber = stackFrame.index; + var scopeObjectProxy = new WebInspector.ObjectProxy(scope, [], 0, '', true); + scopeObjectProxy.isScope = true; + scopeObjectProxy.properties = {}; // TODO(pfeldman): Fix autocomplete. + switch(scope.type) { + case ScopeType.Global: + scopeObjectProxy.isDocument = true; + break; + case ScopeType.Local: + scopeObjectProxy.isLocal = true; + scopeObjectProxy.thisObject = + devtools.DebuggerAgent.formatObjectProxy_(stackFrame.receiver); + break; + case ScopeType.With: + // Catch scope is treated as a regular with scope by WebKit so we + // also treat it this way. + case ScopeType.Catch: + scopeObjectProxy.isWithBlock = true; + break; + case ScopeType.Closure: + scopeObjectProxy.isClosure = true; + break; + } + scopeChain.push(scopeObjectProxy); + } + return new devtools.CallFrame(stackFrame.index, 'function', funcName, + sourceId, line, scopeChain); +}; + + +/** + * Collects properties for an object from the debugger response. + * @param {Object} object An object from the debugger protocol response. + * @param {Array.<WebInspector.ObjectPropertyProxy>} result An array to put the + * properties into. + * @param {boolean} noIntrinsic Whether intrinsic properties should be + * included. + */ +devtools.DebuggerAgent.formatObjectProperties_ = function(object, result, + noIntrinsic) { + devtools.DebuggerAgent.propertiesToProxies_(object.properties, result); + if (noIntrinsic) { + return; + } + + result.push(new WebInspector.ObjectPropertyProxy('__proto__', + devtools.DebuggerAgent.formatObjectProxy_(object.protoObject))); + result.push(new WebInspector.ObjectPropertyProxy('prototype', + devtools.DebuggerAgent.formatObjectProxy_(object.prototypeObject))); + result.push(new WebInspector.ObjectPropertyProxy('constructor', + devtools.DebuggerAgent.formatObjectProxy_(object.constructorFunction))); +}; + + +/** + * For each property in 'properties' creates its proxy representative. + * @param {Array.<Object>} properties Receiver properties or locals array from + * 'backtrace' response. + * @param {Array.<WebInspector.ObjectPropertyProxy>} Results holder. + */ +devtools.DebuggerAgent.propertiesToProxies_ = function(properties, result) { + var map = {}; + for (var i = 0; i < properties.length; ++i) { + var property = properties[i]; + var name = String(property.name); + if (name in map) { + continue; + } + map[name] = true; + var value = devtools.DebuggerAgent.formatObjectProxy_(property.value); + var propertyProxy = new WebInspector.ObjectPropertyProxy(name, value); + result.push(propertyProxy); + } +}; + + +/** + * @param {Object} v An object reference from the debugger response. + * @return {*} The value representation expected by ScriptsPanel. + */ +devtools.DebuggerAgent.formatObjectProxy_ = function(v) { + var description; + var hasChildren = false; + if (v.type == 'object') { + description = v.className; + hasChildren = true; + } else if (v.type == 'function') { + if (v.source) { + description = v.source; + } else { + description = 'function ' + v.name + '()'; + } + hasChildren = true; + } else if (goog.isDef(v.value)) { + description = v.value; + } else if (v.type == 'undefined') { + description = 'undefined'; + } else if (v.type == 'null') { + description = 'null'; + } else { + description = '<unresolved ref: ' + v.ref + ', type: ' + v.type + '>'; + } + var proxy = new WebInspector.ObjectProxy(v, [], 0, description, hasChildren); + proxy.type = v.type; + proxy.isV8Ref = true; + return proxy; +}; + + +/** + * Converts line number from Web Inspector UI(1-based) to v8(0-based). + * @param {number} line Resource line number in Web Inspector UI. + * @return {number} The line number in v8. + */ +devtools.DebuggerAgent.webkitToV8LineNumber_ = function(line) { + return line - 1; +}; + + +/** + * Converts line number from v8(0-based) to Web Inspector UI(1-based). + * @param {number} line Resource line number in v8. + * @return {number} The line number in Web Inspector. + */ +devtools.DebuggerAgent.v8ToWwebkitLineNumber_ = function(line) { + return line + 1; +}; + + +/** + * @param {number} scriptId Id of the script. + * @param {?string} url Script resource URL if any. + * @param {number} lineOffset First line 0-based offset in the containing + * document. + * @param {string} contextType Type of the script's context: + * "page" - regular script from html page + * "injected" - extension content script + * @param {bool} opt_isUnresolved If true, script will not be resolved. + * @constructor + */ +devtools.ScriptInfo = function( + scriptId, url, lineOffset, contextType, opt_isUnresolved) { + this.scriptId_ = scriptId; + this.lineOffset_ = lineOffset; + this.contextType_ = contextType; + this.url_ = url; + this.isUnresolved_ = opt_isUnresolved; + + this.lineToBreakpointInfo_ = {}; +}; + + +/** + * @return {number} + */ +devtools.ScriptInfo.prototype.getLineOffset = function() { + return this.lineOffset_; +}; + + +/** + * @return {string} + */ +devtools.ScriptInfo.prototype.getContextType = function() { + return this.contextType_; +}; + + +/** + * @return {?string} + */ +devtools.ScriptInfo.prototype.getUrl = function() { + return this.url_; +}; + + +/** + * @return {?bool} + */ +devtools.ScriptInfo.prototype.isUnresolved = function() { + return this.isUnresolved_; +}; + + +/** + * @param {number} line 0-based line number in the script. + * @return {?devtools.BreakpointInfo} Information on a breakpoint at the + * specified line in the script or undefined if there is no breakpoint at + * that line. + */ +devtools.ScriptInfo.prototype.getBreakpointInfo = function(line) { + return this.lineToBreakpointInfo_[line]; +}; + + +/** + * Adds breakpoint info to the script. + * @param {devtools.BreakpointInfo} breakpoint + */ +devtools.ScriptInfo.prototype.addBreakpointInfo = function(breakpoint) { + this.lineToBreakpointInfo_[breakpoint.getLine()] = breakpoint; +}; + + +/** + * @param {devtools.BreakpointInfo} breakpoint Breakpoint info to be removed. + */ +devtools.ScriptInfo.prototype.removeBreakpointInfo = function(breakpoint) { + var line = breakpoint.getLine(); + delete this.lineToBreakpointInfo_[line]; +}; + + + +/** + * @param {number} line Breakpoint 0-based line number in the containing script. + * @constructor + */ +devtools.BreakpointInfo = function(line) { + this.line_ = line; + this.v8id_ = -1; + this.removed_ = false; +}; + + +/** + * @return {number} + */ +devtools.BreakpointInfo.prototype.getLine = function(n) { + return this.line_; +}; + + +/** + * @return {number} Unique identifier of this breakpoint in the v8 debugger. + */ +devtools.BreakpointInfo.prototype.getV8Id = function(n) { + return this.v8id_; +}; + + +/** + * Sets id of this breakpoint in the v8 debugger. + * @param {number} id + */ +devtools.BreakpointInfo.prototype.setV8Id = function(id) { + this.v8id_ = id; +}; + + +/** + * Marks this breakpoint as removed from the front-end. + */ +devtools.BreakpointInfo.prototype.markAsRemoved = function() { + this.removed_ = true; +}; + + +/** + * @return {boolean} Whether this breakpoint has been removed from the + * front-end. + */ +devtools.BreakpointInfo.prototype.isRemoved = function() { + return this.removed_; +}; + + +/** + * Call stack frame data. + * @param {string} id CallFrame id. + * @param {string} type CallFrame type. + * @param {string} functionName CallFrame type. + * @param {string} sourceID Source id. + * @param {number} line Source line. + * @param {Array.<Object>} scopeChain Array of scoped objects. + * @construnctor + */ +devtools.CallFrame = function(id, type, functionName, sourceID, line, + scopeChain) { + this.id = id; + this.type = type; + this.functionName = functionName; + this.sourceID = sourceID; + this.line = line; + this.scopeChain = scopeChain; +}; + + +/** + * This method issues asynchronous evaluate request, reports result to the + * callback. + * @param {string} expression An expression to be evaluated in the context of + * this call frame. + * @param {function(Object):undefined} callback Callback to report result to. + */ +devtools.CallFrame.prototype.evaluate_ = function(expression, callback) { + devtools.tools.getDebuggerAgent().requestEvaluate({ + 'expression': expression, + 'frame': this.id, + 'global': false, + 'disable_break': false, + 'compactFormat': true + }, + function(response) { + var result = {}; + if (response.isSuccess()) { + result.value = devtools.DebuggerAgent.formatObjectProxy_( + response.getBody()); + } else { + result.value = response.getMessage(); + result.isException = true; + } + callback(result); + }); +}; + + +/** + * JSON based commands sent to v8 debugger. + * @param {string} command Name of the command to execute. + * @param {Object} opt_arguments Command-specific arguments map. + * @constructor + */ +devtools.DebugCommand = function(command, opt_arguments) { + this.command_ = command; + this.type_ = 'request'; + this.seq_ = ++devtools.DebugCommand.nextSeq_; + if (opt_arguments) { + this.arguments_ = opt_arguments; + } +}; + + +/** + * Next unique number to be used as debugger request sequence number. + * @type {number} + */ +devtools.DebugCommand.nextSeq_ = 1; + + +/** + * @return {number} + */ +devtools.DebugCommand.prototype.getSequenceNumber = function() { + return this.seq_; +}; + + +/** + * @return {string} + */ +devtools.DebugCommand.prototype.toJSONProtocol = function() { + var json = { + 'seq': this.seq_, + 'type': this.type_, + 'command': this.command_ + } + if (this.arguments_) { + json.arguments = this.arguments_; + } + return JSON.stringify(json); +}; + + +/** + * JSON messages sent from v8 debugger. See protocol definition for more + * details: http://code.google.com/p/v8/wiki/DebuggerProtocol + * @param {string} msg Raw protocol packet as JSON string. + * @constructor + */ +devtools.DebuggerMessage = function(msg) { + this.packet_ = JSON.parse(msg); + this.refs_ = []; + if (this.packet_.refs) { + for (var i = 0; i < this.packet_.refs.length; i++) { + this.refs_[this.packet_.refs[i].handle] = this.packet_.refs[i]; + } + } +}; + + +/** + * @return {string} The packet type. + */ +devtools.DebuggerMessage.prototype.getType = function() { + return this.packet_.type; +}; + + +/** + * @return {?string} The packet event if the message is an event. + */ +devtools.DebuggerMessage.prototype.getEvent = function() { + return this.packet_.event; +}; + + +/** + * @return {?string} The packet command if the message is a response to a + * command. + */ +devtools.DebuggerMessage.prototype.getCommand = function() { + return this.packet_.command; +}; + + +/** + * @return {number} The packet request sequence. + */ +devtools.DebuggerMessage.prototype.getRequestSeq = function() { + return this.packet_.request_seq; +}; + + +/** + * @return {number} Whether the v8 is running after processing the request. + */ +devtools.DebuggerMessage.prototype.isRunning = function() { + return this.packet_.running ? true : false; +}; + + +/** + * @return {boolean} Whether the request succeeded. + */ +devtools.DebuggerMessage.prototype.isSuccess = function() { + return this.packet_.success ? true : false; +}; + + +/** + * @return {string} + */ +devtools.DebuggerMessage.prototype.getMessage = function() { + return this.packet_.message; +}; + + +/** + * @return {Object} Parsed message body json. + */ +devtools.DebuggerMessage.prototype.getBody = function() { + return this.packet_.body; +}; + + +/** + * @param {number} handle Object handle. + * @return {?Object} Returns the object with the handle if it was sent in this + * message(some objects referenced by handles may be missing in the message). + */ +devtools.DebuggerMessage.prototype.lookup = function(handle) { + return this.refs_[handle]; +}; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/devtools.css b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/devtools.css new file mode 100644 index 0000000..eb649c5 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/devtools.css @@ -0,0 +1,99 @@ +#scripts-files option.injected { + color: rgb(70, 134, 240); +} + +.data-grid table { + line-height: 120%; +} + +body.attached #toolbar { + height: 34px; + border-top: 1px solid rgb(100, 100, 100); + cursor: default; /* overriden */ + padding-left: 0; +} + + +/* Chrome theme overrides */ +#toolbar { + background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(242, 247, 253)), to(rgb(223, 234, 248))); +} + + +/* Heap Profiler Styles */ + +#heap-snapshot-views { + position: absolute; + top: 0; + right: 0; + left: 200px; + bottom: 0; +} + +#heap-snapshot-status-bar-items { + position: absolute; + top: 0; + bottom: 0; + left: 200px; + overflow: hidden; + border-left: 1px solid rgb(184, 184, 184); + margin-left: -1px; +} + +.heap-snapshot-status-bar-item .glyph { + -webkit-mask-image: url(Images/focusButtonGlyph.png); +} + +.heap-snapshot-sidebar-tree-item .icon { + content: url(Images/profileIcon.png); +} + +.heap-snapshot-sidebar-tree-item.small .icon { + content: url(Images/profileSmallIcon.png); +} + +.heap-snapshot-view { + display: none; + overflow: hidden; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; +} + +.heap-snapshot-view.visible { + display: block; +} + +.heap-snapshot-view .data-grid { + border: none; + max-height: 100%; + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 93px; +} + +#heap-snapshot-summary { + position: absolute; + padding-top: 20px; + bottom: 0; + left: 0; + right: 0; + height: 93px; + margin-left: -1px; + border-left: 1px solid rgb(102, 102, 102); + background-color: rgb(101, 111, 130); + background-image: -webkit-gradient(linear, left top, left bottom, from(rgba(0, 0, 0, 0.5)), to(rgba(0, 0, 0, 0))); + background-repeat: repeat-x; + background-position: top; + text-align: center; + text-shadow: black 0 1px 1px; + white-space: nowrap; + color: white; + -webkit-background-size: 1px 6px; + -webkit-background-origin: padding; + -webkit-background-clip: padding; +} diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/devtools.html b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/devtools.html new file mode 100644 index 0000000..398bbed --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/devtools.html @@ -0,0 +1,142 @@ +<!-- +Copyright (c) 2009 The Chromium Authors. All rights reserved. + +This is the Chromium version of the WebInspector. This html file serves +as a deployment descriptor and specifies which js libraries to include into the +app. Once the "main" frontend method that is building WebInspector +from the js building blocks is extracted, we will be able have different +implementations of it for Chromium and WebKit. That would allow us for +example not to create WebKit Database tab and remove corresponding js files +from here. Longer term we would like to employ closure + basic js compilation. +That way js libraries would know their dependencies and js compiler would be +able to compile them into a single file. After that this HTML file will +include single <script src='fe-compiled.js'> and will become upstreamable. + +Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + its contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--> +<!DOCTYPE html> +<html> +<head> + <meta http-equiv="content-type" content="text/html; charset=utf-8"> + <link rel="stylesheet" type="text/css" href="inspector.css"> + <link rel="stylesheet" type="text/css" href="devtools.css"> + <script type="text/javascript" src="base.js"></script> + <script type="text/javascript" src="utilities.js"></script> + <script type="text/javascript" src="treeoutline.js"></script> + <script type="text/javascript" src="devtools_callback.js"></script> + <script type="text/javascript" src="debugger_agent.js"></script> + <script type="text/javascript" src="inspector_controller.js"></script> + <script type="text/javascript" src="inspector.js"></script> + <script type="text/javascript" src="codemap.js"></script> + <script type="text/javascript" src="consarray.js"></script> + <script type="text/javascript" src="csvparser.js"></script> + <script type="text/javascript" src="logreader.js"></script> + <script type="text/javascript" src="profile.js"></script> + <script type="text/javascript" src="profile_view.js"></script> + <script type="text/javascript" src="profiler_processor.js"></script> + <script type="text/javascript" src="splaytree.js"></script> + <script type="text/javascript" src="Object.js"></script> + <script type="text/javascript" src="KeyboardShortcut.js"></script> + <script type="text/javascript" src="TextPrompt.js"></script> + <script type="text/javascript" src="Placard.js"></script> + <script type="text/javascript" src="View.js"></script> + <script type="text/javascript" src="ChangesView.js"></script> + <script type="text/javascript" src="ConsoleView.js"></script> + <script type="text/javascript" src="Drawer.js"></script> + <script type="text/javascript" src="Resource.js"></script> + <script type="text/javascript" src="ResourceCategory.js"></script> + <script type="text/javascript" src="Database.js"></script> + <script type="text/javascript" src="Callback.js"></script> + <script type="text/javascript" src="DOMAgent.js"></script> + <script type="text/javascript" src="TimelineAgent.js"></script> + <script type="text/javascript" src="InjectedScriptAccess.js"></script> + <script type="text/javascript" src="inspector_controller_impl.js"></script> + <script type="text/javascript" src="DOMStorage.js"></script> + <script type="text/javascript" src="DOMStorageItemsView.js"></script> + <script type="text/javascript" src="DataGrid.js"></script> + <script type="text/javascript" src="DOMStorageDataGrid.js"></script> + <script type="text/javascript" src="Script.js"></script> + <script type="text/javascript" src="Breakpoint.js"></script> + <script type="text/javascript" src="SidebarPane.js"></script> + <script type="text/javascript" src="ElementsTreeOutline.js"></script> + <script type="text/javascript" src="SidebarTreeElement.js"></script> + <script type="text/javascript" src="PropertiesSection.js"></script> + <script type="text/javascript" src="ObjectPropertiesSection.js"></script> + <script type="text/javascript" src="ObjectProxy.js"></script> + <script type="text/javascript" src="BreakpointsSidebarPane.js"></script> + <script type="text/javascript" src="CallStackSidebarPane.js"></script> + <script type="text/javascript" src="ScopeChainSidebarPane.js"></script> + <script type="text/javascript" src="WatchExpressionsSidebarPane.js"></script> + <script type="text/javascript" src="MetricsSidebarPane.js"></script> + <script type="text/javascript" src="PropertiesSidebarPane.js"></script> + <script type="text/javascript" src="Color.js"></script> + <script type="text/javascript" src="StylesSidebarPane.js"></script> + <script type="text/javascript" src="Panel.js"></script> + <script type="text/javascript" src="PanelEnablerView.js"></script> + <script type="text/javascript" src="StatusBarButton.js"></script> + <script type="text/javascript" src="SummaryBar.js"></script> + <script type="text/javascript" src="ElementsPanel.js"></script> + <script type="text/javascript" src="ResourcesPanel.js"></script> + <script type="text/javascript" src="ScriptsPanel.js"></script> + <script type="text/javascript" src="DatabasesPanel.js"></script> + <script type="text/javascript" src="ProfilesPanel.js"></script> + <script type="text/javascript" src="ResourceView.js"></script> + <script type="text/javascript" src="Popup.js"></script> + <script type="text/javascript" src="SourceFrame.js"></script> + <script type="text/javascript" src="SourceView.js"></script> + <script type="text/javascript" src="FontView.js"></script> + <script type="text/javascript" src="ImageView.js"></script> + <script type="text/javascript" src="DatabaseTableView.js"></script> + <script type="text/javascript" src="DatabaseQueryView.js"></script> + <script type="text/javascript" src="ScriptView.js"></script> + <script type="text/javascript" src="ProfileView.js"></script> + <script type="text/javascript" src="ProfileDataGridTree.js"></script> + <script type="text/javascript" src="BottomUpProfileDataGridTree.js"></script> + <script type="text/javascript" src="TopDownProfileDataGridTree.js"></script> + <script type="text/javascript" src="heap_profiler_panel.js"></script> + <script type="text/javascript" src="devtools.js"></script> + <script type="text/javascript" src="devtools_host_stub.js"></script> + <script type="text/javascript" src="tests.js"></script> +</head> +<body class="detached"> + <div id="toolbar"> + <div class="toolbar-item hidden"></div> + <div class="toolbar-item flexable-space"></div> + <div class="toolbar-item hidden" id="search-results-matches"></div> + <div class="toolbar-item"><input id="search" type="search" incremental results="0"><div id="search-toolbar-label" class="toolbar-label"></div></div> + <div class="toolbar-item close"><button id="close-button"></button></div> + </div> + <div id="main"> + <div id="main-panels" tabindex="0" spellcheck="false"></div> + <div id="main-status-bar" class="status-bar"><div id="anchored-status-bar-items"><button id="dock-status-bar-item" class="status-bar-item toggled"><div class="glyph"></div><div class="glyph shadow"></div></button><button id="console-status-bar-item" class="status-bar-item"><div class="glyph"></div><div class="glyph shadow"></div></button><button id="changes-status-bar-item" class="status-bar-item hidden"></button><div id="count-items"><div id="changes-count" class="hidden"></div><div id="error-warning-count" class="hidden"></div></div></div></div> + </div> + <div id="drawer"> + <div id="console-view"><div id="console-messages"><div id="console-prompt" spellcheck="false"><br></div></div></div> + <div id="drawer-status-bar" class="status-bar"><div id="other-drawer-status-bar-items"><button id="clear-console-status-bar-item" class="status-bar-item"><div class="glyph"></div><div class="glyph shadow"></div></button><div id="console-filter" class="status-bar-item"></div></div></div> + </div> +</body> +</html> diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/devtools.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/devtools.js new file mode 100644 index 0000000..5086f80 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/devtools.js @@ -0,0 +1,392 @@ +// Copyright (c) 2009 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 Tools is a main class that wires all components of the + * DevTools frontend together. It is also responsible for overriding existing + * WebInspector functionality while it is getting upstreamed into WebCore. + */ +goog.provide('devtools.Tools'); + +goog.require('devtools.DebuggerAgent'); + + +/** + * Dispatches raw message from the host. + * @param {string} remoteName + * @prama {string} methodName + * @param {string} param1, param2, param3 Arguments to dispatch. + */ +devtools$$dispatch = function(remoteName, methodName, param1, param2, param3) { + remoteName = 'Remote' + remoteName.substring(0, remoteName.length - 8); + var agent = window[remoteName]; + if (!agent) { + debugPrint('No remote agent "' + remoteName + '" found.'); + return; + } + var method = agent[methodName]; + if (!method) { + debugPrint('No method "' + remoteName + '.' + methodName + '" found.'); + return; + } + method.call(this, param1, param2, param3); +}; + + +devtools.ToolsAgent = function() { + RemoteToolsAgent.DidExecuteUtilityFunction = + devtools.Callback.processCallback; + RemoteToolsAgent.FrameNavigate = + goog.bind(this.frameNavigate_, this); + RemoteToolsAgent.DispatchOnClient = + goog.bind(this.dispatchOnClient_, this); + this.debuggerAgent_ = new devtools.DebuggerAgent(); +}; + + +/** + * Resets tools agent to its initial state. + */ +devtools.ToolsAgent.prototype.reset = function() { + DevToolsHost.reset(); + this.debuggerAgent_.reset(); +}; + + +/** + * @param {string} script Script exression to be evaluated in the context of the + * inspected page. + * @param {function(Object|string, boolean):undefined} opt_callback Function to + * call with the result. + */ +devtools.ToolsAgent.prototype.evaluateJavaScript = function(script, + opt_callback) { + InspectorController.evaluate(script, opt_callback || function() {}); +}; + + +/** + * @return {devtools.DebuggerAgent} Debugger agent instance. + */ +devtools.ToolsAgent.prototype.getDebuggerAgent = function() { + return this.debuggerAgent_; +}; + + +/** + * @param {string} url Url frame navigated to. + * @see tools_agent.h + * @private + */ +devtools.ToolsAgent.prototype.frameNavigate_ = function(url) { + this.reset(); + // Do not reset Profiles panel. + var profiles = null; + if ('profiles' in WebInspector.panels) { + profiles = WebInspector.panels['profiles']; + delete WebInspector.panels['profiles']; + } + WebInspector.reset(); + if (profiles != null) { + WebInspector.panels['profiles'] = profiles; + } +}; + + +/** + * @param {string} message Serialized call to be dispatched on WebInspector. + * @private + */ +devtools.ToolsAgent.prototype.dispatchOnClient_ = function(message) { + WebInspector.dispatch.apply(WebInspector, JSON.parse(message)); +}; + + +/** + * Evaluates js expression. + * @param {string} expr + */ +devtools.ToolsAgent.prototype.evaluate = function(expr) { + RemoteToolsAgent.evaluate(expr); +}; + + +/** + * Enables / disables resources panel in the ui. + * @param {boolean} enabled New panel status. + */ +WebInspector.setResourcesPanelEnabled = function(enabled) { + InspectorController.resourceTrackingEnabled_ = enabled; + WebInspector.panels.resources.reset(); +}; + + +/** + * Prints string to the inspector console or shows alert if the console doesn't + * exist. + * @param {string} text + */ +function debugPrint(text) { + var console = WebInspector.console; + if (console) { + console.addMessage(new WebInspector.ConsoleMessage( + WebInspector.ConsoleMessage.MessageSource.JS, + WebInspector.ConsoleMessage.MessageType.Log, + WebInspector.ConsoleMessage.MessageLevel.Log, + 1, 'chrome://devtools/<internal>', undefined, -1, text)); + } else { + alert(text); + } +} + + +/** + * Global instance of the tools agent. + * @type {devtools.ToolsAgent} + */ +devtools.tools = null; + + +var context = {}; // Used by WebCore's inspector routines. + +/////////////////////////////////////////////////////////////////////////////// +// Here and below are overrides to existing WebInspector methods only. +// TODO(pfeldman): Patch WebCore and upstream changes. +var oldLoaded = WebInspector.loaded; +WebInspector.loaded = function() { + devtools.tools = new devtools.ToolsAgent(); + devtools.tools.reset(); + + Preferences.ignoreWhitespace = false; + Preferences.samplingCPUProfiler = true; + oldLoaded.call(this); + + // Hide dock button on Mac OS. + // TODO(pfeldman): remove once Mac OS docking is implemented. + if (InspectorController.platform().indexOf('mac') == 0) { + document.getElementById('dock-status-bar-item').addStyleClass('hidden'); + } + + // Mute refresh action. + document.addEventListener("keydown", function(event) { + if (event.keyIdentifier == 'F5') { + event.preventDefault(); + } else if (event.keyIdentifier == 'U+0052' /* 'R' */ && + (event.ctrlKey || event.metaKey)) { + event.preventDefault(); + } + }, true); + + DevToolsHost.loaded(); +}; + + +/** + * This override is necessary for adding script source asynchronously. + * @override + */ +WebInspector.ScriptView.prototype.setupSourceFrameIfNeeded = function() { + if (!this._frameNeedsSetup) { + return; + } + + this.attach(); + + if (this.script.source) { + this.didResolveScriptSource_(); + } else { + var self = this; + devtools.tools.getDebuggerAgent().resolveScriptSource( + this.script.sourceID, + function(source) { + self.script.source = source || + WebInspector.UIString('<source is not available>'); + self.didResolveScriptSource_(); + }); + } +}; + + +/** + * Performs source frame setup when script source is aready resolved. + */ +WebInspector.ScriptView.prototype.didResolveScriptSource_ = function() { + if (!InspectorController.addSourceToFrame( + "text/javascript", this.script.source, this.sourceFrame.element)) { + return; + } + + delete this._frameNeedsSetup; + + this.sourceFrame.addEventListener( + "syntax highlighting complete", this._syntaxHighlightingComplete, this); + this.sourceFrame.syntaxHighlightJavascript(); +}; + + +/** + * @param {string} type Type of the the property value('object' or 'function'). + * @param {string} className Class name of the property value. + * @constructor + */ +WebInspector.UnresolvedPropertyValue = function(type, className) { + this.type = type; + this.className = className; +}; + + +/** + * This function overrides standard searchableViews getters to perform search + * only in the current view (other views are loaded asynchronously, no way to + * search them yet). + */ +WebInspector.searchableViews_ = function() { + var views = []; + const visibleView = this.visibleView; + if (visibleView && visibleView.performSearch) { + views.push(visibleView); + } + return views; +}; + + +/** + * @override + */ +WebInspector.ResourcesPanel.prototype.__defineGetter__( + 'searchableViews', + WebInspector.searchableViews_); + + +/** + * @override + */ +WebInspector.ScriptsPanel.prototype.__defineGetter__( + 'searchableViews', + WebInspector.searchableViews_); + + +(function() { + var oldShow = WebInspector.ScriptsPanel.prototype.show; + WebInspector.ScriptsPanel.prototype.show = function() { + devtools.tools.getDebuggerAgent().initUI(); + this.enableToggleButton.visible = false; + oldShow.call(this); + }; +})(); + + +(function InterceptProfilesPanelEvents() { + var oldShow = WebInspector.ProfilesPanel.prototype.show; + WebInspector.ProfilesPanel.prototype.show = function() { + devtools.tools.getDebuggerAgent().initializeProfiling(); + this.enableToggleButton.visible = false; + oldShow.call(this); + // Show is called on every show event of a panel, so + // we only need to intercept it once. + WebInspector.ProfilesPanel.prototype.show = oldShow; + }; +})(); + + +/* + * @override + * TODO(mnaganov): Restore l10n when it will be agreed that it is needed. + */ +WebInspector.UIString = function(string) { + return String.vsprintf(string, Array.prototype.slice.call(arguments, 1)); +}; + + +// There is no clear way of setting frame title yet. So sniffing main resource +// load. +(function OverrideUpdateResource() { + var originalUpdateResource = WebInspector.updateResource; + WebInspector.updateResource = function(identifier, payload) { + originalUpdateResource.call(this, identifier, payload); + var resource = this.resources[identifier]; + if (resource && resource.mainResource && resource.finished) { + document.title = + WebInspector.UIString('Developer Tools - %s', resource.url); + } + }; +})(); + + +// Highlight extension content scripts in the scripts list. +(function () { + var original = WebInspector.ScriptsPanel.prototype._addScriptToFilesMenu; + WebInspector.ScriptsPanel.prototype._addScriptToFilesMenu = function(script) { + var result = original.apply(this, arguments); + var debuggerAgent = devtools.tools.getDebuggerAgent(); + var type = debuggerAgent.getScriptContextType(script.sourceID); + var option = script.filesSelectOption; + if (type == 'injected' && option) { + option.addStyleClass('injected'); + } + return result; + }; +})(); + + +/** Pending WebKit upstream by apavlov). Fixes iframe vs drag problem. */ +(function() { + var originalDragStart = WebInspector.elementDragStart; + WebInspector.elementDragStart = function(element) { + var glassPane = document.createElement("div"); + glassPane.style.cssText = + 'position:absolute;width:100%;height:100%;opacity:0;z-index:1'; + glassPane.id = 'glass-pane-for-drag'; + element.parentElement.appendChild(glassPane); + + originalDragStart.apply(this, arguments); + }; + + var originalDragEnd = WebInspector.elementDragEnd; + WebInspector.elementDragEnd = function() { + originalDragEnd.apply(this, arguments); + + var glassPane = document.getElementById('glass-pane-for-drag'); + glassPane.parentElement.removeChild(glassPane); + }; +})(); + + +(function() { + var originalCreatePanels = WebInspector._createPanels; + WebInspector._createPanels = function() { + originalCreatePanels.apply(this, arguments); + this.panels.heap = new WebInspector.HeapProfilerPanel(); + }; +})(); + + +(function () { +var orig = InjectedScriptAccess.getProperties; +InjectedScriptAccess.getProperties = function( + objectProxy, ignoreHasOwnProperty, callback) { + if (objectProxy.isScope) { + devtools.tools.getDebuggerAgent().resolveScope(objectProxy.objectId, + callback); + } else if (objectProxy.isV8Ref) { + devtools.tools.getDebuggerAgent().resolveChildren(objectProxy.objectId, + callback, true); + } else { + orig.apply(this, arguments); + } +}; +})() + + +WebInspector.resourceTrackingWasEnabled = function() +{ + InspectorController.resourceTrackingEnabled_ = true; + this.panels.resources.resourceTrackingWasEnabled(); +}; + +WebInspector.resourceTrackingWasDisabled = function() +{ + InspectorController.resourceTrackingEnabled_ = false; + this.panels.resources.resourceTrackingWasDisabled(); +}; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/devtools_callback.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/devtools_callback.js new file mode 100644 index 0000000..f252861 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/devtools_callback.js @@ -0,0 +1,57 @@ +// Copyright (c) 2009 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 Generic callback manager. + */ +goog.provide('devtools.Callback'); + + +/** + * Generic callback support as a singleton object. + * @constructor + */ +devtools.Callback = function() { + this.lastCallbackId_ = 1; + this.callbacks_ = {}; +}; + + +/** + * Assigns id to a callback. + * @param {Function} callback Callback to assign id to. + * @return {number} Callback id. + */ +devtools.Callback.prototype.wrap = function(callback) { + var callbackId = this.lastCallbackId_++; + this.callbacks_[callbackId] = callback || function() {}; + return callbackId; +}; + + +/** + * Executes callback with the given id. + * @param {callbackId} callbackId Id of a callback to call. + */ +devtools.Callback.prototype.processCallback = function(callbackId, + opt_vararg) { + var args = Array.prototype.slice.call(arguments, 1); + var callback = this.callbacks_[callbackId]; + callback.apply(null, args); + delete this.callbacks_[callbackId]; +}; + + +/** + * @type {devtools.Callback} Callback support singleton. + * @private + */ +devtools.Callback.INSTANCE_ = new devtools.Callback(); + +devtools.Callback.wrap = goog.bind( + devtools.Callback.INSTANCE_.wrap, + devtools.Callback.INSTANCE_); +devtools.Callback.processCallback = goog.bind( + devtools.Callback.INSTANCE_.processCallback, + devtools.Callback.INSTANCE_); diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/devtools_host_stub.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/devtools_host_stub.js new file mode 100644 index 0000000..2f3da60 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/devtools_host_stub.js @@ -0,0 +1,335 @@ +// Copyright (c) 2009 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 These stubs emulate backend functionality and allows + * DevTools frontend to function as a standalone web app. + */ + +/** + * @constructor + */ +RemoteDebuggerAgentStub = function() { + this.activeProfilerModules_ = + devtools.DebuggerAgent.ProfilerModules.PROFILER_MODULE_NONE; + this.profileLogPos_ = 0; + this.heapProfSample_ = 0; + this.heapProfLog_ = ''; +}; + + +RemoteDebuggerAgentStub.prototype.DebugBreak = function() { +}; + + +RemoteDebuggerAgentStub.prototype.GetContextId = function() { + RemoteDebuggerAgent.SetContextId(3); +}; + + +RemoteDebuggerAgentStub.prototype.StopProfiling = function(modules) { + this.activeProfilerModules_ &= ~modules; +}; + + +RemoteDebuggerAgentStub.prototype.StartProfiling = function(modules) { + var profModules = devtools.DebuggerAgent.ProfilerModules; + if (modules & profModules.PROFILER_MODULE_HEAP_SNAPSHOT) { + if (modules & profModules.PROFILER_MODULE_HEAP_STATS) { + this.heapProfLog_ += + 'heap-sample-begin,"Heap","allocated",' + + (new Date()).getTime() + '\n' + + 'heap-sample-stats,"Heap","allocated",10000,1000\n'; + this.heapProfLog_ += + 'heap-sample-item,STRING_TYPE,100,1000\n' + + 'heap-sample-item,CODE_TYPE,10,200\n' + + 'heap-sample-item,MAP_TYPE,20,350\n'; + var sample = RemoteDebuggerAgentStub.HeapSamples[this.heapProfSample_]; + if (++this.heapProfSample_ == RemoteDebuggerAgentStub.HeapSamples.length) + this.heapProfSample_ = 0; + for (var obj in sample) { + this.heapProfLog_ += + 'heap-js-cons-item,"' + obj + '",' + sample[obj][0] + + ',' + sample[obj][1] + '\n'; + } + this.heapProfLog_ += + 'heap-sample-end,"Heap","allocated"\n'; + } + } else { + this.activeProfilerModules_ |= modules; + } +}; + + +RemoteDebuggerAgentStub.prototype.GetActiveProfilerModules = function() { + var self = this; + setTimeout(function() { + RemoteDebuggerAgent.DidGetActiveProfilerModules( + self.activeProfilerModules_); + }, 100); +}; + + +RemoteDebuggerAgentStub.prototype.GetNextLogLines = function() { + var profModules = devtools.DebuggerAgent.ProfilerModules; + var logLines = ''; + if (this.activeProfilerModules_ & profModules.PROFILER_MODULE_CPU) { + if (this.profileLogPos_ < RemoteDebuggerAgentStub.ProfilerLogBuffer.length) { + this.profileLogPos_ += RemoteDebuggerAgentStub.ProfilerLogBuffer.length; + logLines += RemoteDebuggerAgentStub.ProfilerLogBuffer; + } + } + if (this.heapProfLog_) { + logLines += this.heapProfLog_; + this.heapProfLog_ = ''; + } + setTimeout(function() { + RemoteDebuggerAgent.DidGetNextLogLines(logLines); + }, 100); +}; + + +/** + * @constructor + */ +RemoteToolsAgentStub = function() { +}; + + +RemoteToolsAgentStub.prototype.HideDOMNodeHighlight = function() { +}; + + +RemoteToolsAgentStub.prototype.HighlightDOMNode = function() { +}; + + +RemoteToolsAgentStub.prototype.evaluate = function(expr) { + window.eval(expr); +}; + +RemoteToolsAgentStub.prototype.EvaluateJavaScript = function(callId, script) { + setTimeout(function() { + var result = eval(script); + RemoteToolsAgent.DidEvaluateJavaScript(callId, result); + }, 0); +}; + + +RemoteToolsAgentStub.prototype.ExecuteUtilityFunction = function(callId, + functionName, args) { + setTimeout(function() { + var result = []; + if (functionName == 'getProperties') { + result = [ + 'undefined', 'undefined_key', undefined, + 'string', 'string_key', 'value', + 'function', 'func', undefined, + 'array', 'array_key', [10], + 'object', 'object_key', undefined, + 'boolean', 'boolean_key', true, + 'number', 'num_key', 911, + 'date', 'date_key', new Date() ]; + } else if (functionName == 'getPrototypes') { + result = ['Proto1', 'Proto2', 'Proto3']; + } else if (functionName == 'getStyles') { + result = { + 'computedStyle' : [0, '0px', '0px', null, null, null, ['display', false, false, '', 'none']], + 'inlineStyle' : [1, '0px', '0px', null, null, null, ['display', false, false, '', 'none']], + 'styleAttributes' : { + attr: [2, '0px', '0px', null, null, null, ['display', false, false, '', 'none']] + }, + 'matchedCSSRules' : [ + { 'selector' : 'S', + 'style' : [3, '0px', '0px', null, null, null, ['display', false, false, '', 'none']], + 'parentStyleSheet' : { 'href' : 'http://localhost', + 'ownerNodeName' : 'DIV' } + } + ] + }; + } else if (functionName == 'toggleNodeStyle' || + functionName == 'applyStyleText' || + functionName == 'setStyleProperty') { + alert(functionName + '(' + args + ')'); + } else if (functionName == 'evaluate') { + try { + result = [ window.eval(JSON.parse(args)[0]), false ]; + } catch (e) { + result = [ e.toString(), true ]; + } + } else if (functionName == 'InspectorController' || + functionName == 'InjectedScript') { + // do nothing; + } else { + alert('Unexpected utility function:' + functionName); + } + RemoteToolsAgent.DidExecuteUtilityFunction(callId, + JSON.stringify(result), ''); + }, 0); +}; + + +RemoteToolsAgentStub.prototype.ExecuteVoidJavaScript = function() { +}; + + +RemoteToolsAgentStub.prototype.SetResourceTrackingEnabled = function(enabled, always) { + RemoteToolsAgent.SetResourcesPanelEnabled(enabled); + if (enabled) { + WebInspector.resourceTrackingWasEnabled(); + } else { + WebInspector.resourceTrackingWasDisabled(); + } + addDummyResource(); +}; + + +RemoteDebuggerAgentStub.ProfilerLogBuffer = + 'profiler,begin,1\n' + + 'profiler,resume\n' + + 'code-creation,LazyCompile,0x1000,256,"test1 http://aaa.js:1"\n' + + 'code-creation,LazyCompile,0x2000,256,"test2 http://bbb.js:2"\n' + + 'code-creation,LazyCompile,0x3000,256,"test3 http://ccc.js:3"\n' + + 'tick,0x1010,0x0,3\n' + + 'tick,0x2020,0x0,3,0x1010\n' + + 'tick,0x2020,0x0,3,0x1010\n' + + 'tick,0x3010,0x0,3,0x2020, 0x1010\n' + + 'tick,0x2020,0x0,3,0x1010\n' + + 'tick,0x2030,0x0,3,0x2020, 0x1010\n' + + 'tick,0x2020,0x0,3,0x1010\n' + + 'tick,0x1010,0x0,3\n' + + 'profiler,pause\n'; + + +RemoteDebuggerAgentStub.HeapSamples = [ + {foo: [1, 100], bar: [20, 2000]}, + {foo: [2000, 200000], bar: [10, 1000]}, + {foo: [15, 1500], bar: [15, 1500]}, + {bar: [20, 2000]}, + {foo: [15, 1500], bar: [15, 1500]}, + {bar: [20, 2000], baz: [15, 1500]} +]; + + +/** + * @constructor + */ +RemoteDebuggerCommandExecutorStub = function() { +}; + + +RemoteDebuggerCommandExecutorStub.prototype.DebuggerCommand = function(cmd) { + if ('{"seq":2,"type":"request","command":"scripts","arguments":{"' + + 'includeSource":false}}' == cmd) { + var response1 = + '{"seq":5,"request_seq":2,"type":"response","command":"scripts","' + + 'success":true,"body":[{"handle":61,"type":"script","name":"' + + 'http://www/~test/t.js","id":59,"lineOffset":0,"columnOffset":0,' + + '"lineCount":1,"sourceStart":"function fib(n) {","sourceLength":300,' + + '"scriptType":2,"compilationType":0,"context":{"ref":60}}],"refs":[{' + + '"handle":60,"type":"context","data":{"type":"page","value":3}}],' + + '"running":false}'; + this.sendResponse_(response1); + } else if ('{"seq":3,"type":"request","command":"scripts","arguments":{' + + '"ids":[59],"includeSource":true}}' == cmd) { + this.sendResponse_( + '{"seq":8,"request_seq":3,"type":"response","command":"scripts",' + + '"success":true,"body":[{"handle":1,"type":"script","name":' + + '"http://www/~test/t.js","id":59,"lineOffset":0,"columnOffset":0,' + + '"lineCount":1,"source":"function fib(n) {return n+1;}",' + + '"sourceLength":244,"scriptType":2,"compilationType":0,"context":{' + + '"ref":0}}],"refs":[{"handle":0,"type":"context","data":{"type":' + + '"page","value":3}}],"running":false}'); + } else { + debugPrint('Unexpected command: ' + cmd); + } +}; + + +RemoteDebuggerCommandExecutorStub.prototype.sendResponse_ = function(response) { + setTimeout(function() { + RemoteDebuggerAgent.DebuggerOutput(response); + }, 0); +}; + + +/** + * @constructor + */ +DevToolsHostStub = function() { + this.isStub = true; + window.domAutomationController = { + send: function(text) { + debugPrint(text); + } + }; +}; + + +function addDummyResource() { + var payload = { + requestHeaders : {}, + requestURL: 'http://google.com/simple_page.html', + host: 'google.com', + path: 'simple_page.html', + lastPathComponent: 'simple_page.html', + isMainResource: true, + cached: false, + mimeType: 'text/html', + suggestedFilename: 'simple_page.html', + expectedContentLength: 10000, + statusCode: 200, + contentLength: 10000, + responseHeaders: {}, + type: WebInspector.Resource.Type.Document, + finished: true, + startTime: new Date(), + + didResponseChange: true, + didCompletionChange: true, + didTypeChange: true + }; + + WebInspector.addResource(1, payload); + WebInspector.updateResource(1, payload); +} + + +DevToolsHostStub.prototype.loaded = function() { + addDummyResource(); + uiTests.runAllTests(); +}; + + +DevToolsHostStub.prototype.reset = function() { +}; + + +DevToolsHostStub.prototype.getPlatform = function() { + return "windows"; +}; + + +DevToolsHostStub.prototype.addResourceSourceToFrame = function( + identifier, mimeType, element) { +}; + + +DevToolsHostStub.prototype.addSourceToFrame = function(mimeType, source, + element) { +}; + + +DevToolsHostStub.prototype.getApplicationLocale = function() { + return "en-US"; +}; + + +if (!window['DevToolsHost']) { + window['RemoteDebuggerAgent'] = new RemoteDebuggerAgentStub(); + window['RemoteDebuggerCommandExecutor'] = + new RemoteDebuggerCommandExecutorStub(); + window['RemoteToolsAgent'] = new RemoteToolsAgentStub(); + window['DevToolsHost'] = new DevToolsHostStub(); +} diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/heap_profiler_panel.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/heap_profiler_panel.js new file mode 100644 index 0000000..eb1dffa --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/heap_profiler_panel.js @@ -0,0 +1,680 @@ +// Copyright (c) 2009 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 Heap profiler panel implementation. + */ + +WebInspector.HeapProfilerPanel = function() { + WebInspector.Panel.call(this); + + this.element.addStyleClass("heap-profiler"); + + this.sidebarElement = document.createElement("div"); + this.sidebarElement.id = "heap-snapshot-sidebar"; + this.sidebarElement.className = "sidebar"; + this.element.appendChild(this.sidebarElement); + + this.sidebarResizeElement = document.createElement("div"); + this.sidebarResizeElement.className = "sidebar-resizer-vertical"; + this.sidebarResizeElement.addEventListener("mousedown", this._startSidebarDragging.bind(this), false); + this.element.appendChild(this.sidebarResizeElement); + + this.sidebarTreeElement = document.createElement("ol"); + this.sidebarTreeElement.className = "sidebar-tree"; + this.sidebarElement.appendChild(this.sidebarTreeElement); + + this.sidebarTree = new TreeOutline(this.sidebarTreeElement); + + this.snapshotViews = document.createElement("div"); + this.snapshotViews.id = "heap-snapshot-views"; + this.element.appendChild(this.snapshotViews); + + this.snapshotButton = new WebInspector.StatusBarButton(WebInspector.UIString("Take heap snapshot."), "heap-snapshot-status-bar-item"); + this.snapshotButton.addEventListener("click", this._snapshotClicked.bind(this), false); + + this.snapshotViewStatusBarItemsContainer = document.createElement("div"); + this.snapshotViewStatusBarItemsContainer.id = "heap-snapshot-status-bar-items"; + + this.reset(); +}; + +WebInspector.HeapProfilerPanel.prototype = { + toolbarItemClass: "heap-profiler", + + get toolbarItemLabel() { + return WebInspector.UIString("Heap"); + }, + + get statusBarItems() { + return [this.snapshotButton.element, this.snapshotViewStatusBarItemsContainer]; + }, + + show: function() { + WebInspector.Panel.prototype.show.call(this); + this._updateSidebarWidth(); + }, + + reset: function() { + if (this._snapshots) { + var snapshotsLength = this._snapshots.length; + for (var i = 0; i < snapshotsLength; ++i) { + var snapshot = this._snapshots[i]; + delete snapshot._snapshotView; + } + } + + this._snapshots = []; + + this.sidebarTreeElement.removeStyleClass("some-expandable"); + + this.sidebarTree.removeChildren(); + this.snapshotViews.removeChildren(); + + this.snapshotViewStatusBarItemsContainer.removeChildren(); + }, + + handleKeyEvent: function(event) { + this.sidebarTree.handleKeyEvent(event); + }, + + addSnapshot: function(snapshot) { + this._snapshots.push(snapshot); + snapshot.list = this._snapshots; + snapshot.listIndex = this._snapshots.length - 1; + + var snapshotsTreeElement = new WebInspector.HeapSnapshotSidebarTreeElement(snapshot); + snapshotsTreeElement.small = false; + snapshot._snapshotsTreeElement = snapshotsTreeElement; + + this.sidebarTree.appendChild(snapshotsTreeElement); + + this.dispatchEventToListeners("snapshot added"); + }, + + showSnapshot: function(snapshot) { + if (!snapshot) + return; + + if (this.visibleView) + this.visibleView.hide(); + var view = this.snapshotViewForSnapshot(snapshot); + view.show(this.snapshotViews); + this.visibleView = view; + + this.snapshotViewStatusBarItemsContainer.removeChildren(); + var statusBarItems = view.statusBarItems; + for (var i = 0; i < statusBarItems.length; ++i) + this.snapshotViewStatusBarItemsContainer.appendChild(statusBarItems[i]); + }, + + showView: function(view) + { + this.showSnapshot(view.snapshot); + }, + + snapshotViewForSnapshot: function(snapshot) + { + if (!snapshot) + return null; + if (!snapshot._snapshotView) + snapshot._snapshotView = new WebInspector.HeapSnapshotView(this, snapshot); + return snapshot._snapshotView; + }, + + closeVisibleView: function() + { + if (this.visibleView) + this.visibleView.hide(); + delete this.visibleView; + }, + + _snapshotClicked: function() { + devtools.tools.getDebuggerAgent().startProfiling( + devtools.DebuggerAgent.ProfilerModules.PROFILER_MODULE_HEAP_SNAPSHOT | + devtools.DebuggerAgent.ProfilerModules.PROFILER_MODULE_HEAP_STATS | + devtools.DebuggerAgent.ProfilerModules.PROFILER_MODULE_JS_CONSTRUCTORS); + }, + + _startSidebarDragging: function(event) + { + WebInspector.elementDragStart(this.sidebarResizeElement, this._sidebarDragging.bind(this), this._endSidebarDragging.bind(this), event, "col-resize"); + }, + + _sidebarDragging: function(event) + { + this._updateSidebarWidth(event.pageX); + + event.preventDefault(); + }, + + _endSidebarDragging: function(event) + { + WebInspector.elementDragEnd(event); + }, + + _updateSidebarWidth: function(width) + { + if (this.sidebarElement.offsetWidth <= 0) { + // The stylesheet hasn"t loaded yet or the window is closed, + // so we can"t calculate what is need. Return early. + return; + } + + if (!("_currentSidebarWidth" in this)) + this._currentSidebarWidth = this.sidebarElement.offsetWidth; + if (typeof width === "undefined") + width = this._currentSidebarWidth; + + width = Number.constrain(width, Preferences.minSidebarWidth, window.innerWidth / 2); + this._currentSidebarWidth = width; + this.sidebarElement.style.width = width + "px"; + this.snapshotViews.style.left = width + "px"; + this.snapshotViewStatusBarItemsContainer.style.left = width + "px"; + this.sidebarResizeElement.style.left = (width - 3) + "px"; + } +}; + +WebInspector.HeapProfilerPanel.prototype.__proto__ = WebInspector.Panel.prototype; + +WebInspector.HeapSnapshotView = function(parent, snapshot) +{ + WebInspector.View.call(this); + + this.element.addStyleClass("heap-snapshot-view"); + + this.parent = parent; + this.parent.addEventListener("snapshot added", this._updateBaseOptions, this); + + this.showCountAsPercent = true; + this.showSizeAsPercent = true; + this.showCountDeltaAsPercent = true; + this.showSizeDeltaAsPercent = true; + + this.summaryBar = new WebInspector.SummaryBar(this.categories); + this.summaryBar.element.id = "heap-snapshot-summary"; + this.summaryBar.calculator = new WebInspector.HeapSummaryCalculator(snapshot.used); + this.element.appendChild(this.summaryBar.element); + + var columns = { "cons": { title: WebInspector.UIString("Constructor"), disclosure: true, sortable: true }, + "count": { title: WebInspector.UIString("Count"), width: "54px", sortable: true }, + "size": { title: WebInspector.UIString("Size"), width: "72px", sort: "descending", sortable: true }, + "countDelta": { title: WebInspector.UIString("\xb1 Count"), width: "72px", sortable: true }, + "sizeDelta": { title: WebInspector.UIString("\xb1 Size"), width: "72px", sortable: true } }; + + this.dataGrid = new WebInspector.DataGrid(columns); + this.dataGrid.addEventListener("sorting changed", this._sortData, this); + this.dataGrid.element.addEventListener("mousedown", this._mouseDownInDataGrid.bind(this), true); + this.element.appendChild(this.dataGrid.element); + + this.snapshot = snapshot; + + this.baseSelectElement = document.createElement("select"); + this.baseSelectElement.className = "status-bar-item"; + this.baseSelectElement.addEventListener("change", this._changeBase.bind(this), false); + this._updateBaseOptions(); + if (this.snapshot.listIndex > 0) + this.baseSelectElement.selectedIndex = this.snapshot.listIndex - 1; + else + this.baseSelectElement.selectedIndex = this.snapshot.listIndex; + this._resetDataGridList(); + + this.percentButton = new WebInspector.StatusBarButton("", "percent-time-status-bar-item status-bar-item"); + this.percentButton.addEventListener("click", this._percentClicked.bind(this), false); + + this.refresh(); + + this._updatePercentButton(); +}; + +WebInspector.HeapSnapshotView.prototype = { + + get categories() + { + return {code: {title: WebInspector.UIString("Code"), color: {r: 255, g: 121, b: 0}}, data: {title: WebInspector.UIString("Objects and Data"), color: {r: 47, g: 102, b: 236}}, other: {title: WebInspector.UIString("Other"), color: {r: 186, g: 186, b: 186}}}; + }, + + get statusBarItems() + { + return [this.baseSelectElement, this.percentButton.element]; + }, + + get snapshot() + { + return this._snapshot; + }, + + set snapshot(snapshot) + { + this._snapshot = snapshot; + }, + + show: function(parentElement) + { + WebInspector.View.prototype.show.call(this, parentElement); + this.dataGrid.updateWidths(); + }, + + resize: function() + { + if (this.dataGrid) + this.dataGrid.updateWidths(); + }, + + refresh: function() + { + this.dataGrid.removeChildren(); + + var children = this.snapshotDataGridList.children; + var count = children.length; + for (var index = 0; index < count; ++index) + this.dataGrid.appendChild(children[index]); + + this._updateSummaryGraph(); + }, + + refreshShowAsPercents: function() + { + this._updatePercentButton(); + this.refreshVisibleData(); + }, + + refreshVisibleData: function() + { + var child = this.dataGrid.children[0]; + while (child) { + child.refresh(); + child = child.traverseNextNode(false, null, true); + } + this._updateSummaryGraph(); + }, + + _changeBase: function() { + if (this.baseSnapshot === this.snapshot.list[this.baseSelectElement.selectedIndex]) + return; + + this._resetDataGridList(); + this.refresh(); + }, + + _createSnapshotDataGridList: function() + { + if (this._snapshotDataGridList) + delete this._snapshotDataGridList; + + this._snapshotDataGridList = new WebInspector.HeapSnapshotDataGridList(this, this.baseSnapshot.entries, this.snapshot.entries); + return this._snapshotDataGridList; + }, + + _mouseDownInDataGrid: function(event) + { + if (event.detail < 2) + return; + + var cell = event.target.enclosingNodeOrSelfWithNodeName("td"); + if (!cell || (!cell.hasStyleClass("count-column") && !cell.hasStyleClass("size-column") && !cell.hasStyleClass("countDelta-column") && !cell.hasStyleClass("sizeDelta-column"))) + return; + + if (cell.hasStyleClass("count-column")) + this.showCountAsPercent = !this.showCountAsPercent; + else if (cell.hasStyleClass("size-column")) + this.showSizeAsPercent = !this.showSizeAsPercent; + else if (cell.hasStyleClass("countDelta-column")) + this.showCountDeltaAsPercent = !this.showCountDeltaAsPercent; + else if (cell.hasStyleClass("sizeDelta-column")) + this.showSizeDeltaAsPercent = !this.showSizeDeltaAsPercent; + + this.refreshShowAsPercents(); + + event.preventDefault(); + event.stopPropagation(); + }, + + get _isShowingAsPercent() + { + return this.showCountAsPercent && this.showSizeAsPercent && this.showCountDeltaAsPercent && this.showSizeDeltaAsPercent; + }, + + _percentClicked: function(event) + { + var currentState = this._isShowingAsPercent; + this.showCountAsPercent = !currentState; + this.showSizeAsPercent = !currentState; + this.showCountDeltaAsPercent = !currentState; + this.showSizeDeltaAsPercent = !currentState; + this.refreshShowAsPercents(); + }, + + _resetDataGridList: function() + { + this.baseSnapshot = this.snapshot.list[this.baseSelectElement.selectedIndex]; + var lastComparator = WebInspector.HeapSnapshotDataGridList.propertyComparator("objectsSize", false); + if (this.snapshotDataGridList) { + lastComparator = this.snapshotDataGridList.lastComparator; + } + this.snapshotDataGridList = this._createSnapshotDataGridList(); + this.snapshotDataGridList.sort(lastComparator, true); + }, + + _sortData: function() + { + var sortAscending = this.dataGrid.sortOrder === "ascending"; + var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier; + var sortProperty = { + "cons": "constructorName", + "count": "objectsCount", + "size": "objectsSize", + "countDelta": this.showCountDeltaAsPercent ? "objectsCountDeltaPercent" : "objectsCountDelta", + "sizeDelta": this.showSizeDeltaAsPercent ? "objectsSizeDeltaPercent" : "objectsSizeDelta" + }[sortColumnIdentifier]; + + this.snapshotDataGridList.sort(WebInspector.HeapSnapshotDataGridList.propertyComparator(sortProperty, sortAscending)); + + this.refresh(); + }, + + _updateBaseOptions: function() + { + // We're assuming that snapshots can only be added. + if (this.baseSelectElement.length == this.snapshot.list.length) + return; + + for (var i = this.baseSelectElement.length, n = this.snapshot.list.length; i < n; ++i) { + var baseOption = document.createElement("option"); + baseOption.label = WebInspector.UIString("Compared to %s", this.snapshot.list[i].title); + this.baseSelectElement.appendChild(baseOption); + } + }, + + _updatePercentButton: function() + { + if (this._isShowingAsPercent) { + this.percentButton.title = WebInspector.UIString("Show absolute counts and sizes."); + this.percentButton.toggled = true; + } else { + this.percentButton.title = WebInspector.UIString("Show counts and sizes as percentages."); + this.percentButton.toggled = false; + } + }, + + _updateSummaryGraph: function() + { + this.summaryBar.calculator.showAsPercent = this._isShowingAsPercent; + this.summaryBar.update(this.snapshot.lowlevels); + } +}; + +WebInspector.HeapSnapshotView.prototype.__proto__ = WebInspector.View.prototype; + +WebInspector.HeapSummaryCalculator = function(total) +{ + this.total = total; +} + +WebInspector.HeapSummaryCalculator.prototype = { + computeSummaryValues: function(lowLevels) + { + function highFromLow(type) { + if (type == "CODE_TYPE" || type == "SHARED_FUNCTION_INFO_TYPE" || type == "SCRIPT_TYPE") return "code"; + if (type == "STRING_TYPE" || type == "HEAP_NUMBER_TYPE" || type.match(/^JS_/) || type.match(/_ARRAY_TYPE$/)) return "data"; + return "other"; + } + var highLevels = {data: 0, code: 0, other: 0}; + for (var item in lowLevels) { + var highItem = highFromLow(item); + highLevels[highItem] += lowLevels[item].size; + } + return {categoryValues: highLevels}; + }, + + formatValue: function(value) + { + if (this.showAsPercent) + return WebInspector.UIString("%.2f%%", value / this.total * 100.0); + else + return Number.bytesToString(value); + }, + + get showAsPercent() + { + return this._showAsPercent; + }, + + set showAsPercent(x) + { + this._showAsPercent = x; + } +} + +WebInspector.HeapSnapshotSidebarTreeElement = function(snapshot) +{ + this.snapshot = snapshot; + this.snapshot.title = WebInspector.UIString("Snapshot %d", this.snapshot.number); + + WebInspector.SidebarTreeElement.call(this, "heap-snapshot-sidebar-tree-item", "", "", snapshot, false); + + this.refreshTitles(); +}; + +WebInspector.HeapSnapshotSidebarTreeElement.prototype = { + onselect: function() + { + WebInspector.panels.heap.showSnapshot(this.snapshot); + }, + + get mainTitle() + { + if (this._mainTitle) + return this._mainTitle; + return this.snapshot.title; + }, + + set mainTitle(x) + { + this._mainTitle = x; + this.refreshTitles(); + }, + + get subtitle() + { + if (this._subTitle) + return this._subTitle; + return WebInspector.UIString("Used %s of %s (%.0f%%)", Number.bytesToString(this.snapshot.used, null, false), Number.bytesToString(this.snapshot.capacity, null, false), this.snapshot.used / this.snapshot.capacity * 100.0); + }, + + set subtitle(x) + { + this._subTitle = x; + this.refreshTitles(); + } +}; + +WebInspector.HeapSnapshotSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype; + +WebInspector.HeapSnapshotDataGridNode = function(snapshotView, baseEntry, snapshotEntry, owningList) +{ + WebInspector.DataGridNode.call(this, null, false); + + this.snapshotView = snapshotView; + this.list = owningList; + + if (!snapshotEntry) + snapshotEntry = { cons: baseEntry.cons, count: 0, size: 0 }; + this.constructorName = snapshotEntry.cons; + this.objectsCount = snapshotEntry.count; + this.objectsSize = snapshotEntry.size; + + if (!baseEntry) + baseEntry = { count: 0, size: 0 }; + this.baseObjectsCount = baseEntry.count; + this.objectsCountDelta = this.objectsCount - this.baseObjectsCount; + this.baseObjectsSize = baseEntry.size; + this.objectsSizeDelta = this.objectsSize - this.baseObjectsSize; +}; + +WebInspector.HeapSnapshotDataGridNode.prototype = { + get data() + { + var data = {}; + + data["cons"] = this.constructorName; + + if (this.snapshotView.showCountAsPercent) + data["count"] = WebInspector.UIString("%.2f%%", this.objectsCountPercent); + else + data["count"] = this.objectsCount; + + if (this.snapshotView.showSizeAsPercent) + data["size"] = WebInspector.UIString("%.2f%%", this.objectsSizePercent); + else + data["size"] = Number.bytesToString(this.objectsSize); + + function signForDelta(delta) { + if (delta == 0) + return ""; + if (delta > 0) + return "+"; + else + // Math minus sign, same width as plus. + return "\u2212"; + } + + function showDeltaAsPercent(value) { + if (value === Number.POSITIVE_INFINITY) + return WebInspector.UIString("new"); + else if (value === Number.NEGATIVE_INFINITY) + return WebInspector.UIString("deleted"); + if (value > 1000.0) + return WebInspector.UIString("%s >1000%%", signForDelta(value)); + return WebInspector.UIString("%s%.2f%%", signForDelta(value), Math.abs(value)); + } + + if (this.snapshotView.showCountDeltaAsPercent) + data["countDelta"] = showDeltaAsPercent(this.objectsCountDeltaPercent); + else + data["countDelta"] = WebInspector.UIString("%s%d", signForDelta(this.objectsCountDelta), Math.abs(this.objectsCountDelta)); + + if (this.snapshotView.showSizeDeltaAsPercent) + data["sizeDelta"] = showDeltaAsPercent(this.objectsSizeDeltaPercent); + else + data["sizeDelta"] = WebInspector.UIString("%s%s", signForDelta(this.objectsSizeDelta), Number.bytesToString(Math.abs(this.objectsSizeDelta))); + + return data; + }, + + get objectsCountPercent() + { + return this.objectsCount / this.list.objectsCount * 100.0; + }, + + get objectsSizePercent() + { + return this.objectsSize / this.list.objectsSize * 100.0; + }, + + get objectsCountDeltaPercent() + { + if (this.baseObjectsCount > 0) { + if (this.objectsCount > 0) + return this.objectsCountDelta / this.baseObjectsCount * 100.0; + else + return Number.NEGATIVE_INFINITY; + } else + return Number.POSITIVE_INFINITY; + }, + + get objectsSizeDeltaPercent() + { + if (this.baseObjectsSize > 0) { + if (this.objectsSize > 0) + return this.objectsSizeDelta / this.baseObjectsSize * 100.0; + else + return Number.NEGATIVE_INFINITY; + } else + return Number.POSITIVE_INFINITY; + } +}; + +WebInspector.HeapSnapshotDataGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype; + +WebInspector.HeapSnapshotDataGridList = function(snapshotView, baseEntries, snapshotEntries) +{ + this.list = this; + this.snapshotView = snapshotView; + this.children = []; + this.lastComparator = null; + this.populateChildren(baseEntries, snapshotEntries); +}; + +WebInspector.HeapSnapshotDataGridList.prototype = { + appendChild: function(child) + { + this.insertChild(child, this.children.length); + }, + + insertChild: function(child, index) + { + this.children.splice(index, 0, child); + }, + + removeChildren: function() + { + this.children = []; + }, + + populateChildren: function(baseEntries, snapshotEntries) + { + for (var item in snapshotEntries) + this.appendChild(new WebInspector.HeapSnapshotDataGridNode(this.snapshotView, baseEntries[item], snapshotEntries[item], this)); + + for (item in baseEntries) { + if (!(item in snapshotEntries)) + this.appendChild(new WebInspector.HeapSnapshotDataGridNode(this.snapshotView, baseEntries[item], null, this)); + } + }, + + sort: function(comparator, force) { + if (!force && this.lastComparator === comparator) + return; + + this.children.sort(comparator); + this.lastComparator = comparator; + }, + + get objectsCount() { + if (!this._objectsCount) { + this._objectsCount = 0; + for (var i = 0, n = this.children.length; i < n; ++i) { + this._objectsCount += this.children[i].objectsCount; + } + } + return this._objectsCount; + }, + + get objectsSize() { + if (!this._objectsSize) { + this._objectsSize = 0; + for (var i = 0, n = this.children.length; i < n; ++i) { + this._objectsSize += this.children[i].objectsSize; + } + } + return this._objectsSize; + } +}; + +WebInspector.HeapSnapshotDataGridList.propertyComparators = [{}, {}]; + +WebInspector.HeapSnapshotDataGridList.propertyComparator = function(property, isAscending) +{ + var comparator = this.propertyComparators[(isAscending ? 1 : 0)][property]; + if (!comparator) { + comparator = function(lhs, rhs) { + var l = lhs[property], r = rhs[property]; + var result = l < r ? -1 : (l > r ? 1 : 0); + return isAscending ? result : -result; + }; + this.propertyComparators[(isAscending ? 1 : 0)][property] = comparator; + } + return comparator; +}; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/back.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/back.png Binary files differnew file mode 100644 index 0000000..9363960 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/back.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/checker.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/checker.png Binary files differnew file mode 100644 index 0000000..8349908 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/checker.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/clearConsoleButtonGlyph.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/clearConsoleButtonGlyph.png Binary files differnew file mode 100644 index 0000000..b1f9465 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/clearConsoleButtonGlyph.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/closeButtons.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/closeButtons.png Binary files differnew file mode 100644 index 0000000..28158a4 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/closeButtons.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/consoleButtonGlyph.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/consoleButtonGlyph.png Binary files differnew file mode 100644 index 0000000..d10d43c --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/consoleButtonGlyph.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/cookie.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/cookie.png Binary files differnew file mode 100644 index 0000000..90c3c15 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/cookie.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/database.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/database.png Binary files differnew file mode 100644 index 0000000..339efa6 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/database.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/databaseTable.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/databaseTable.png Binary files differnew file mode 100644 index 0000000..3718708 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/databaseTable.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/debuggerContinue.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/debuggerContinue.png Binary files differnew file mode 100644 index 0000000..d90a855 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/debuggerContinue.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/debuggerPause.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/debuggerPause.png Binary files differnew file mode 100644 index 0000000..97f958a --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/debuggerPause.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/debuggerStepInto.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/debuggerStepInto.png Binary files differnew file mode 100644 index 0000000..277f126 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/debuggerStepInto.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/debuggerStepOut.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/debuggerStepOut.png Binary files differnew file mode 100644 index 0000000..3032e32 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/debuggerStepOut.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/debuggerStepOver.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/debuggerStepOver.png Binary files differnew file mode 100644 index 0000000..7d47245 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/debuggerStepOver.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallDown.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallDown.png Binary files differnew file mode 100644 index 0000000..cffc835 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallDown.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallDownBlack.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallDownBlack.png Binary files differnew file mode 100644 index 0000000..4b49c13 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallDownBlack.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallDownWhite.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallDownWhite.png Binary files differnew file mode 100644 index 0000000..aebae12 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallDownWhite.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallRight.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallRight.png Binary files differnew file mode 100644 index 0000000..a3102ea --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallRight.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallRightBlack.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallRightBlack.png Binary files differnew file mode 100644 index 0000000..2c45859 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallRightBlack.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallRightDown.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallRightDown.png Binary files differnew file mode 100644 index 0000000..035c069 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallRightDown.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallRightDownBlack.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallRightDownBlack.png Binary files differnew file mode 100644 index 0000000..86f67bd --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallRightDownBlack.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallRightDownWhite.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallRightDownWhite.png Binary files differnew file mode 100644 index 0000000..972d794 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallRightDownWhite.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallRightWhite.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallRightWhite.png Binary files differnew file mode 100644 index 0000000..a10168f --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/disclosureTriangleSmallRightWhite.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/dockButtonGlyph.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/dockButtonGlyph.png Binary files differnew file mode 100644 index 0000000..7052f4b --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/dockButtonGlyph.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/elementsIcon.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/elementsIcon.png Binary files differnew file mode 100644 index 0000000..fde3db9 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/elementsIcon.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/enableOutlineButtonGlyph.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/enableOutlineButtonGlyph.png Binary files differnew file mode 100644 index 0000000..85e0bd6 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/enableOutlineButtonGlyph.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/enableSolidButtonGlyph.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/enableSolidButtonGlyph.png Binary files differnew file mode 100644 index 0000000..25b2e96 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/enableSolidButtonGlyph.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/errorIcon.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/errorIcon.png Binary files differnew file mode 100644 index 0000000..c697263 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/errorIcon.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/errorMediumIcon.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/errorMediumIcon.png Binary files differnew file mode 100644 index 0000000..6ca32bb --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/errorMediumIcon.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/excludeButtonGlyph.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/excludeButtonGlyph.png Binary files differnew file mode 100644 index 0000000..5128576 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/excludeButtonGlyph.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/focusButtonGlyph.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/focusButtonGlyph.png Binary files differnew file mode 100644 index 0000000..b71807c --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/focusButtonGlyph.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/forward.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/forward.png Binary files differnew file mode 100644 index 0000000..ad70f3e --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/forward.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/glossyHeader.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/glossyHeader.png Binary files differnew file mode 100644 index 0000000..6cbefb7 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/glossyHeader.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/glossyHeaderPressed.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/glossyHeaderPressed.png Binary files differnew file mode 100644 index 0000000..1153506 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/glossyHeaderPressed.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/glossyHeaderSelected.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/glossyHeaderSelected.png Binary files differnew file mode 100644 index 0000000..71d5af6 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/glossyHeaderSelected.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/glossyHeaderSelectedPressed.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/glossyHeaderSelectedPressed.png Binary files differnew file mode 100644 index 0000000..7047dbe --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/glossyHeaderSelectedPressed.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/goArrow.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/goArrow.png Binary files differnew file mode 100644 index 0000000..f318a56 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/goArrow.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/graphLabelCalloutLeft.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/graphLabelCalloutLeft.png Binary files differnew file mode 100644 index 0000000..6426dbd --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/graphLabelCalloutLeft.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/graphLabelCalloutRight.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/graphLabelCalloutRight.png Binary files differnew file mode 100644 index 0000000..8c87eae --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/graphLabelCalloutRight.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/largerResourcesButtonGlyph.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/largerResourcesButtonGlyph.png Binary files differnew file mode 100644 index 0000000..71256d6 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/largerResourcesButtonGlyph.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/localStorage.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/localStorage.png Binary files differnew file mode 100644 index 0000000..44a3019 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/localStorage.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/nodeSearchButtonGlyph.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/nodeSearchButtonGlyph.png Binary files differnew file mode 100644 index 0000000..faf5df2 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/nodeSearchButtonGlyph.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/paneBottomGrow.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/paneBottomGrow.png Binary files differnew file mode 100644 index 0000000..d55b865 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/paneBottomGrow.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/paneBottomGrowActive.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/paneBottomGrowActive.png Binary files differnew file mode 100644 index 0000000..ef3f259 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/paneBottomGrowActive.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/paneGrowHandleLine.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/paneGrowHandleLine.png Binary files differnew file mode 100644 index 0000000..4eaf61b --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/paneGrowHandleLine.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/pauseOnExceptionButtonGlyph.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/pauseOnExceptionButtonGlyph.png Binary files differnew file mode 100644 index 0000000..c3cec5f --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/pauseOnExceptionButtonGlyph.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/percentButtonGlyph.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/percentButtonGlyph.png Binary files differnew file mode 100644 index 0000000..0ace3b7 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/percentButtonGlyph.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/profileGroupIcon.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/profileGroupIcon.png Binary files differnew file mode 100644 index 0000000..44616d4 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/profileGroupIcon.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/profileIcon.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/profileIcon.png Binary files differnew file mode 100644 index 0000000..8008f9b --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/profileIcon.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/profileSmallIcon.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/profileSmallIcon.png Binary files differnew file mode 100644 index 0000000..7935520 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/profileSmallIcon.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/profilesIcon.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/profilesIcon.png Binary files differnew file mode 100644 index 0000000..ecd5b04 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/profilesIcon.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/profilesSilhouette.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/profilesSilhouette.png Binary files differnew file mode 100644 index 0000000..42bb966 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/profilesSilhouette.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/radioDot.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/radioDot.png Binary files differnew file mode 100644 index 0000000..609878f --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/radioDot.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/recordButtonGlyph.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/recordButtonGlyph.png Binary files differnew file mode 100644 index 0000000..bfdad1a --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/recordButtonGlyph.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/recordToggledButtonGlyph.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/recordToggledButtonGlyph.png Binary files differnew file mode 100644 index 0000000..2c22f87 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/recordToggledButtonGlyph.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/reloadButtonGlyph.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/reloadButtonGlyph.png Binary files differnew file mode 100644 index 0000000..28e047a --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/reloadButtonGlyph.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourceCSSIcon.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourceCSSIcon.png Binary files differnew file mode 100644 index 0000000..aead6a7 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourceCSSIcon.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourceDocumentIcon.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourceDocumentIcon.png Binary files differnew file mode 100644 index 0000000..1683a09 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourceDocumentIcon.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourceDocumentIconSmall.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourceDocumentIconSmall.png Binary files differnew file mode 100644 index 0000000..468ced9 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourceDocumentIconSmall.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourceJSIcon.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourceJSIcon.png Binary files differnew file mode 100644 index 0000000..9ef6ed0 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourceJSIcon.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourcePlainIcon.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourcePlainIcon.png Binary files differnew file mode 100644 index 0000000..0ed37b6 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourcePlainIcon.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourcePlainIconSmall.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourcePlainIconSmall.png Binary files differnew file mode 100644 index 0000000..0fa967d --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourcePlainIconSmall.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourcesIcon.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourcesIcon.png Binary files differnew file mode 100644 index 0000000..982424d --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourcesIcon.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourcesSilhouette.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourcesSilhouette.png Binary files differnew file mode 100644 index 0000000..9c8bb53 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourcesSilhouette.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourcesSizeGraphIcon.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourcesSizeGraphIcon.png Binary files differnew file mode 100644 index 0000000..e60dbe5 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourcesSizeGraphIcon.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourcesTimeGraphIcon.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourcesTimeGraphIcon.png Binary files differnew file mode 100644 index 0000000..c6953e9 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/resourcesTimeGraphIcon.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/scriptsIcon.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/scriptsIcon.png Binary files differnew file mode 100644 index 0000000..213b31e --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/scriptsIcon.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/scriptsSilhouette.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/scriptsSilhouette.png Binary files differnew file mode 100644 index 0000000..206396f --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/scriptsSilhouette.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/searchSmallBlue.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/searchSmallBlue.png Binary files differnew file mode 100644 index 0000000..9c990f4 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/searchSmallBlue.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/searchSmallBrightBlue.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/searchSmallBrightBlue.png Binary files differnew file mode 100644 index 0000000..b1d8055 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/searchSmallBrightBlue.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/searchSmallGray.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/searchSmallGray.png Binary files differnew file mode 100644 index 0000000..4f3c068 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/searchSmallGray.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/searchSmallWhite.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/searchSmallWhite.png Binary files differnew file mode 100644 index 0000000..85f430d --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/searchSmallWhite.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/segment.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/segment.png Binary files differnew file mode 100644 index 0000000..759266e --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/segment.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/segmentEnd.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/segmentEnd.png Binary files differnew file mode 100644 index 0000000..72672ff --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/segmentEnd.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/segmentHover.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/segmentHover.png Binary files differnew file mode 100644 index 0000000..c5017f4 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/segmentHover.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/segmentHoverEnd.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/segmentHoverEnd.png Binary files differnew file mode 100644 index 0000000..d51363d --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/segmentHoverEnd.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/segmentSelected.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/segmentSelected.png Binary files differnew file mode 100644 index 0000000..c92f584 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/segmentSelected.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/segmentSelectedEnd.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/segmentSelectedEnd.png Binary files differnew file mode 100644 index 0000000..be5e0852 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/segmentSelectedEnd.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/sessionStorage.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/sessionStorage.png Binary files differnew file mode 100644 index 0000000..4d50e35 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/sessionStorage.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/splitviewDimple.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/splitviewDimple.png Binary files differnew file mode 100644 index 0000000..584ffd4 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/splitviewDimple.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/splitviewDividerBackground.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/splitviewDividerBackground.png Binary files differnew file mode 100644 index 0000000..1120a7f --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/splitviewDividerBackground.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/statusbarBackground.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/statusbarBackground.png Binary files differnew file mode 100644 index 0000000..b466a49 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/statusbarBackground.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/statusbarBottomBackground.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/statusbarBottomBackground.png Binary files differnew file mode 100644 index 0000000..fb5c9e4 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/statusbarBottomBackground.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/statusbarButtons.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/statusbarButtons.png Binary files differnew file mode 100644 index 0000000..e8090cb --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/statusbarButtons.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/statusbarMenuButton.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/statusbarMenuButton.png Binary files differnew file mode 100644 index 0000000..9b3abdd --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/statusbarMenuButton.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/statusbarMenuButtonSelected.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/statusbarMenuButtonSelected.png Binary files differnew file mode 100644 index 0000000..8189c43 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/statusbarMenuButtonSelected.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/statusbarResizerHorizontal.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/statusbarResizerHorizontal.png Binary files differnew file mode 100644 index 0000000..56deeab --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/statusbarResizerHorizontal.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/statusbarResizerVertical.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/statusbarResizerVertical.png Binary files differnew file mode 100644 index 0000000..7fc145277 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/statusbarResizerVertical.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/storageIcon.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/storageIcon.png Binary files differnew file mode 100644 index 0000000..79c7bb3 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/storageIcon.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelineHollowPillBlue.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelineHollowPillBlue.png Binary files differnew file mode 100644 index 0000000..c7c273b --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelineHollowPillBlue.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelineHollowPillGray.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelineHollowPillGray.png Binary files differnew file mode 100644 index 0000000..9ff37ef --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelineHollowPillGray.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelineHollowPillGreen.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelineHollowPillGreen.png Binary files differnew file mode 100644 index 0000000..cc5a8f3 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelineHollowPillGreen.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelineHollowPillOrange.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelineHollowPillOrange.png Binary files differnew file mode 100644 index 0000000..08a81e4 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelineHollowPillOrange.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelineHollowPillPurple.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelineHollowPillPurple.png Binary files differnew file mode 100644 index 0000000..565a05c --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelineHollowPillPurple.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelineHollowPillRed.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelineHollowPillRed.png Binary files differnew file mode 100644 index 0000000..c3a1b9b --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelineHollowPillRed.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelineHollowPillYellow.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelineHollowPillYellow.png Binary files differnew file mode 100644 index 0000000..780045b --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelineHollowPillYellow.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelinePillBlue.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelinePillBlue.png Binary files differnew file mode 100644 index 0000000..c897faa --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelinePillBlue.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelinePillGray.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelinePillGray.png Binary files differnew file mode 100644 index 0000000..2128896 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelinePillGray.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelinePillGreen.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelinePillGreen.png Binary files differnew file mode 100644 index 0000000..9b66125 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelinePillGreen.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelinePillOrange.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelinePillOrange.png Binary files differnew file mode 100644 index 0000000..dd944fb --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelinePillOrange.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelinePillPurple.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelinePillPurple.png Binary files differnew file mode 100644 index 0000000..21b96f7 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelinePillPurple.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelinePillRed.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelinePillRed.png Binary files differnew file mode 100644 index 0000000..f5e213b --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelinePillRed.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelinePillYellow.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelinePillYellow.png Binary files differnew file mode 100644 index 0000000..ae2a5a23 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/timelinePillYellow.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/tipBalloon.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/tipBalloon.png Binary files differnew file mode 100644 index 0000000..4cdf738 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/tipBalloon.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/tipBalloonBottom.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/tipBalloonBottom.png Binary files differnew file mode 100644 index 0000000..3317a5a --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/tipBalloonBottom.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/tipIcon.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/tipIcon.png Binary files differnew file mode 100644 index 0000000..8ca6124 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/tipIcon.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/tipIconPressed.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/tipIconPressed.png Binary files differnew file mode 100644 index 0000000..443e410 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/tipIconPressed.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/toolbarItemSelected.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/toolbarItemSelected.png Binary files differnew file mode 100644 index 0000000..bd681f18 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/toolbarItemSelected.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/treeDownTriangleBlack.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/treeDownTriangleBlack.png Binary files differnew file mode 100644 index 0000000..0821112 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/treeDownTriangleBlack.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/treeDownTriangleWhite.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/treeDownTriangleWhite.png Binary files differnew file mode 100644 index 0000000..1667b51 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/treeDownTriangleWhite.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/treeRightTriangleBlack.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/treeRightTriangleBlack.png Binary files differnew file mode 100644 index 0000000..90de820 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/treeRightTriangleBlack.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/treeRightTriangleWhite.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/treeRightTriangleWhite.png Binary files differnew file mode 100644 index 0000000..2b6a82f --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/treeRightTriangleWhite.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/treeUpTriangleBlack.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/treeUpTriangleBlack.png Binary files differnew file mode 100644 index 0000000..ef69dbc --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/treeUpTriangleBlack.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/treeUpTriangleWhite.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/treeUpTriangleWhite.png Binary files differnew file mode 100644 index 0000000..43ce4be --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/treeUpTriangleWhite.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/undockButtonGlyph.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/undockButtonGlyph.png Binary files differnew file mode 100644 index 0000000..eed2b65 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/undockButtonGlyph.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/userInputIcon.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/userInputIcon.png Binary files differnew file mode 100644 index 0000000..325023f --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/userInputIcon.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/userInputPreviousIcon.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/userInputPreviousIcon.png Binary files differnew file mode 100644 index 0000000..068d572 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/userInputPreviousIcon.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/userInputResultIcon.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/userInputResultIcon.png Binary files differnew file mode 100644 index 0000000..794a5ca --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/userInputResultIcon.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/warningIcon.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/warningIcon.png Binary files differnew file mode 100644 index 0000000..d5e4c82 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/warningIcon.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/warningMediumIcon.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/warningMediumIcon.png Binary files differnew file mode 100644 index 0000000..291e111 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/warningMediumIcon.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/warningsErrors.png b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/warningsErrors.png Binary files differnew file mode 100644 index 0000000..878b593 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/images/warningsErrors.png diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/inject.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/inject.js new file mode 100644 index 0000000..8a9b199 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/inject.js @@ -0,0 +1,50 @@ +// Copyright (c) 2009 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 Javascript that is being injected into the inspectable page + * while debugging. + */ +goog.provide('devtools.Injected'); + + +/** + * Main injected object. + * @constructor. + */ +devtools.Injected = function() { +}; + + +/** + * Dispatches given method with given args on the host object. + * @param {string} method Method name. + */ +devtools.Injected.prototype.InspectorController = function(method, var_args) { + var args = Array.prototype.slice.call(arguments, 1); + return InspectorController[method].apply(InspectorController, args); +}; + + +/** + * Dispatches given method with given args on the InjectedScript. + * @param {string} method Method name. + */ +devtools.Injected.prototype.InjectedScript = function(method, var_args) { + var args = Array.prototype.slice.call(arguments, 1); + var result = InjectedScript[method].apply(InjectedScript, args); + return result; +}; + + +// Plugging into upstreamed support. +InjectedScript._window = function() { + return contentWindow; +}; + + +// Plugging into upstreamed support. +Object.className = function(obj) { + return (obj == null) ? "null" : obj.constructor.name; +}; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/inspector.css b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/inspector.css new file mode 100644 index 0000000..ea6f661 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/inspector.css @@ -0,0 +1,3302 @@ +/* + * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2009 Anthony Ricaud <rik@webkit.org> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +body { + cursor: default; + height: 100%; + width: 100%; + overflow: hidden; + font-family: Lucida Grande, sans-serif; + font-size: 10px; + margin: 0; + -webkit-text-size-adjust: none; + -webkit-user-select: none; +} + +* { + -webkit-box-sizing: border-box; +} + +:focus { + outline: none; +} + +input[type="search"]:focus, input[type="text"]:focus { + outline: auto 5px -webkit-focus-ring-color; +} + +iframe, a img { + border: none; +} + +img { + -webkit-user-drag: none; +} + +.hidden { + display: none !important; +} + +#toolbar { + position: absolute; + top: 0; + left: 0; + right: 0; + height: 56px; + display: -webkit-box; + padding: 0 5px; + background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(191, 191, 191)), to(rgb(151, 151, 151))); + border-bottom: 1px solid rgb(80, 80, 80); + -webkit-box-orient: horizontal; + -webkit-background-origin: padding; + -webkit-background-clip: padding; +} + +body.inactive #toolbar { + background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(233, 233, 233)), to(rgb(207, 207, 207))); + border-bottom: 1px solid rgb(64%, 64%, 64%); +} + +body.detached.platform-mac-leopard #toolbar { + background: transparent !important; +} + +body.attached #toolbar { + height: 34px; + border-top: 1px solid rgb(100, 100, 100); + cursor: row-resize; + padding-left: 0; +} + +body.attached.inactive #toolbar { + border-top: 1px solid rgb(64%, 64%, 64%); +} + +.toolbar-item { + display: -webkit-box; + padding: 4px 6px; + margin: 0; + background-color: transparent; + border-style: none; + border-color: transparent; + -webkit-box-orient: vertical; + -webkit-box-align: center; + -webkit-box-pack: end; +} + +.toolbar-item.toggleable.toggled-on { + border-width: 0 2px 0 2px; + padding: 4px 4px; + -webkit-border-image: url(Images/toolbarItemSelected.png) 0 2 0 2; +} + +.toolbar-item.flexable-space { + -webkit-box-flex: 1; + visibility: hidden; +} + +.toolbar-item input { + margin-bottom: 8px; +} + +.toolbar-icon { + display: inline-block; + width: 32px; + height: 32px; + -webkit-background-size: 100% auto; +} + +body.attached .toolbar-icon { + width: 24px; + height: 24px; + vertical-align: middle; +} + +.toolbar-item:active .toolbar-icon { + background-position: 0 32px; +} + +body.attached .toolbar-item:active .toolbar-icon { + background-position: 0 24px; +} + +.toolbar-label { + font-size: 11px; + font-family: Lucida Grande, sans-serif; + text-shadow: rgba(255, 255, 255, 0.5) 0 1px 0; +} + +.toolbar-item.toggleable:active .toolbar-label { + text-shadow: none; +} + +body.attached .toolbar-label { + display: inline-block; + vertical-align: middle; + margin-left: 3px; +} + +body.attached #search-toolbar-label { + display: none; +} + +#search { + width: 205px; + font-size: 16px; + margin-bottom: 5px; +} + +body.attached #search { + font-size: 11px; + margin-bottom: 8px; +} + +#search-results-matches { + font-size: 11px; + text-shadow: rgba(255, 255, 255, 0.5) 0 1px 0; + margin-bottom: 22px; +} + +body.attached #search-results-matches { + margin-bottom: 6px; +} + +.toolbar-item.elements .toolbar-icon { + background-image: url(Images/elementsIcon.png); +} + +.toolbar-item.resources .toolbar-icon { + background-image: url(Images/resourcesIcon.png); +} + +.toolbar-item.scripts .toolbar-icon { + background-image: url(Images/scriptsIcon.png); +} + +.toolbar-item.storage .toolbar-icon { + background-image: url(Images/storageIcon.png); +} + +.toolbar-item.profiles .toolbar-icon { + background-image: url(Images/profilesIcon.png); +} + +#close-button { + width: 14px; + height: 14px; + background-image: url(Images/closeButtons.png); + background-position: 0 0; + background-color: transparent; + border: 0 none transparent; + margin: 5px 0; +} + +#close-button:hover { + background-position: 14px 0; +} + +#close-button:active { + background-position: 28px 0; +} + +body.detached .toolbar-item.close { + display: none; +} + +#main { + position: absolute; + z-index: 1; + top: 56px; + left: 0; + right: 0; + bottom: 0; + overflow: hidden; + background-color: white; +} + +body.attached #main { + top: 34px; +} + +#main-panels { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 23px; + overflow: hidden; +} + +#main-status-bar { + position: absolute; + bottom: 0; + left: 0; + right: 0; +} + +body.drawer-visible #main-status-bar { + height: 24px; + background-image: url(Images/statusbarResizerVertical.png), url(Images/statusbarBackground.png); + background-repeat: no-repeat, repeat-x; + background-position: right center, center; + cursor: row-resize; +} + +body.drawer-visible #main-status-bar * { + cursor: default; +} + +body.drawer-visible #main-panels { + bottom: 24px; +} + +.status-bar { + background-color: rgb(235, 235, 235); + background-image: url(Images/statusbarBackground.png); + background-repeat: repeat-x; + white-space: nowrap; + height: 23px; + overflow: hidden; + z-index: 12; +} + +.status-bar > div { + display: inline-block; + vertical-align: top; +} + +.status-bar-item { + display: inline-block; + height: 24px; + padding: 0; + margin-left: -1px; + margin-right: 0; + vertical-align: top; + border: 0 transparent none; + background-color: transparent; +} + +.status-bar-item:active { + position: relative; + z-index: 200; +} + +.glyph { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.75); + z-index: 1; +} + +.glyph.shadow { + top: 1px; + background-color: white !important; + z-index: 0; +} + +button.status-bar-item { + position: relative; + width: 32px; + background-image: url(Images/statusbarButtons.png); + background-position: 0 0; +} + +button.status-bar-item:active { + background-position: 32px 0 !important; +} + +button.status-bar-item .glyph.shadow { + background-color: rgba(255, 255, 255, 0.33) !important; +} + +button.status-bar-item.toggled-on .glyph { + background-color: rgb(66, 129, 235); +} + +button.status-bar-item:disabled { + opacity: 0.5; + background-position: 0 0 !important; +} + +select.status-bar-item { + min-width: 48px; + border-width: 0 17px 0 2px; + padding: 0 2px 0 6px; + font-weight: bold; + color: rgb(48, 48, 48); + text-shadow: rgba(255, 255, 255, 0.75) 0 1px 0; + -webkit-border-image: url(Images/statusbarMenuButton.png) 0 17 0 2; + -webkit-border-radius: 0; + -webkit-appearance: none; +} + +select.status-bar-item:active { + color: black; + -webkit-border-image: url(Images/statusbarMenuButtonSelected.png) 0 17 0 2; +} + +#dock-status-bar-item .glyph { + -webkit-mask-image: url(Images/undockButtonGlyph.png); +} + +body.detached #dock-status-bar-item .glyph { + -webkit-mask-image: url(Images/dockButtonGlyph.png); +} + +#console-status-bar-item .glyph { + -webkit-mask-image: url(Images/consoleButtonGlyph.png); +} + +#clear-console-status-bar-item .glyph { + -webkit-mask-image: url(Images/clearConsoleButtonGlyph.png); +} + +#changes-status-bar-item .glyph { + -webkit-mask-image: url(Images/consoleButtonGlyph.png); /* TODO: Needs Image for Changes Toggle Button */ +} + +#clear-changes-status-bar-item .glyph { + -webkit-mask-image: url(Images/clearConsoleButtonGlyph.png); +} + +#count-items { + position: absolute; + right: 16px; + top: 0; + cursor: pointer; + padding: 6px 2px; + font-size: 10px; + height: 19px; +} + +#changes-count, #error-warning-count { + display: inline; +} + +#error-warning-count:hover, #changes-count:hover { + border-bottom: 1px solid rgb(96, 96, 96); +} + +#style-changes-count::before { + content: url(Images/styleIcon.png); /* TODO: Needs Image for Style Changes Icon */ + width: 10px; + height: 10px; + vertical-align: -1px; + margin-right: 2px; +} + +#error-count::before { + content: url(Images/errorIcon.png); + width: 10px; + height: 10px; + vertical-align: -1px; + margin-right: 2px; +} + +#changes-count + #error-warning-count, #error-count + #warning-count { + margin-left: 6px; +} + +#warning-count::before { + content: url(Images/warningIcon.png); + width: 10px; + height: 10px; + vertical-align: -1px; + margin-right: 2px; +} + +#drawer { + display: none; + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 200px; + background-color: white; + background-image: url(Images/statusbarBottomBackground.png); + background-repeat: repeat-x; + background-position: bottom; +} + +body.drawer-visible #drawer { + display: block; +} + +#drawer-status-bar { + position: absolute; + bottom: 0; + left: 0; + right: 0; + background: none; +} + +#console-messages { + position: absolute; + z-index: 0; + top: 0; + left: 0; + right: 0; + bottom: 23px; + font-size: initial; + font-family: monospace; + padding: 2px 0; + overflow-y: overlay; + -webkit-user-select: text; + -webkit-text-size-adjust: auto; +} + +#console-prompt { + position: relative; + padding: 1px 22px 1px 24px; + min-height: 16px; + white-space: pre-wrap; + -webkit-user-modify: read-write-plaintext-only; +} + +#console-prompt::before { + background-image: url(Images/userInputIcon.png); +} + +.console-user-command-result.console-log-level::before { + background-image: url(Images/userInputResultIcon.png); +} + +.console-message, .console-user-command { + position: relative; + border-bottom: 1px solid rgb(240, 240, 240); + padding: 1px 22px 1px 24px; + min-height: 16px; +} + +.console-adjacent-user-command-result { + border-bottom: none; +} + +.console-adjacent-user-command-result + .console-user-command-result.console-log-level::before { + background-image: none; +} + +.console-message::before, .console-user-command::before, #console-prompt::before, .console-group-title::before { + position: absolute; + display: block; + content: ""; + left: 7px; + top: 0.8em; + width: 10px; + height: 10px; + margin-top: -5px; + -webkit-user-select: none; +} + +.console-message .bubble { + display: inline-block; + height: 14px; + background-color: rgb(128, 151, 189); + vertical-align: middle; + white-space: nowrap; + padding: 1px 4px; + margin-top: -2px; + margin-right: 4px; + text-align: left; + font-size: 11px; + font-family: Helvetia, Arial, sans-serif; + font-weight: bold; + text-shadow: none; + color: white; + -webkit-border-radius: 7px; +} + +.console-message-text { + white-space: pre-wrap; +} + +.repeated-message { + padding-left: 6px; +} + +.repeated-message.console-error-level::before, .repeated-message.console-warning-level:before, .repeated-message.console-debug-level:before { + visibility: hidden; +} + +.console-group .console-group > .console-group-messages { + margin-left: 16px; +} + +.console-group-title { + font-weight: bold; +} + +.console-group-title::before { + background-image: url(Images/disclosureTriangleSmallDown.png); + top: 0.6em; + width: 11px; + height: 12px; +} + +.console-group.collapsed .console-group-title::before { + background-image: url(Images/disclosureTriangleSmallRight.png); +} + +.console-group.collapsed > .console-group-messages { + display: none; +} + +.console-error-level .console-message-text { + color: red; +} + +.console-debug-level .console-message-text { + color: blue; +} + +.console-debug-level::before { + background-image: url(Images/searchSmallBrightBlue.png); +} + +.console-error-level::before { + background-image: url(Images/errorIcon.png); +} + +.console-warning-level::before { + background-image: url(Images/warningIcon.png); +} + +.console-user-command .console-message { + margin-left: -24px; + padding-right: 0; + border-bottom: none; +} + +.console-user-command::before { + background-image: url(Images/userInputPreviousIcon.png); +} + +.console-user-command > .console-message-text { + color: rgb(0, 128, 255); +} + +#console-messages a { + color: rgb(33%, 33%, 33%); + cursor: pointer; +} + +#console-messages a:hover { + color: rgb(15%, 15%, 15%); +} + +.console-message-url { + float: right; +} + +.console-group-messages .section { + margin: 0 0 0 12px !important; +} + +.console-group-messages .section .header { + padding: 0 8px 0 0; + background-image: none; + border: none; + min-height: 0; +} + +.console-group-messages .section .header::before { + position: absolute; + top: 1px; + left: 1px; + width: 8px; + height: 8px; + content: url(Images/treeRightTriangleBlack.png); +} + +.console-group-messages .section.expanded .header::before { + content: url(Images/treeDownTriangleBlack.png); +} + +.console-group-messages .section .header .title { + color: black; + font-weight: normal; +} + +.console-group-messages .section .properties li .info { + padding-top: 0; + padding-bottom: 0; + color: rgb(60%, 60%, 60%); +} + +.console-group-messages .outline-disclosure { + padding-left: 0; +} + +.console-group-messages .outline-disclosure > ol { + padding: 0 0 0 12px !important; +} + +.console-group-messages .outline-disclosure, .console-group-messages .outline-disclosure ol { + font-size: inherit; + line-height: 1em; +} + +.console-group-messages .outline-disclosure.single-node li { + padding-left: 2px; +} + +.console-group-messages .outline-disclosure li .selection { + margin-left: -6px; + margin-right: -6px; +} + +.console-group-messages .add-attribute { + display: none; +} + +.console-formatted-object, .console-formatted-node { + position: relative; + display: inline-block; + vertical-align: top; +} + +.console-formatted-object .section, .console-formatted-node .section { + position: static; +} + +.console-formatted-object .properties, .console-formatted-node .properties { + padding-left: 0 !important; +} + +.error-message { + color: red; +} + +.auto-complete-text { + color: rgb(128, 128, 128); + -webkit-user-select: none; + -webkit-user-modify: read-only; +} + +.panel { + display: none; + overflow: hidden; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; +} + +.panel.visible { + display: block; +} + +.resource-view { + display: none; + overflow: hidden; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + overflow: hidden; +} + +.resource-view.visible { + display: block; +} + +.resource-view.headers-visible { + overflow-y: auto; + overflow-x: hidden; +} + +.resource-view-headers { + display: none; + padding: 6px; + border-bottom: 1px solid rgb(64%, 64%, 64%); + background-color: white; + -webkit-user-select: text; +} + +.resource-view-headers .outline-disclosure .parent { + -webkit-user-select: none; + font-weight: bold; +} + +.resource-view.headers-visible .resource-view-headers { + display: block; +} + +.resource-view-headers .outline-disclosure .children li { + white-space: nowrap; +} + +.resource-view-headers .outline-disclosure li.expanded .header-count { + display: none; +} + +.resource-view-headers .outline-disclosure .header-name { + color: rgb(33%, 33%, 33%); + display: inline-block; + width: 105px; + text-align: right; + margin-right: 0.5em; + font-weight: bold; + vertical-align: top; + overflow: hidden; + text-overflow: ellipsis; +} + +.resource-view-headers .outline-disclosure .header-value { + display: inline-block; + white-space: normal; + word-break: break-word; + vertical-align: top; + margin-right: 100px; +} + +.resource-view-headers .outline-disclosure .raw-form-data { + white-space:pre-wrap; +} + +.resource-view .resource-view-content { + position: absolute; + top: 0; + right: 0; + left: 0; + bottom: 0; +} + +.resource-view.headers-visible .resource-view-content { + position: relative; + top: auto; + right: auto; + left: auto; + bottom: auto; +} + +.resource-view.headers-visible .source-view-frame { + height: auto; + vertical-align: top; +} + +.invisible { + color: inherit; + text-decoration: none; +} + +.webkit-line-gutter-backdrop { + /* Keep this in sync with view-source.css (.webkit-line-gutter-backdrop) */ + width: 31px; + background-color: rgb(240, 240, 240); + border-right: 1px solid rgb(187, 187, 187); + position: absolute; + z-index: -1; + left: 0; + top: 0; + height: 100% +} + +.resource-view.font .resource-view-content { + font-size: 60px; + white-space: pre-wrap; + word-wrap: break-word; + text-align: center; + padding: 15px; +} + +.resource-view.image .resource-view-content > .image { + padding: 20px 20px 10px 20px; + text-align: center; +} + +.resource-view.image .resource-view-content > .info { + padding-bottom: 10px; + font-size: 11px; + -webkit-user-select: text; +} + +.resource-view.image img { + max-width: 100%; + max-height: 1000px; + background-image: url(Images/checker.png); + -webkit-box-shadow: 0px 5px 10px rgba(0, 0, 0, 0.5); + -webkit-user-select: text; + -webkit-user-drag: auto; +} + +.resource-view.image .title { + text-align: center; + font-size: 13px; +} + +.resource-view.image .infoList { + margin: 0; +} + +.resource-view.image .infoList dt { + font-weight: bold; + display: inline-block; + width: 50%; + text-align: right; + color: rgb(76, 76, 76); +} + +.resource-view.image .infoList dd { + display: inline-block; + padding-left: 8px; + width: 50%; + text-align: left; + margin: 0; +} + +.resource-view.image .infoList dd::after { + white-space: pre; + content: "\A"; +} + +#elements-content { + display: block; + overflow: auto; + padding: 0; + position: absolute; + top: 0; + left: 0; + right: 225px; + bottom: 0; +} + +#elements-sidebar { + position: absolute; + top: 0; + right: 0; + bottom: 0; + width: 225px; + background-color: rgb(245, 245, 245); + border-left: 1px solid rgb(64%, 64%, 64%); + cursor: default; + overflow: auto; +} + +.crumbs { + display: inline-block; + font-size: 11px; + line-height: 19px; + text-shadow: rgba(255, 255, 255, 0.75) 0 1px 0; + color: rgb(20, 20, 20); + margin-left: -1px; + padding-right: 12px; +} + +.crumbs .crumb { + height: 24px; + border-width: 0 12px 0 2px; + -webkit-border-image: url(Images/segment.png) 0 12 0 2; + margin-right: -12px; + padding-left: 18px; + padding-right: 2px; + white-space: nowrap; + line-height: 23px; + float: right; +} + +.crumbs .crumb.collapsed > * { + display: none; +} + +.crumbs .crumb.collapsed::before { + content: "\2026"; + font-weight: bold; +} + +.crumbs .crumb.compact .extra { + display: none; +} + +.crumbs .crumb.dimmed { + color: rgba(0, 0, 0, 0.45); +} + +.crumbs .crumb.start { + padding-left: 7px; +} + +.crumbs .crumb.end { + border-width: 0 2px 0 2px; + padding-right: 6px; + -webkit-border-image: url(Images/segmentEnd.png) 0 2 0 2; +} + +.crumbs .crumb.selected { + -webkit-border-image: url(Images/segmentSelected.png) 0 12 0 2; + color: black; + text-shadow: rgba(255, 255, 255, 0.5) 0 1px 0; +} + +.crumbs .crumb.selected:hover { + -webkit-border-image: url(Images/segmentSelected.png) 0 12 0 2; +} + +.crumbs .crumb.selected.end, .crumbs .crumb.selected.end:hover { + -webkit-border-image: url(Images/segmentSelectedEnd.png) 0 2 0 2; +} + +.crumbs .crumb:hover { + -webkit-border-image: url(Images/segmentHover.png) 0 12 0 2; + color: black; +} + +.crumbs .crumb.dimmed:hover { + -webkit-border-image: url(Images/segmentHover.png) 0 12 0 2; + color: rgba(0, 0, 0, 0.75); +} + +.crumbs .crumb.end:hover { + -webkit-border-image: url(Images/segmentHoverEnd.png) 0 2 0 2; +} + +.outline-disclosure li.hovered:not(.selected) .selection { + display: block; + left: 3px; + right: 3px; + background-color: rgba(56, 121, 217, 0.1); + -webkit-border-radius: 5px; +} + +.outline-disclosure li.highlighted .highlight { + background-color: rgb(255, 230, 179); + -webkit-border-radius: 4px; + padding-bottom: 2px; + margin-bottom: -2px; +} + +.outline-disclosure li.selected.highlighted .highlight { + background-color: transparent; + padding-bottom: 0; + margin-bottom: 0; +} + +.outline-disclosure li .selection { + display: none; + position: absolute; + left: 0; + right: 0; + height: 15px; + z-index: -1; +} + +.outline-disclosure li.selected .selection { + display: block; + background-color: rgb(212, 212, 212); +} + +:focus .outline-disclosure li.selected .selection { + background-color: rgb(56, 121, 217); +} + +.outline-disclosure > ol { + position: relative; + padding: 2px 6px !important; + margin: 0; + color: black; + cursor: default; + min-width: 100%; +} + +.outline-disclosure, .outline-disclosure ol { + list-style-type: none; + font-size: 11px; + -webkit-padding-start: 12px; + margin: 0; +} + +.outline-disclosure li { + padding: 0 0 2px 14px; + margin-top: 1px; + margin-bottom: 1px; + word-wrap: break-word; + text-indent: -2px +} + +:focus .outline-disclosure li.selected { + color: white; +} + +:focus .outline-disclosure li.selected * { + color: inherit; +} + +.outline-disclosure li.parent { + text-indent: -12px +} + +.outline-disclosure li .webkit-html-tag.close { + margin-left: -12px; +} + +.outline-disclosure li.parent::before { + content: url(Images/treeRightTriangleBlack.png); + float: left; + width: 8px; + height: 8px; + margin-top: 1px; + padding-right: 2px; +} + +.outline-disclosure li.parent::before { + content: url(Images/treeRightTriangleBlack.png); +} + +:focus .outline-disclosure li.parent.selected::before { + content: url(Images/treeRightTriangleWhite.png); +} + +.outline-disclosure li.parent.expanded::before { + content: url(Images/treeDownTriangleBlack.png); +} + +:focus .outline-disclosure li.parent.expanded.selected::before { + content: url(Images/treeDownTriangleWhite.png); +} + +.outline-disclosure ol.children { + display: none; +} + +.outline-disclosure ol.children.expanded { + display: block; +} + +.webkit-html-comment { + /* Keep this in sync with view-source.css (.webkit-html-comment) */ + color: rgb(35, 110, 37); +} + +.webkit-html-tag { + /* Keep this in sync with view-source.css (.webkit-html-tag) */ + color: rgb(136, 18, 128); +} + +.webkit-html-doctype { + /* Keep this in sync with view-source.css (.webkit-html-doctype) */ + color: rgb(192, 192, 192); +} + +.webkit-html-attribute-name { + /* Keep this in sync with view-source.css (.webkit-html-attribute-name) */ + color: rgb(153, 69, 0); +} + +.webkit-html-attribute-value { + /* Keep this in sync with view-source.css (.webkit-html-attribute-value) */ + color: rgb(26, 26, 166); +} + +.webkit-html-external-link, .webkit-html-resource-link { + /* Keep this in sync with view-source.css (.webkit-html-external-link, .webkit-html-resource-link) */ + color: #00e; +} + +.webkit-html-external-link { + /* Keep this in sync with view-source.css (.webkit-html-external-link) */ + text-decoration: none; +} + +.webkit-html-external-link:hover { + /* Keep this in sync with view-source.css (.webkit-html-external-link:hover) */ + text-decoration: underline; +} + +.add-attribute { + margin-left: 1px; + margin-right: 1px; +} + +.placard { + position: relative; + margin-top: 1px; + padding: 3px 8px 4px 18px; + min-height: 18px; + white-space: nowrap; +} + +.placard:nth-of-type(2n) { + background-color: rgb(234, 243, 255); +} + +.placard.selected { + border-top: 1px solid rgb(145, 160, 192); + background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(162, 177, 207)), to(rgb(120, 138, 177))); + -webkit-background-origin: padding; + -webkit-background-clip: padding; +} + +:focus .placard.selected { + border-top: 1px solid rgb(68, 128, 200); + background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(92, 147, 213)), to(rgb(21, 83, 170))); +} + +body.inactive .placard.selected { + border-top: 1px solid rgb(151, 151, 151); + background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(180, 180, 180)), to(rgb(138, 138, 138))); +} + +.placard .title { + color: black; + font-weight: normal; + word-wrap: break-word; + white-space: normal; +} + +.placard.selected .title { + color: white; + font-weight: bold; +} + +.placard .subtitle { + float: right; + font-size: 10px; + margin-left: 5px; + max-width: 55%; + color: rgba(0, 0, 0, 0.7); + text-overflow: ellipsis; + overflow: hidden; +} + +.placard.selected .subtitle { + color: rgba(255, 255, 255, 0.7); +} + +.placard .subtitle a { + color: inherit; +} + +.section { + position: relative; + margin-top: 1px; +} + +.section:nth-last-of-type(1) { + margin-bottom: 1px; +} + +.watch-expressions-buttons-container { + text-align: center; +} + +.section .header { + padding: 2px 8px 4px 18px; + border-top: 1px solid rgb(145, 160, 192); + background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(162, 177, 207)), to(rgb(120, 138, 177))); + min-height: 18px; + white-space: nowrap; + -webkit-background-origin: padding; + -webkit-background-clip: padding; +} + +.section.no-affect .header { + background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(167, 167, 167)), to(rgb(123, 123, 123))) +} + +.section .header::before { + position: absolute; + top: 4px; + left: 7px; + width: 8px; + height: 8px; + content: url(Images/treeRightTriangleWhite.png); +} + +.section.blank-section .header::before { + display: none; +} + +.section.expanded .header::before { + content: url(Images/treeDownTriangleWhite.png); +} + +.section .header .title { + color: white; + font-weight: bold; + word-wrap: break-word; + white-space: normal; +} + +.section .header .title.blank-title { + font-style: italic; +} + +.section .header label { + display: none; +} + +.section.expanded .header label { + display: inline; +} + +.section .header input[type=checkbox] { + height: 10px; + width: 10px; + margin-left: 0; + margin-top: 0; + margin-bottom: 0; + vertical-align: 2px; +} + +.section .header .subtitle { + float: right; + font-size: 10px; + margin-left: 5px; + max-width: 55%; + color: rgba(255, 255, 255, 0.7); + text-overflow: ellipsis; + overflow: hidden; +} + +.section .header .subtitle a { + color: inherit; +} + +.section .properties { + display: none; + margin: 0; + padding: 2px 6px 3px; + list-style: none; + background-color: white; + min-height: 18px; +} + +.section.no-affect .properties li { + opacity: 0.5; +} + +.section.no-affect .properties li.editing { + opacity: 1.0; +} + +.section.expanded .properties { + display: block; +} + +.section .properties li { + margin-left: 12px; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + -webkit-user-select: text; + cursor: auto; +} + +.section .properties li.parent { + margin-left: 1px; +} + +.section .properties ol { + display: none; + margin: 0; + -webkit-padding-start: 12px; + list-style: none; +} + +.section .properties ol.expanded { + display: block; +} + +.section .properties li.parent::before { + content: url(Images/treeRightTriangleBlack.png); + opacity: 0.75; + float: left; + width: 8px; + height: 8px; + margin-top: 0; + padding-right: 3px; + -webkit-user-select: none; + cursor: default; +} + +.section .properties li.parent.expanded::before { + content: url(Images/treeDownTriangleBlack.png); + margin-top: 1px; +} + +.section .properties li .info { + padding-top: 4px; + padding-bottom: 3px; +} + +.editing { + -webkit-user-select: text; + -webkit-box-shadow: rgba(0, 0, 0, .5) 3px 3px 4px; + outline: 1px solid rgb(66%, 66%, 66%) !important; + background-color: white; + -webkit-user-modify: read-write-plaintext-only; + text-overflow: clip; + padding-left: 2px; + margin-left: -2px; + padding-right: 2px; + margin-right: -2px; + margin-bottom: -1px; + padding-bottom: 1px; + opacity: 1.0 !important; +} + +.editing, .editing * { + color: black !important; + text-decoration: none !important; +} + +.section .properties li.editing { + margin-left: 10px; + text-overflow: clip; +} + +li.editing .swatch, li.editing .enabled-button, li.editing-sub-part .delete-button { + display: none !important; +} + +.watch-expressions > li.editing-sub-part .name { + display: block; + width: 100%; +} + +.watch-expressions > li.editing-sub-part .value, .watch-expressions > li.editing-sub-part .separator { + display: none; +} + +.section .properties li.editing-sub-part { + padding: 3px 6px 8px 18px; + margin: -3px -6px -8px -6px; + text-overflow: clip; +} + +.section .properties .overloaded, .section .properties .disabled { + text-decoration: line-through; +} + +.section.computed-style .properties .disabled { + text-decoration: none; + opacity: 0.5; +} + +.section .properties .implicit, .section .properties .inherited { + opacity: 0.5; +} + +.section:not(.show-inherited) .properties .inherited { + display: none; +} + +.section .properties .enabled-button { + display: none; + float: right; + font-size: 10px; + margin: 0 0 0 4px; + vertical-align: top; + position: relative; + z-index: 1; +} + +/* FIXME: need a better icon (comment in bug 27514) */ +.section .properties .delete-button { + width: 10px; + height: 10px; + background-image: url(Images/errorIcon.png); + background-position: 0 0; + background-color: transparent; + background-repeat: no-repeat; + border: 0 none transparent; +} + +.section:hover .properties .enabled-button { + display: block; +} + +.section .properties .name { + color: rgb(136, 19, 145); +} + +.section .properties .value.dimmed { + color: rgb(100, 100, 100); +} + +.section .properties .number { + color: blue; +} + +.section .properties .priority { + color: rgb(128, 0, 0); +} + +.section .properties .keyword { + color: rgb(136, 19, 79); +} + +.section .properties .color { + color: rgb(118, 15, 21); +} + +.swatch { + display: inline-block; + vertical-align: baseline; + margin-left: 1px; + margin-right: 2px; + margin-bottom: -1px; + width: 1em; + height: 1em; + border: 1px solid rgba(128, 128, 128, 0.6); +} + +.swatch:hover { + border: 1px solid rgba(64, 64, 64, 0.8); +} + +.pane:not(.expanded) + .pane, .pane:first-of-type { + margin-top: -1px; +} + +.pane > .title { + background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(243, 243, 243)), color-stop(0.05, rgb(243, 243, 243)), color-stop(0.05, rgb(230, 230, 230)), to(rgb(209, 209, 209))); + height: 20px; + padding: 0 5px; + border-top: 1px solid rgb(189, 189, 189); + border-bottom: 1px solid rgb(189, 189, 189); + font-weight: bold; + font-size: 12px; + line-height: 18px; + color: rgb(110, 110, 110); + text-shadow: white 0 1px 0; + -webkit-background-origin: padding; + -webkit-background-clip: padding; +} + +.pane > .title:active { + background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(231, 231, 231)), color-stop(0.05, rgb(231, 231, 231)), color-stop(0.05, rgb(207, 207, 207)), to(rgb(186, 186, 186))); + border-top: 1px solid rgb(178, 178, 178); + border-bottom: 1px solid rgb(178, 178, 178); +} + +.pane > .title::before { + content: url(Images/disclosureTriangleSmallRightBlack.png); + float: left; + width: 11px; + height: 12px; + margin-right: 2px; + margin-top: 1px; +} + +.pane.expanded > .title::before { + content: url(Images/disclosureTriangleSmallDownBlack.png); +} + +.pane > .title > select { + display: none; + float: right; + width: 23px; + height: 17px; + color: transparent; + background-color: transparent; + border: none; + background-image: url(Images/paneSettingsButtons.png); + background-repeat: no-repeat; + margin: 1px 0 0 0; + padding: 0; + -webkit-border-radius: 0; + -webkit-appearance: none; +} + +.pane.expanded:hover > .title > select { + display: inline-block; +} + +.pane > .title > select:hover { + background-position: -23px 0px; +} + +.pane > .title > select:active { + background-position: -46px 0px; +} + +.pane > .body { + position: relative; + display: none; + background-color: white; + overflow-y: auto; + overflow-x: hidden; +} + +.pane > .body .info { + text-align: center; + font-style: italic; + font-size: 10px; + padding: 6px; + color: gray; +} + +.pane.expanded > .body, .pane.expanded > .growbar { + display: block; +} + +.pane.expanded:nth-last-of-type(1) { + border-bottom: 1px solid rgb(189, 189, 189); +} + +.pane > .growbar { + display: none; + background-image: url(Images/paneGrowHandleLine.png), url(Images/paneBottomGrow.png); + background-repeat: no-repeat, repeat-x; + background-position: center center, bottom; + height: 5px; +} + +.metrics { + padding: 8px; + font-size: 10px; + text-align: center; + white-space: nowrap; +} + +.metrics .label { + position: absolute; + margin-top: -10px; + font-size: 9px; + color: grey; + background-color: white; + margin-left: 3px; + padding-left: 2px; + padding-right: 2px; +} + +.metrics .position { + border: 1px rgb(66%, 66%, 66%) dotted; + display: inline-block; + text-align: center; + padding: 3px; + margin: 3px; +} + +.metrics .margin { + border: 1px dashed; + display: inline-block; + text-align: center; + vertical-align: middle; + padding: 3px; + margin: 3px; +} + +.metrics .border { + border: 1px black solid; + display: inline-block; + text-align: center; + vertical-align: middle; + padding: 3px; + margin: 3px; +} + +.metrics .padding { + border: 1px grey dashed; + display: inline-block; + text-align: center; + vertical-align: middle; + padding: 3px; + margin: 3px; +} + +.metrics .content { + position: static; + border: 1px grey solid; + display: inline-block; + text-align: center; + vertical-align: middle; + padding: 3px; + margin: 3px; + min-width: 80px; + text-align: center; + overflow: visible; +} + +.metrics .content span { + display: inline-block; +} + +.metrics .editing { + position: relative; + z-index: 100; +} + +.metrics .left { + display: inline-block; + vertical-align: middle; +} + +.metrics .right { + display: inline-block; + vertical-align: middle; +} + +.metrics .top { + display: inline-block; +} + +.metrics .bottom { + display: inline-block; +} + +.sidebar { + position: absolute; + top: 0; + left: 0; + bottom: 0; + width: 200px; + overflow-y: auto; + overflow-x: hidden; + background-color: rgb(214, 221, 229); + border-right: 1px solid rgb(64%, 64%, 64%); +} + +body.inactive .sidebar { + background-color: rgb(232, 232, 232); +} + +.database-sidebar-tree-item .icon { + content: url(Images/database.png); +} + +.database-table-sidebar-tree-item .icon { + content: url(Images/databaseTable.png); +} + +.domstorage-sidebar-tree-item.local-storage .icon { + content: url(Images/localStorage.png); +} + +.domstorage-sidebar-tree-item.session-storage .icon { + content: url(Images/sessionStorage.png); +} + +.cookie-sidebar-tree-item .icon { + content: url(Images/cookie.png); +} + +#storage-views { + position: absolute; + top: 0; + right: 0; + left: 200px; + bottom: 0; +} + +.storage-view { + display: none; + overflow: hidden; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; +} + +.storage-view.visible { + display: block; +} + +.storage-view.table { + overflow: hidden; +} + +.storage-view.table .data-grid { + border: none; + height: 100%; +} + +.storage-view.table .storage-table-empty, .storage-view.table .storage-table-error { + position: absolute; + top: 0; + bottom: 25%; + left: 0; + right: 0; + font-size: 24px; + color: rgb(75%, 75%, 75%); + margin-top: auto; + margin-bottom: auto; + height: 50px; + line-height: 26px; + text-align: center; + font-weight: bold; + padding: 10px; + white-space: pre-wrap; +} + +.storage-view.table .storage-table-error { + color: rgb(66%, 33%, 33%); +} + +.data-grid { + position: relative; + border: 1px solid #aaa; +} + +.data-grid .highlight { + background-color: rgb(255, 230, 179); +} + +.data-grid tr.selected .highlight { + background-color: transparent; +} + +.data-grid table { + table-layout: fixed; + border-spacing: 0; + border-collapse: collapse; + width: 100%; + font-size: 10px; + font-family: Lucida Grande, sans-serif; +} + +.data-grid .data-container { + position: absolute; + top: 16px; + bottom: 0; + left: 0; + right: 0; + padding-right: 14px; + overflow-x: hidden; + overflow-y: overlay; + background-image: -webkit-gradient(linear, left top, left bottom, from(white), color-stop(0.5, white), color-stop(0.5, rgb(234, 243, 255)), to(rgb(234, 243, 255))); + -webkit-background-size: 1px 32px; +} + +.data-grid.inline .data-container { + position: static; +} + +.data-grid th { + text-align: left; + background-image: url(Images/glossyHeader.png); + background-repeat: repeat-x; + border-right: 1px solid rgb(179, 179, 179); + border-bottom: 1px solid rgb(179, 179, 179); + height: 15px; + font-weight: normal; + vertical-align: middle; + padding: 0 4px; + white-space: nowrap; +} + +.data-grid th.corner { + width: 15px; + border-right: 0 none transparent; +} + +.data-grid tr.filler { + display: table-row !important; + height: auto !important; +} + +.data-grid tr.filler td { + height: auto !important; + padding: 0 !important; +} + +.data-grid table.data { + position: absolute; + left: 0; + top: 0; + right: 16px; + bottom: 0; + height: 100%; + border-top: 0 none transparent; + background-image: -webkit-gradient(linear, left top, left bottom, from(white), color-stop(0.5, white), color-stop(0.5, rgb(234, 243, 255)), to(rgb(234, 243, 255))); + -webkit-background-size: 1px 32px; +} + +.data-grid.inline table.data { + position: static; +} + +.data-grid table.data tr { + display: none; +} + +.data-grid table.data tr.revealed { + display: table-row; +} + +.data-grid td { + vertical-align: top; + height: 12px; + padding: 2px 4px; + white-space: nowrap; + border-right: 1px solid #aaa; + -webkit-user-select: text; +} + +.data-grid td > div, .data-grid th > div { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +} + +.data-grid .centered div { + text-align: center; +} + +.data-grid .right div { + text-align: right; +} + +.data-grid th.sortable div { + position: relative; +} + +.data-grid th.sortable:active { + background-image: url(Images/glossyHeaderPressed.png); +} + +.data-grid th.sort-ascending, .data-grid th.sort-descending { + border-right: 1px solid rgb(107, 140, 196); + border-bottom: 1px solid rgb(107, 140, 196); + background-image: url(Images/glossyHeaderSelected.png); + background-repeat: repeat-x; +} + +.data-grid th.sortable.sort-ascending:active, .data-grid th.sortable.sort-descending:active { + background-image: url(Images/glossyHeaderSelectedPressed.png); +} + +.data-grid th.sort-ascending div::after { + position: absolute; + top: 0; + right: 0; + width: 8px; + height: 8px; + content: url(Images/treeUpTriangleBlack.png); +} + +.data-grid th.sort-descending div::after { + position: absolute; + top: 0; + right: 0; + margin-top: 1px; + width: 8px; + height: 8px; + content: url(Images/treeDownTriangleBlack.png); +} + +body.inactive .data-grid th.sort-ascending, body.inactive .data-grid th.sort-descending { + background-image: url(Images/glossyHeader.png); + border-right: 1px solid rgb(179, 179, 179); + border-bottom: 1px solid rgb(179, 179, 179); +} + +.data-grid tr.parent td.disclosure::before { + float: left; + content: url(Images/treeRightTriangleBlack.png); + width: 8px; + height: 8px; + margin-right: 2px; + -webkit-user-select: none; +} + +.data-grid tr.expanded td.disclosure::before { + content: url(Images/treeDownTriangleBlack.png); + width: 8px; + height: 8px; + margin-top: 1px; +} + +.data-grid tr.selected { + background-color: rgb(212, 212, 212); + color: inherit; +} + +.data-grid:focus tr.selected { + background-color: rgb(56, 121, 217); + color: white; +} + +.data-grid:focus tr.parent.selected td.disclosure::before { + content: url(Images/treeRightTriangleWhite.png); +} + +.data-grid:focus tr.expanded.selected td.disclosure::before { + content: url(Images/treeDownTriangleWhite.png); +} + +.data-grid tr:not(.parent) td.disclosure { + text-indent: 10px; +} + +.data-grid-resizer { + position: absolute; + top: 0; + bottom: 0; + width: 5px; + z-index: 500; + cursor: col-resize; +} + +.storage-view.query { + font-size: initial; + font-family: monospace; + padding: 2px 0; + overflow-y: overlay; + overflow-x: hidden; + -webkit-text-size-adjust: auto; +} + +.database-query-prompt { + position: relative; + padding: 1px 22px 1px 24px; + min-height: 16px; + white-space: pre-wrap; + -webkit-user-modify: read-write-plaintext-only; + -webkit-user-select: text; +} + +.database-user-query::before, .database-query-prompt::before, .database-query-result::before { + position: absolute; + display: block; + content: ""; + left: 7px; + top: 0.8em; + width: 10px; + height: 10px; + margin-top: -5px; + -webkit-user-select: none; +} + +.database-query-prompt::before { + background-image: url(Images/userInputIcon.png); +} + +.database-user-query { + position: relative; + border-bottom: 1px solid rgb(245, 245, 245); + padding: 1px 22px 1px 24px; + min-height: 16px; +} + +.database-user-query::before { + background-image: url(Images/userInputPreviousIcon.png); +} + +.database-query-text { + color: rgb(0, 128, 255); + -webkit-user-select: text; +} + +.database-query-result { + position: relative; + padding: 1px 22px 1px 24px; + min-height: 16px; + margin-left: -24px; + padding-right: 0; +} + +.database-query-result.error { + color: red; + -webkit-user-select: text; +} + +.database-query-result.error::before { + background-image: url(Images/errorIcon.png); +} + +.panel-enabler-view { + z-index: 1000; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: white; + font-size: 13px; + text-align: center; + overflow-x: hidden; + overflow-y: overlay; + display: none; +} + +.panel-enabler-view.visible { + display: block; +} + +.panel-enabler-view .panel-enabler-view-content { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + max-height: 390px; + margin: auto; + white-space: nowrap; +} + +.panel-enabler-view h1 { + color: rgb(110, 116, 128); + font-size: 16px; + line-height: 20px; + font-weight: normal; + margin-top: 0; +} + +.panel-enabler-disclaimer { + font-size: 10px; + color: rgb(110, 116, 128); + margin-bottom: 12px; + margin-left: 20px; +} + +.panel-enabler-disclaimer:empty { + display: none; +} + +.panel-enabler-view img { + height: 100%; + min-height: 200px; + max-width: 100%; + top: 0; + bottom: 0; + padding: 20px 0 20px 20px; + margin: auto; + vertical-align: middle; +} + +.panel-enabler-view img.hidden { + display: initial !important; + width: 0; +} + +.panel-enabler-view form { + display: inline-block; + vertical-align: middle; + width: 330px; + margin: 0; + padding: 15px; + white-space: normal; +} + +.panel-enabler-view label { + position: relative; + display: block; + text-align: left; + word-break: break-word; + margin: 0 0 5px 20px; +} + +.panel-enabler-view button { + font-size: 13px; + margin: 6px 0 0 0; + padding: 3px 20px; + color: rgb(6, 6, 6); + height: 24px; + background-color: transparent; + border: 1px solid rgb(165, 165, 165); + background-color: rgb(237, 237, 237); + background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(252, 252, 252)), to(rgb(223, 223, 223))); + -webkit-border-radius: 12px; + -webkit-appearance: none; +} + +.panel-enabler-view button:active { + background-color: rgb(215, 215, 215); + background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(194, 194, 194)), to(rgb(239, 239, 239))); +} + +body.inactive .panel-enabler-view button, .panel-enabler-view button:disabled { + color: rgb(130, 130, 130); + border-color: rgb(212, 212, 212); + background-color: rgb(239, 239, 239); + background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(250, 250, 250)), to(rgb(235, 235, 235))); +} + +.panel-enabler-view input { + height: 17px; + width: 17px; + border: 1px solid rgb(165, 165, 165); + background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(252, 252, 252)), to(rgb(223, 223, 223))); + -webkit-border-radius: 8px; + -webkit-appearance: none; + vertical-align: middle; + margin: 0 5px 5px 0; +} + +.panel-enabler-view input:active { + background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(194, 194, 194)), to(rgb(239, 239, 239))); +} + +.panel-enabler-view input:checked { + background: url(Images/radioDot.png) center no-repeat, + -webkit-gradient(linear, left top, left bottom, from(rgb(252, 252, 252)), to(rgb(223, 223, 223))); +} + +.panel-enabler-view.resources img { + content: url(Images/resourcesSilhouette.png); +} + +.panel-enabler-view.scripts img { + content: url(Images/scriptsSilhouette.png); +} + +.panel-enabler-view.profiles img { + content: url(Images/profilesSilhouette.png); +} + +button.enable-toggle-status-bar-item .glyph { + -webkit-mask-image: url(Images/enableOutlineButtonGlyph.png); +} + +button.enable-toggle-status-bar-item.toggled-on .glyph { + -webkit-mask-image: url(Images/enableSolidButtonGlyph.png); +} + +.scripts-pause-on-exceptions-status-bar-item .glyph { + -webkit-mask-image: url(Images/pauseOnExceptionButtonGlyph.png); +} + +#scripts-status-bar { + position: absolute; + top: -1px; + left: 0; + right: 0; + height: 24px; +} + +#scripts-files { + max-width: 250px; +} + +#scripts-functions { + max-width: 150px; +} + +#scripts-status-bar .status-bar-item img { + margin-top: 2px; +} + +#scripts-back img { + content: url(Images/back.png); +} + +#scripts-forward img { + content: url(Images/forward.png); +} + +#scripts-pause img { + content: url(Images/debuggerPause.png); +} + +#scripts-pause.paused img { + content: url(Images/debuggerContinue.png); +} + +#scripts-step-over img { + content: url(Images/debuggerStepOver.png); +} + +#scripts-step-into img { + content: url(Images/debuggerStepInto.png); +} + +#scripts-step-out img { + content: url(Images/debuggerStepOut.png); +} + +#scripts-debugger-status { + position: absolute; + line-height: 24px; + top: 0; + right: 8px; +} + +#scripts-sidebar-resizer-widget { + position: absolute; + top: 0; + bottom: 0; + right: 225px; + width: 16px; + cursor: col-resize; + background-image: url(Images/statusbarResizerHorizontal.png); + background-repeat: no-repeat; + background-position: center; +} + +#scripts-sidebar-buttons { + position: absolute; + right: 0; + top: 0; + bottom: 0; + width: 225px; + overflow: hidden; + border-left: 1px solid rgb(64%, 64%, 64%); +} + +#script-resource-views { + display: block; + overflow: auto; + padding: 0; + position: absolute; + top: 23px; + left: 0; + right: 225px; + bottom: 0; +} + +.script-view { + display: none; + overflow: hidden; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; +} + +.script-view.visible { + display: block; +} + +#scripts-sidebar { + position: absolute; + top: 23px; + right: 0; + bottom: 0; + width: 225px; + background-color: rgb(245, 245, 245); + border-left: 1px solid rgb(64%, 64%, 64%); + cursor: default; + overflow: auto; +} + +.resources-larger-resources-status-bar-item .glyph { + -webkit-mask-image: url(Images/largerResourcesButtonGlyph.png); +} + +#resources-filter { + height: 24px; + padding: 2px 10px 0; + background: -webkit-gradient(linear, left top, left bottom, from(rgb(233, 233, 233)), to(rgb(207, 207, 207))); + border-bottom: 1px solid rgb(177, 177, 177); + overflow: hidden; +} + +#console-filter { + height: 24px; + padding: 2px 10px 0; + overflow: hidden; +} + +#resources-filter li, #console-filter li { + display: inline-block; + margin: 1px 1px 0 0; + padding: 0 6px 3px; + font-size: 12px; + line-height: 12px; + font-weight: bold; + color: rgb(40, 40, 40); + border: 1px solid transparent; + border-bottom: 0; + background: transparent; + -webkit-border-radius: 8px; + text-shadow: rgba(255, 255, 255, 0.5) 1px 1px 0; +} + +#resources-filter li.selected, #resources-filter li:hover, #resources-filter li:active, +#console-filter li.selected, #console-filter li:hover, #console-filter li:active { + color: white; + text-shadow: rgb(80, 80, 80) 1px 1px 1px; + background: rgba(20, 20, 20, 0.4); + border-color: rgba(20, 20, 20, 0.2); + -webkit-box-shadow: 0 1px 0px rgba(255, 255, 255, 0.5); +} + +#resources-filter li:hover, +#console-filter li:hover { + background: rgba(20, 20, 20, 0.4); + border-color: transparent; + -webkit-box-shadow: none; +} + +#resources-filter li:active, +#console-filter li:active { + background: rgba(20, 20, 20, 0.6); +} + +#resources-container { + position: absolute; + top: 24px; + left: 0; + bottom: 0; + right: 0; + border-right: 0 none transparent; + overflow-y: auto; + overflow-x: hidden; +} + +#resources-container.viewing-resource { + right: auto; + width: 200px; + border-right: 1px solid rgb(64%, 64%, 64%); +} + +#resources-container.viewing-resource #resources-sidebar { + width: 100%; + border-right: 0 none transparent; +} + +#resources-sidebar { + min-height: 100%; + bottom: auto; + overflow: visible; +} + +#resources-container-content { + position: absolute; + top: 0; + right: 0; + left: 200px; + min-height: 100%; +} + +#resources-container.viewing-resource #resources-container-content { + display: none; +} + +#resources-summary { + position: absolute; + padding-top: 20px; + top: 0; + left: 0; + right: 0; + height: 93px; + margin-left: -1px; + border-left: 1px solid rgb(102, 102, 102); + background-color: rgb(101, 111, 130); + background-image: -webkit-gradient(linear, left top, left bottom, from(rgba(0, 0, 0, 0)), to(rgba(0, 0, 0, 0.5))); + background-repeat: repeat-x; + background-position: bottom; + text-align: center; + text-shadow: black 0 1px 1px; + white-space: nowrap; + color: white; + -webkit-background-size: 1px 6px; + -webkit-background-origin: padding; + -webkit-background-clip: padding; +} + +.summary-graph-legend { + margin-top: -10px; + padding-left: 15px; +} + +.summary-graph-legend-item { + display: inline-block; + font-weight: bold; + margin-right: 15px; + vertical-align: top; +} + +.summary-graph-legend-item.total { + margin-left: 10px; +} + +.summary-graph-legend-label { + display: inline-block; + text-align: left; +} + +.summary-graph-legend-header { + font-size: 12px; +} + +.summary-graph-legend-value { + font-size: 10px; +} + +.summary-graph-legend-swatch { + vertical-align: top; + margin-top: 1px; + margin-right: 3px; +} + +#resources-dividers { + position: absolute; + left: 0; + right: 0; + height: 100%; + top: 0; + z-index: -100; +} + +#resources-dividers-label-bar { + position: absolute; + top: 93px; + left: 0px; + right: 0; + background-color: rgba(255, 255, 255, 0.8); + background-clip: padding; + border-bottom: 1px solid rgba(0, 0, 0, 0.3); + height: 20px; + z-index: 200; +} + +.resources-divider { + position: absolute; + width: 1px; + top: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.1); +} + +.resources-divider.last { + background-color: transparent; +} + +.resources-divider-label { + position: absolute; + top: 4px; + right: 3px; + font-size: 9px; + color: rgb(50%, 50%, 50%); + white-space: nowrap; +} + +.resources-graph-label { + position: absolute; + top: 0; + bottom: 0; + margin: auto -7px; + height: 13px; + line-height: 13px; + font-size: 9px; + color: rgba(0, 0, 0, 0.75); + text-shadow: rgba(255, 255, 255, 0.25) 1px 0 0, rgba(255, 255, 255, 0.25) -1px 0 0, rgba(255, 255, 255, 0.333) 0 1px 0, rgba(255, 255, 255, 0.25) 0 -1px 0; + z-index: 150; + overflow: hidden; + text-align: center; + font-weight: bold; + opacity: 0; + -webkit-transition: opacity 250ms ease-in-out; +} + +.resources-graph-side:hover .resources-graph-label { + opacity: 1; +} + +.resources-graph-label:empty { + display: none; +} + +.resources-graph-label.waiting { + margin-right: 5px; +} + +.resources-graph-label.before { + color: rgba(0, 0, 0, 0.7); + text-shadow: none; + text-align: right; + margin-right: 2px; +} + +.resources-graph-label.before::after { + padding-left: 2px; + height: 6px; + content: url(Images/graphLabelCalloutLeft.png); +} + +.resources-graph-label.after { + color: rgba(0, 0, 0, 0.7); + text-shadow: none; + text-align: left; + margin-left: 2px; +} + +.resources-graph-label.after::before { + padding-right: 2px; + height: 6px; + content: url(Images/graphLabelCalloutRight.png); +} + +.resources-graph-bar { + position: absolute; + top: 0; + bottom: 0; + margin: auto -7px; + border-width: 6px 7px; + height: 13px; + min-width: 14px; + opacity: 0.65; + -webkit-border-image: url(Images/timelinePillGray.png) 6 7 6 7; +} + +.resources-category-documents, .resources-category-stylesheets, .resources-category-images, +.resources-category-scripts, .resources-category-xhr, .resources-category-fonts, .resources-category-other { + display: none; +} + +.filter-all .resources-category-documents, .filter-documents .resources-category-documents, +.filter-all .resources-category-stylesheets, .filter-stylesheets .resources-category-stylesheets, +.filter-all .resources-category-images, .filter-images .resources-category-images, +.filter-all .resources-category-scripts, .filter-scripts .resources-category-scripts, +.filter-all .resources-category-xhr, .filter-xhr .resources-category-xhr, +.filter-all .resources-category-fonts, .filter-fonts .resources-category-fonts, +.filter-all .resources-category-other, .filter-other .resources-category-other, +.resource-sidebar-tree-item.selected { + display: list-item; +} + +.console-warning-level, .console-error-level, .console-log-level { + display: none; +} + +.filter-all .console-warning-level, .filter-warnings .console-warning-level, +.filter-all .console-error-level, .filter-errors .console-error-level, +.filter-all .console-log-level, .filter-logs .console-log-level { + display: block; +} + +.resources-graph-bar.waiting { + opacity: 0.35; +} + +.resource-cached .resources-graph-bar { + -webkit-border-image: url(Images/timelineHollowPillGray.png) 6 7 6 7; +} + +.resources-category-documents .resources-graph-bar { + -webkit-border-image: url(Images/timelinePillBlue.png) 6 7 6 7; +} + +.resources-category-documents.resource-cached .resources-graph-bar { + -webkit-border-image: url(Images/timelineHollowPillBlue.png) 6 7 6 7; +} + +.resources-category-stylesheets .resources-graph-bar { + -webkit-border-image: url(Images/timelinePillGreen.png) 6 7 6 7; +} + +.resources-category-stylesheets.resource-cached .resources-graph-bar { + -webkit-border-image: url(Images/timelineHollowPillGreen.png) 6 7 6 7; +} + +.resources-category-images .resources-graph-bar { + -webkit-border-image: url(Images/timelinePillPurple.png) 6 7 6 7; +} + +.resources-category-images.resource-cached .resources-graph-bar { + -webkit-border-image: url(Images/timelineHollowPillPurple.png) 6 7 6 7; +} + +.resources-category-fonts .resources-graph-bar { + -webkit-border-image: url(Images/timelinePillRed.png) 6 7 6 7; +} + +.resources-category-fonts.resource-cached .resources-graph-bar { + -webkit-border-image: url(Images/timelineHollowPillRed.png) 6 7 6 7; +} + +.resources-category-scripts .resources-graph-bar { + -webkit-border-image: url(Images/timelinePillOrange.png) 6 7 6 7; +} + +.resources-category-scripts.resource-cached .resources-graph-bar { + -webkit-border-image: url(Images/timelineHollowPillOrange.png) 6 7 6 7; +} + +.resources-category-xhr .resources-graph-bar { + -webkit-border-image: url(Images/timelinePillYellow.png) 6 7 6 7; +} + +.resources-category-xhr.resource-cached .resources-graph-bar { + -webkit-border-image: url(Images/timelineHollowPillYellow.png) 6 7 6 7; +} + +.tip-button { + background-image: url(Images/tipIcon.png); + border: none; + width: 16px; + height: 16px; + float: right; + background-color: transparent; + margin-top: 1px; +} + +.tip-button:active { + background-image: url(Images/tipIconPressed.png); +} + +.tip-balloon { + position: absolute; + left: 145px; + top: -5px; + z-index: 1000; + border-width: 51px 15px 18px 37px; + -webkit-border-image: url(Images/tipBalloon.png) 51 15 18 37; + width: 265px; +} + +.tip-balloon.bottom { + position: absolute; + left: 145px; + top: auto; + bottom: -7px; + z-index: 1000; + border-width: 18px 15px 51px 37px; + -webkit-border-image: url(Images/tipBalloonBottom.png) 18 15 51 37; +} + +.tip-balloon-content { + margin-top: -40px; + margin-bottom: -2px; + margin-left: 2px; +} + +.tip-balloon.bottom .tip-balloon-content { + margin-top: -10px; + margin-bottom: -35px; +} + +#resource-views { + position: absolute; + top: 24px; + right: 0; + left: 200px; + bottom: 0; +} + +.source-view-frame { + width: 100%; + height: 100%; +} + +.sidebar-resizer-vertical { + position: absolute; + top: 0; + bottom: 0; + width: 5px; + z-index: 500; + cursor: col-resize; +} + +.resources .sidebar-resizer-vertical { + top: 24px; +} + +.sidebar-tree, .sidebar-tree .children { + position: relative; + padding: 0; + margin: 0; + list-style: none; + font-size: 11px; +} + +.sidebar-tree-section { + position: relative; + height: 18px; + padding: 4px 10px 6px 10px; + white-space: nowrap; + margin-top: 1px; + color: rgb(92, 110, 129); + font-weight: bold; + text-shadow: rgba(255, 255, 255, 0.75) 0 1px 0; +} + +.sidebar-tree-item { + position: relative; + height: 36px; + padding: 0 5px 0 5px; + white-space: nowrap; + margin-top: 1px; + line-height: 34px; + border-top: 1px solid transparent; +} + +.sidebar-tree .children { + display: none; +} + +.sidebar-tree .children.expanded { + display: block; +} + +.sidebar-tree-section + .children > .sidebar-tree-item { + padding-left: 10px !important; +} + +.sidebar-tree-section + .children.small > .sidebar-tree-item { + padding-left: 17px !important; +} + +.sidebar-tree > .children > .sidebar-tree-item { + padding-left: 37px; +} + +.sidebar-tree > .children > .children > .sidebar-tree-item { + padding-left: 37px; +} + +.sidebar-tree.hide-disclosure-buttons > .children { + display: none; +} + +.sidebar-tree > .children.hide-disclosure-buttons > .children { + display: none; +} + +.sidebar-tree.some-expandable:not(.hide-disclosure-buttons) > .sidebar-tree-item:not(.parent) .icon { + margin-left: 16px; +} + +.sidebar-tree-item .disclosure-button { + float: left; + width: 16px; + height: 100%; + border: 0; + background-color: transparent; + background-image: url(Images/disclosureTriangleSmallRight.png); + background-repeat: no-repeat; + background-position: center; + -webkit-apearance: none; +} + +.sidebar-tree.hide-disclosure-buttons .sidebar-tree-item .disclosure-button { + display: none; +} + +body.inactive .sidebar-tree-item .disclosure-button { + background-image: url(Images/disclosureTriangleSmallRightBlack.png); +} + +body.inactive .sidebar-tree-item.expanded .disclosure-button { + background-image: url(Images/disclosureTriangleSmallDownBlack.png); +} + +body.inactive .sidebar-tree-item .disclosure-button:active { + background-image: url(Images/disclosureTriangleSmallRightDownBlack.png); +} + +.sidebar-tree-item.selected .disclosure-button { + background-image: url(Images/disclosureTriangleSmallRightWhite.png) !important; +} + +.sidebar-tree-item.expanded .disclosure-button { + background-image: url(Images/disclosureTriangleSmallDown.png); +} + +.sidebar-tree-item.selected.expanded .disclosure-button { + background-image: url(Images/disclosureTriangleSmallDownWhite.png) !important; +} + +.sidebar-tree-item.selected .disclosure-button:active { + background-image: url(Images/disclosureTriangleSmallRightDownWhite.png) !important; +} + +.sidebar-tree-item .disclosure-button:active { + background-image: url(Images/disclosureTriangleSmallRightDown.png); +} + +.sidebar-tree-item .icon { + float: left; + width: 32px; + height: 32px; + margin-top: 1px; + margin-right: 3px; +} + +.sidebar-tree-item .status { + float: right; + height: 16px; + margin-top: 9px; + margin-left: 4px; + line-height: 1em; +} + +.sidebar-tree-item .status:empty { + display: none; +} + +.sidebar-tree-item .status .bubble { + display: inline-block; + height: 14px; + min-width: 16px; + margin-top: 1px; + background-color: rgb(128, 151, 189); + vertical-align: middle; + white-space: nowrap; + padding: 1px 4px; + text-align: center; + font-size: 11px; + font-family: Helvetia, Arial, sans-serif; + font-weight: bold; + text-shadow: none; + color: white; + -webkit-border-radius: 7px; +} + +.sidebar-tree-item .status .bubble:empty { + display: none; +} + +.sidebar-tree-item.selected .status .bubble { + background-color: white !important; + color: rgb(132, 154, 190) !important; +} + +:focus .sidebar-tree-item.selected .status .bubble { + color: rgb(36, 98, 172) !important; +} + +body.inactive .sidebar-tree-item.selected .status .bubble { + color: rgb(159, 159, 159) !important; +} + +.sidebar-tree.small .sidebar-tree-item, .sidebar-tree .children.small .sidebar-tree-item, .sidebar-tree-item.small, .small .resources-graph-side { + height: 20px; +} + +.sidebar-tree.small .sidebar-tree-item .icon, .sidebar-tree .children.small .sidebar-tree-item .icon, .sidebar-tree-item.small .icon { + width: 16px; + height: 16px; +} + +.sidebar-tree.small .sidebar-tree-item .status, .sidebar-tree .children.small .sidebar-tree-item .status, .sidebar-tree-item.small .status { + margin-top: 1px; +} + +.sidebar-tree-item.selected { + color: white; + border-top: 1px solid rgb(145, 160, 192); + background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(162, 177, 207)), to(rgb(120, 138, 177))); + text-shadow: rgba(0, 0, 0, 0.33) 0 1px 0; + font-weight: bold; + -webkit-background-origin: padding; + -webkit-background-clip: padding; +} + +:focus .sidebar-tree-item.selected { + border-top: 1px solid rgb(68, 128, 200); + background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(92, 147, 213)), to(rgb(21, 83, 170))); +} + +body.inactive .sidebar-tree-item.selected { + border-top: 1px solid rgb(151, 151, 151); + background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(180, 180, 180)), to(rgb(138, 138, 138))); +} + +.sidebar-tree-item .titles { + position: relative; + top: 5px; + line-height: 11px; + padding-bottom: 1px; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; +} + +.sidebar-tree-item .titles.no-subtitle { + top: 10px; +} + +.sidebar-tree.small .sidebar-tree-item .titles, .sidebar-tree .children.small .sidebar-tree-item .titles, .sidebar-tree-item.small .titles { + top: 2px; + line-height: normal; +} + +.sidebar-tree:not(.small) .sidebar-tree-item:not(.small) .title::after, .sidebar-tree .children:not(.small) .sidebar-tree-item .title::after { + content: "\A"; + white-space: pre; +} + +.sidebar-tree-item .subtitle { + font-size: 9px; + color: rgba(0, 0, 0, 0.7); +} + +.sidebar-tree.small .sidebar-tree-item .subtitle, .sidebar-tree .children.small .sidebar-tree-item .subtitle, .sidebar-tree-item.small .subtitle { + display: none; +} + +.sidebar-tree-item.selected .subtitle { + color: rgba(255, 255, 255, 0.9); +} + +#resources-graphs { + position: absolute; + left: 0; + right: 0; + max-height: 100%; + top: 112px; +} + +.resources-graph-side { + position: relative; + height: 36px; + padding: 0 5px; + white-space: nowrap; + margin-top: 1px; + border-top: 1px solid transparent; + overflow: hidden; +} + +.resources-graph-bar-area { + position: absolute; + top: 0; + bottom: 0; + right: 8px; + left: 9px; +} + +#resources-container:not(.viewing-resource) .resource-sidebar-tree-item:nth-of-type(2n) { + background-color: rgba(0, 0, 0, 0.05); +} + +#resources-container:not(.viewing-resource) .resources-graph-side:nth-of-type(2n) { + background-color: rgba(0, 0, 0, 0.05); +} + +.resources-time-graph-sidebar-item .icon { + content: url(Images/resourcesTimeGraphIcon.png); +} + +.resources-size-graph-sidebar-item .icon { + content: url(Images/resourcesSizeGraphIcon.png); +} + +.resources-size-graph-sidebar-item .icon { + content: url(Images/resourcesSizeGraphIcon.png); +} + +.resource-sidebar-tree-item .icon { + content: url(Images/resourcePlainIcon.png); +} + +.children.small .resource-sidebar-tree-item .icon { + content: url(Images/resourcePlainIconSmall.png); +} + +.resource-sidebar-tree-item.resources-category-documents .icon { + content: url(Images/resourceDocumentIcon.png); +} + +.children.small .resource-sidebar-tree-item.resources-category-documents .icon { + content: url(Images/resourceDocumentIconSmall.png); +} + +.resource-sidebar-tree-item.resources-category-stylesheets .icon { + content: url(Images/resourceCSSIcon.png); +} + +.children.small .resource-sidebar-tree-item.resources-category-stylesheets .icon { + content: url(Images/resourceDocumentIconSmall.png); +} + +.resource-sidebar-tree-item.resources-category-images .icon { + position: relative; + background-image: url(Images/resourcePlainIcon.png); + background-repeat: no-repeat; + content: ""; +} + +.resource-sidebar-tree-item.resources-category-images .image-resource-icon-preview { + position: absolute; + margin: auto; + top: 3px; + bottom: 4px; + left: 5px; + right: 5px; + max-width: 18px; + max-height: 21px; + min-width: 1px; + min-height: 1px; +} + +.children.small .resource-sidebar-tree-item.resources-category-images .icon { + background-image: url(Images/resourcePlainIconSmall.png); + content: ""; +} + +.children.small .resource-sidebar-tree-item.resources-category-images .image-resource-icon-preview { + top: 2px; + bottom: 1px; + left: 3px; + right: 3px; + max-width: 8px; + max-height: 11px; +} + +.resource-sidebar-tree-item.resources-category-fonts .icon { + content: url(Images/resourcePlainIcon.png); +} + +.children.small .resource-sidebar-tree-item.resources-category-fonts .icon { + content: url(Images/resourcePlainIconSmall.png); +} + +.resource-sidebar-tree-item.resources-category-scripts .icon { + content: url(Images/resourceJSIcon.png); +} + +.children.small .resource-sidebar-tree-item.resources-category-scripts .icon { + content: url(Images/resourceDocumentIconSmall.png); +} + +.resource-sidebar-tree-item.resources-category-xhr .icon { + content: url(Images/resourcePlainIcon.png); +} + +.children.small .resource-sidebar-tree-item.resources-category-xhr .icon { + content: url(Images/resourceDocumentIconSmall.png); +} + +.bubble.debug, .console-debug-level .bubble { + background-color: rgb(0, 0, 255) !important; +} + +.bubble.warning, .console-warning-level .bubble { + background-color: rgb(232, 164, 0) !important; +} + +.bubble.error, .console-error-level .bubble { + background-color: rgb(216, 35, 35) !important; +} + +.bubble.search-matches { + background-image: url(Images/searchSmallWhite.png); + background-repeat: no-repeat; + background-position: 3px 2px; + padding-left: 13px !important; +} + +.sidebar-tree-item.selected .bubble.search-matches { + background-image: url(Images/searchSmallBlue.png); +} + +:focus .sidebar-tree-item.selected .bubble.search-matches { + background-image: url(Images/searchSmallBrightBlue.png); +} + +body.inactive .sidebar-tree-item.selected .bubble.search-matches { + background-image: url(Images/searchSmallGray.png); +} + +/* Profiler Style */ + +#profile-views { + position: absolute; + top: 0; + right: 0; + left: 200px; + bottom: 0; +} + +#profile-view-status-bar-items { + position: absolute; + top: 0; + bottom: 0; + left: 200px; + overflow: hidden; + border-left: 1px solid rgb(184, 184, 184); + margin-left: -1px; +} + +.profile-sidebar-tree-item .icon { + content: url(Images/profileIcon.png); +} + +.profile-sidebar-tree-item.small .icon { + content: url(Images/profileSmallIcon.png); +} + +.profile-group-sidebar-tree-item .icon { + content: url(Images/profileGroupIcon.png); +} + +.profile-view { + display: none; + overflow: hidden; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; +} + +.profile-view.visible { + display: block; +} + +.profile-view .data-grid { + border: none; + height: 100%; +} + +.profile-view .data-grid th.average-column { + text-align: center; +} + +.profile-view .data-grid td.average-column { + text-align: right; +} + +.profile-view .data-grid th.self-column { + text-align: center; +} + +.profile-view .data-grid td.self-column { + text-align: right; +} + +.profile-view .data-grid th.total-column { + text-align: center; +} + +.profile-view .data-grid td.total-column { + text-align: right; +} + +.profile-view .data-grid .calls-column { + text-align: center; +} + +.profile-node-file { + float: right; + color: gray; + margin-top: -1px; +} + +.data-grid tr.selected .profile-node-file { + color: rgb(33%, 33%, 33%); +} + +.data-grid:focus tr.selected .profile-node-file { + color: white; +} + +button.enable-toggle-status-bar-item .glyph { +} + +.record-profile-status-bar-item .glyph { + -webkit-mask-image: url(Images/recordButtonGlyph.png); +} + +.record-profile-status-bar-item.toggled-on .glyph { + -webkit-mask-image: url(Images/recordToggledButtonGlyph.png); + background-color: rgb(216, 0, 0) !important; +} + +/* FIXME: should have its own glyph. */ +.heap-snapshot-status-bar-item .glyph { + -webkit-mask-image: url(Images/focusButtonGlyph.png); +} + +.node-search-status-bar-item .glyph { + -webkit-mask-image: url(Images/nodeSearchButtonGlyph.png); +} + +.percent-time-status-bar-item .glyph { + -webkit-mask-image: url(Images/percentButtonGlyph.png); +} + +.focus-profile-node-status-bar-item .glyph { + -webkit-mask-image: url(Images/focusButtonGlyph.png); +} + +.exclude-profile-node-status-bar-item .glyph { + -webkit-mask-image: url(Images/excludeButtonGlyph.png); +} + +.reset-profile-status-bar-item .glyph { + -webkit-mask-image: url(Images/reloadButtonGlyph.png); +} + +.delete-storage-status-bar-item .glyph { + -webkit-mask-image: url(Images/excludeButtonGlyph.png); +} + +#storage-view-status-bar-items { + position: absolute; + top: 0; + bottom: 0; + left: 200px; + overflow: hidden; + border-left: 1px solid rgb(184, 184, 184); + margin-left: -1px; +} + +.refresh-storage-status-bar-item .glyph { + -webkit-mask-image: url(Images/reloadButtonGlyph.png); +} + +#storage-view-status-bar-items { + position: absolute; + top: 0; + bottom: 0; + left: 200px; + overflow: hidden; + border-left: 1px solid rgb(184, 184, 184); + margin-left: -1px; +} + +ol.breakpoint-list { + -webkit-padding-start: 2px; + list-style: none; + margin: 0; +} + +.breakpoint-list li { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + margin: 4px 0; +} + +.breakpoint-list .checkbox-elem { + font-size: 10px; + margin: 0 4px; + vertical-align: top; + position: relative; + z-index: 1; +} + +.breakpoint-list .source-text { + font-family: monospace; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + margin: 2px 0 0px 20px; +} + +.breakpoint-list a { + color: rgb(33%, 33%, 33%); + cursor: pointer; +} + +.breakpoint-list a:hover { + color: rgb(15%, 15%, 15%); +} diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/inspector.html b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/inspector.html new file mode 100644 index 0000000..7f544fe --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/inspector.html @@ -0,0 +1,112 @@ +<!-- +Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + its contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--> +<!DOCTYPE html> +<html> +<head> + <meta http-equiv="content-type" content="text/html; charset=utf-8"> + <link rel="stylesheet" type="text/css" href="inspector.css"> + <script type="text/javascript" src="utilities.js"></script> + <script type="text/javascript" src="treeoutline.js"></script> + <script type="text/javascript" src="inspector.js"></script> + <script type="text/javascript" src="Object.js"></script> + <script type="text/javascript" src="KeyboardShortcut.js"></script> + <script type="text/javascript" src="TextPrompt.js"></script> + <script type="text/javascript" src="Popup.js"></script> + <script type="text/javascript" src="Placard.js"></script> + <script type="text/javascript" src="View.js"></script> + <script type="text/javascript" src="Callback.js"></script> + <script type="text/javascript" src="Drawer.js"></script> + <script type="text/javascript" src="ChangesView.js"></script> + <script type="text/javascript" src="ConsoleView.js"></script> + <script type="text/javascript" src="Resource.js"></script> + <script type="text/javascript" src="ResourceCategory.js"></script> + <script type="text/javascript" src="Database.js"></script> + <script type="text/javascript" src="DOMStorage.js"></script> + <script type="text/javascript" src="DOMStorageItemsView.js"></script> + <script type="text/javascript" src="DataGrid.js"></script> + <script type="text/javascript" src="DOMStorageDataGrid.js"></script> + <script type="text/javascript" src="CookieItemsView.js"></script> + <script type="text/javascript" src="Script.js"></script> + <script type="text/javascript" src="Breakpoint.js"></script> + <script type="text/javascript" src="SidebarPane.js"></script> + <script type="text/javascript" src="ElementsTreeOutline.js"></script> + <script type="text/javascript" src="SidebarTreeElement.js"></script> + <script type="text/javascript" src="PropertiesSection.js"></script> + <script type="text/javascript" src="ObjectProxy.js"></script> + <script type="text/javascript" src="ObjectPropertiesSection.js"></script> + <script type="text/javascript" src="BreakpointsSidebarPane.js"></script> + <script type="text/javascript" src="CallStackSidebarPane.js"></script> + <script type="text/javascript" src="ScopeChainSidebarPane.js"></script> + <script type="text/javascript" src="WatchExpressionsSidebarPane.js"></script> + <script type="text/javascript" src="MetricsSidebarPane.js"></script> + <script type="text/javascript" src="PropertiesSidebarPane.js"></script> + <script type="text/javascript" src="Color.js"></script> + <script type="text/javascript" src="StylesSidebarPane.js"></script> + <script type="text/javascript" src="Panel.js"></script> + <script type="text/javascript" src="PanelEnablerView.js"></script> + <script type="text/javascript" src="StatusBarButton.js"></script> + <script type="text/javascript" src="SummaryBar.js"></script> + <script type="text/javascript" src="ElementsPanel.js"></script> + <script type="text/javascript" src="ResourcesPanel.js"></script> + <script type="text/javascript" src="ScriptsPanel.js"></script> + <script type="text/javascript" src="StoragePanel.js"></script> + <script type="text/javascript" src="ProfilesPanel.js"></script> + <script type="text/javascript" src="ResourceView.js"></script> + <script type="text/javascript" src="SourceFrame.js"></script> + <script type="text/javascript" src="SourceView.js"></script> + <script type="text/javascript" src="FontView.js"></script> + <script type="text/javascript" src="ImageView.js"></script> + <script type="text/javascript" src="DatabaseTableView.js"></script> + <script type="text/javascript" src="DatabaseQueryView.js"></script> + <script type="text/javascript" src="ScriptView.js"></script> + <script type="text/javascript" src="ProfileDataGridTree.js"></script> + <script type="text/javascript" src="BottomUpProfileDataGridTree.js"></script> + <script type="text/javascript" src="TopDownProfileDataGridTree.js"></script> + <script type="text/javascript" src="ProfileView.js"></script> + <script type="text/javascript" src="DOMAgent.js"></script> + <script type="text/javascript" src="InjectedScript.js"></script> + <script type="text/javascript" src="InjectedScriptAccess.js"></script> + <script type="text/javascript" src="TimelineAgent.js"></script> +</head> +<body class="detached"> + <div id="toolbar"> + <div class="toolbar-item close"><button id="close-button"></button></div> + <div class="toolbar-item flexable-space"></div> + <div class="toolbar-item hidden" id="search-results-matches"></div> + <div class="toolbar-item"><input id="search" type="search" incremental results="0"><div id="search-toolbar-label" class="toolbar-label"></div></div> + </div> + <div id="main"> + <div id="main-panels" tabindex="0" spellcheck="false"></div> + <div id="main-status-bar" class="status-bar"><div id="anchored-status-bar-items"><button id="dock-status-bar-item" class="status-bar-item toggled"><div class="glyph"></div><div class="glyph shadow"></div></button><button id="console-status-bar-item" class="status-bar-item"><div class="glyph"></div><div class="glyph shadow"></div></button><button id="changes-status-bar-item" class="status-bar-item hidden"></button><div id="count-items"><div id="changes-count" class="hidden"></div><div id="error-warning-count" class="hidden"></div></div></div></div> + </div> + <div id="drawer"> + <div id="console-view"><div id="console-messages"><div id="console-prompt" spellcheck="false"><br></div></div></div> + <div id="drawer-status-bar" class="status-bar"><div id="other-drawer-status-bar-items"><button id="clear-console-status-bar-item" class="status-bar-item"><div class="glyph"></div><div class="glyph shadow"></div></button><div id="console-filter" class="status-bar-item"></div></div></div> + </div> +</body> +</html> diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/inspector.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/inspector.js new file mode 100644 index 0000000..902dd94 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/inspector.js @@ -0,0 +1,1553 @@ +/* + * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2007 Matt Lilek (pewtermoose@gmail.com). + * Copyright (C) 2009 Joseph Pecoraro + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +var Preferences = { + ignoreWhitespace: true, + showUserAgentStyles: true, + maxInlineTextChildLength: 80, + minConsoleHeight: 75, + minSidebarWidth: 100, + minElementsSidebarWidth: 200, + minScriptsSidebarWidth: 200, + showInheritedComputedStyleProperties: false, + styleRulesExpandedState: {}, + showMissingLocalizedStrings: false, + heapProfilerPresent: false, + samplingCPUProfiler: false, + showColorNicknames: true, + colorFormat: "hex" +} + +var WebInspector = { + resources: {}, + resourceURLMap: {}, + missingLocalizedStrings: {}, + + get previousFocusElement() + { + return this._previousFocusElement; + }, + + get currentFocusElement() + { + return this._currentFocusElement; + }, + + set currentFocusElement(x) + { + if (this._currentFocusElement !== x) + this._previousFocusElement = this._currentFocusElement; + this._currentFocusElement = x; + + if (this._currentFocusElement) { + this._currentFocusElement.focus(); + + // Make a caret selection inside the new element if there isn't a range selection and + // there isn't already a caret selection inside. + var selection = window.getSelection(); + if (selection.isCollapsed && !this._currentFocusElement.isInsertionCaretInside()) { + var selectionRange = this._currentFocusElement.ownerDocument.createRange(); + selectionRange.setStart(this._currentFocusElement, 0); + selectionRange.setEnd(this._currentFocusElement, 0); + + selection.removeAllRanges(); + selection.addRange(selectionRange); + } + } else if (this._previousFocusElement) + this._previousFocusElement.blur(); + }, + + get currentPanel() + { + return this._currentPanel; + }, + + set currentPanel(x) + { + if (this._currentPanel === x) + return; + + if (this._currentPanel) + this._currentPanel.hide(); + + this._currentPanel = x; + + this.updateSearchLabel(); + + if (x) { + x.show(); + + if (this.currentQuery) { + if (x.performSearch) { + function performPanelSearch() + { + this.updateSearchMatchesCount(); + + x.currentQuery = this.currentQuery; + x.performSearch(this.currentQuery); + } + + // Perform the search on a timeout so the panel switches fast. + setTimeout(performPanelSearch.bind(this), 0); + } else { + // Update to show Not found for panels that can't be searched. + this.updateSearchMatchesCount(); + } + } + } + + for (var panelName in WebInspector.panels) { + if (WebInspector.panels[panelName] == x) + InspectorController.storeLastActivePanel(panelName); + } + }, + + _createPanels: function() + { + var hiddenPanels = (InspectorController.hiddenPanels() || "").split(','); + if (hiddenPanels.indexOf("elements") === -1) + this.panels.elements = new WebInspector.ElementsPanel(); + if (hiddenPanels.indexOf("resources") === -1) + this.panels.resources = new WebInspector.ResourcesPanel(); + if (hiddenPanels.indexOf("scripts") === -1) + this.panels.scripts = new WebInspector.ScriptsPanel(); + if (hiddenPanels.indexOf("profiles") === -1) + this.panels.profiles = new WebInspector.ProfilesPanel(); + if (hiddenPanels.indexOf("storage") === -1 && hiddenPanels.indexOf("databases") === -1) + this.panels.storage = new WebInspector.StoragePanel(); + }, + + get attached() + { + return this._attached; + }, + + set attached(x) + { + if (this._attached === x) + return; + + this._attached = x; + + this.updateSearchLabel(); + + var dockToggleButton = document.getElementById("dock-status-bar-item"); + var body = document.body; + + if (x) { + InspectorController.attach(); + body.removeStyleClass("detached"); + body.addStyleClass("attached"); + dockToggleButton.title = WebInspector.UIString("Undock into separate window."); + } else { + InspectorController.detach(); + body.removeStyleClass("attached"); + body.addStyleClass("detached"); + dockToggleButton.title = WebInspector.UIString("Dock to main window."); + } + }, + + get errors() + { + return this._errors || 0; + }, + + set errors(x) + { + x = Math.max(x, 0); + + if (this._errors === x) + return; + this._errors = x; + this._updateErrorAndWarningCounts(); + }, + + get warnings() + { + return this._warnings || 0; + }, + + set warnings(x) + { + x = Math.max(x, 0); + + if (this._warnings === x) + return; + this._warnings = x; + this._updateErrorAndWarningCounts(); + }, + + _updateErrorAndWarningCounts: function() + { + var errorWarningElement = document.getElementById("error-warning-count"); + if (!errorWarningElement) + return; + + if (!this.errors && !this.warnings) { + errorWarningElement.addStyleClass("hidden"); + return; + } + + errorWarningElement.removeStyleClass("hidden"); + + errorWarningElement.removeChildren(); + + if (this.errors) { + var errorElement = document.createElement("span"); + errorElement.id = "error-count"; + errorElement.textContent = this.errors; + errorWarningElement.appendChild(errorElement); + } + + if (this.warnings) { + var warningsElement = document.createElement("span"); + warningsElement.id = "warning-count"; + warningsElement.textContent = this.warnings; + errorWarningElement.appendChild(warningsElement); + } + + if (this.errors) { + if (this.warnings) { + if (this.errors == 1) { + if (this.warnings == 1) + errorWarningElement.title = WebInspector.UIString("%d error, %d warning", this.errors, this.warnings); + else + errorWarningElement.title = WebInspector.UIString("%d error, %d warnings", this.errors, this.warnings); + } else if (this.warnings == 1) + errorWarningElement.title = WebInspector.UIString("%d errors, %d warning", this.errors, this.warnings); + else + errorWarningElement.title = WebInspector.UIString("%d errors, %d warnings", this.errors, this.warnings); + } else if (this.errors == 1) + errorWarningElement.title = WebInspector.UIString("%d error", this.errors); + else + errorWarningElement.title = WebInspector.UIString("%d errors", this.errors); + } else if (this.warnings == 1) + errorWarningElement.title = WebInspector.UIString("%d warning", this.warnings); + else if (this.warnings) + errorWarningElement.title = WebInspector.UIString("%d warnings", this.warnings); + else + errorWarningElement.title = null; + }, + + get styleChanges() + { + return this._styleChanges; + }, + + set styleChanges(x) + { + x = Math.max(x, 0); + + if (this._styleChanges === x) + return; + this._styleChanges = x; + this._updateChangesCount(); + }, + + _updateChangesCount: function() + { + // TODO: Remove immediate return when enabling the Changes Panel + return; + + var changesElement = document.getElementById("changes-count"); + if (!changesElement) + return; + + if (!this.styleChanges) { + changesElement.addStyleClass("hidden"); + return; + } + + changesElement.removeStyleClass("hidden"); + changesElement.removeChildren(); + + if (this.styleChanges) { + var styleChangesElement = document.createElement("span"); + styleChangesElement.id = "style-changes-count"; + styleChangesElement.textContent = this.styleChanges; + changesElement.appendChild(styleChangesElement); + } + + if (this.styleChanges) { + if (this.styleChanges === 1) + changesElement.title = WebInspector.UIString("%d style change", this.styleChanges); + else + changesElement.title = WebInspector.UIString("%d style changes", this.styleChanges); + } + }, + + get hoveredDOMNode() + { + return this._hoveredDOMNode; + }, + + set hoveredDOMNode(x) + { + if (this._hoveredDOMNode === x) + return; + + this._hoveredDOMNode = x; + + if (this._hoveredDOMNode) + this._updateHoverHighlightSoon(this.showingDOMNodeHighlight ? 50 : 500); + else + this._updateHoverHighlight(); + }, + + _updateHoverHighlightSoon: function(delay) + { + if ("_updateHoverHighlightTimeout" in this) + clearTimeout(this._updateHoverHighlightTimeout); + this._updateHoverHighlightTimeout = setTimeout(this._updateHoverHighlight.bind(this), delay); + }, + + _updateHoverHighlight: function() + { + if ("_updateHoverHighlightTimeout" in this) { + clearTimeout(this._updateHoverHighlightTimeout); + delete this._updateHoverHighlightTimeout; + } + + if (this._hoveredDOMNode) { + InspectorController.highlightDOMNode(this._hoveredDOMNode.id); + this.showingDOMNodeHighlight = true; + } else { + InspectorController.hideDOMNodeHighlight(); + this.showingDOMNodeHighlight = false; + } + } +} + +WebInspector.loaded = function() +{ + var platform = InspectorController.platform(); + document.body.addStyleClass("platform-" + platform); + + var colorFormat = InspectorController.setting("color-format"); + if (colorFormat) + Preferences.colorFormat = colorFormat; + + this.drawer = new WebInspector.Drawer(); + this.console = new WebInspector.ConsoleView(this.drawer); + // TODO: Uncomment when enabling the Changes Panel + // this.changes = new WebInspector.ChangesView(this.drawer); + // TODO: Remove class="hidden" from inspector.html on button#changes-status-bar-item + this.drawer.visibleView = this.console; + this.domAgent = new WebInspector.DOMAgent(); + + this.resourceCategories = { + documents: new WebInspector.ResourceCategory(WebInspector.UIString("Documents"), "documents"), + stylesheets: new WebInspector.ResourceCategory(WebInspector.UIString("Stylesheets"), "stylesheets"), + images: new WebInspector.ResourceCategory(WebInspector.UIString("Images"), "images"), + scripts: new WebInspector.ResourceCategory(WebInspector.UIString("Scripts"), "scripts"), + xhr: new WebInspector.ResourceCategory(WebInspector.UIString("XHR"), "xhr"), + fonts: new WebInspector.ResourceCategory(WebInspector.UIString("Fonts"), "fonts"), + other: new WebInspector.ResourceCategory(WebInspector.UIString("Other"), "other") + }; + + this.panels = {}; + this._createPanels(); + + var toolbarElement = document.getElementById("toolbar"); + var previousToolbarItem = toolbarElement.children[0]; + + this.panelOrder = []; + for (var panelName in this.panels) { + var panel = this.panels[panelName]; + var panelToolbarItem = panel.toolbarItem; + this.panelOrder.push(panel); + panelToolbarItem.addEventListener("click", this._toolbarItemClicked.bind(this)); + if (previousToolbarItem) + toolbarElement.insertBefore(panelToolbarItem, previousToolbarItem.nextSibling); + else + toolbarElement.insertBefore(panelToolbarItem, toolbarElement.firstChild); + previousToolbarItem = panelToolbarItem; + } + + this.Tips = { + ResourceNotCompressed: {id: 0, message: WebInspector.UIString("You could save bandwidth by having your web server compress this transfer with gzip or zlib.")} + }; + + this.Warnings = { + IncorrectMIMEType: {id: 0, message: WebInspector.UIString("Resource interpreted as %s but transferred with MIME type %s.")} + }; + + this.addMainEventListeners(document); + + window.addEventListener("unload", this.windowUnload.bind(this), true); + window.addEventListener("resize", this.windowResize.bind(this), true); + + document.addEventListener("focus", this.focusChanged.bind(this), true); + document.addEventListener("keydown", this.documentKeyDown.bind(this), true); + document.addEventListener("keyup", this.documentKeyUp.bind(this), true); + document.addEventListener("beforecopy", this.documentCanCopy.bind(this), true); + document.addEventListener("copy", this.documentCopy.bind(this), true); + document.addEventListener("contextmenu", this.contextMenu.bind(this), true); + + var mainPanelsElement = document.getElementById("main-panels"); + mainPanelsElement.handleKeyEvent = this.mainKeyDown.bind(this); + mainPanelsElement.handleKeyUpEvent = this.mainKeyUp.bind(this); + mainPanelsElement.handleCopyEvent = this.mainCopy.bind(this); + + // Focus the mainPanelsElement in a timeout so it happens after the initial focus, + // so it doesn't get reset to the first toolbar button. This initial focus happens + // on Mac when the window is made key and the WebHTMLView becomes the first responder. + setTimeout(function() { WebInspector.currentFocusElement = mainPanelsElement }, 0); + + var dockToggleButton = document.getElementById("dock-status-bar-item"); + dockToggleButton.addEventListener("click", this.toggleAttach.bind(this), false); + + if (this.attached) + dockToggleButton.title = WebInspector.UIString("Undock into separate window."); + else + dockToggleButton.title = WebInspector.UIString("Dock to main window."); + + var errorWarningCount = document.getElementById("error-warning-count"); + errorWarningCount.addEventListener("click", this.showConsole.bind(this), false); + this._updateErrorAndWarningCounts(); + + this.styleChanges = 0; + // TODO: Uncomment when enabling the Changes Panel + // var changesElement = document.getElementById("changes-count"); + // changesElement.addEventListener("click", this.showChanges.bind(this), false); + // this._updateErrorAndWarningCounts(); + + var searchField = document.getElementById("search"); + searchField.addEventListener("keydown", this.searchKeyDown.bind(this), false); + searchField.addEventListener("keyup", this.searchKeyUp.bind(this), false); + searchField.addEventListener("search", this.performSearch.bind(this), false); // when the search is emptied + + document.getElementById("toolbar").addEventListener("mousedown", this.toolbarDragStart, true); + document.getElementById("close-button").addEventListener("click", this.close, true); + + InspectorController.loaded(); +} + +var windowLoaded = function() +{ + var localizedStringsURL = InspectorController.localizedStringsURL(); + if (localizedStringsURL) { + var localizedStringsScriptElement = document.createElement("script"); + localizedStringsScriptElement.addEventListener("load", WebInspector.loaded.bind(WebInspector), false); + localizedStringsScriptElement.type = "text/javascript"; + localizedStringsScriptElement.src = localizedStringsURL; + document.getElementsByTagName("head").item(0).appendChild(localizedStringsScriptElement); + } else + WebInspector.loaded(); + + window.removeEventListener("load", windowLoaded, false); + delete windowLoaded; +}; + +window.addEventListener("load", windowLoaded, false); + +WebInspector.dispatch = function() { + var methodName = arguments[0]; + var parameters = Array.prototype.slice.call(arguments, 1); + WebInspector[methodName].apply(this, parameters); +} + +WebInspector.windowUnload = function(event) +{ + InspectorController.windowUnloading(); +} + +WebInspector.windowResize = function(event) +{ + if (this.currentPanel && this.currentPanel.resize) + this.currentPanel.resize(); +} + +WebInspector.windowFocused = function(event) +{ + if (event.target.nodeType === Node.DOCUMENT_NODE) + document.body.removeStyleClass("inactive"); +} + +WebInspector.windowBlured = function(event) +{ + if (event.target.nodeType === Node.DOCUMENT_NODE) + document.body.addStyleClass("inactive"); +} + +WebInspector.focusChanged = function(event) +{ + this.currentFocusElement = event.target; +} + +WebInspector.setAttachedWindow = function(attached) +{ + this.attached = attached; +} + +WebInspector.close = function(event) +{ + InspectorController.closeWindow(); +} + +WebInspector.documentClick = function(event) +{ + var anchor = event.target.enclosingNodeOrSelfWithNodeName("a"); + if (!anchor) + return; + + // Prevent the link from navigating, since we don't do any navigation by following links normally. + event.preventDefault(); + + function followLink() + { + // FIXME: support webkit-html-external-link links here. + if (anchor.href in WebInspector.resourceURLMap) { + if (anchor.hasStyleClass("webkit-html-external-link")) { + anchor.removeStyleClass("webkit-html-external-link"); + anchor.addStyleClass("webkit-html-resource-link"); + } + + WebInspector.showResourceForURL(anchor.href, anchor.lineNumber, anchor.preferredPanel); + } else { + var profileStringRegEx = new RegExp("webkit-profile://.+/([0-9]+)"); + var profileString = profileStringRegEx.exec(anchor.href); + if (profileString) + WebInspector.showProfileById(profileString[1]) + } + } + + if (WebInspector.followLinkTimeout) + clearTimeout(WebInspector.followLinkTimeout); + + if (anchor.preventFollowOnDoubleClick) { + // Start a timeout if this is the first click, if the timeout is canceled + // before it fires, then a double clicked happened or another link was clicked. + if (event.detail === 1) + WebInspector.followLinkTimeout = setTimeout(followLink, 333); + return; + } + + followLink(); +} + +WebInspector.documentKeyDown = function(event) +{ + if (!this.currentFocusElement) + return; + if (this.currentFocusElement.handleKeyEvent) + this.currentFocusElement.handleKeyEvent(event); + else if (this.currentFocusElement.id && this.currentFocusElement.id.length && WebInspector[this.currentFocusElement.id + "KeyDown"]) + WebInspector[this.currentFocusElement.id + "KeyDown"](event); + + if (!event.handled) { + var isMac = InspectorController.platform().indexOf("mac-") === 0; + + switch (event.keyIdentifier) { + case "U+001B": // Escape key + this.drawer.visible = !this.drawer.visible; + event.preventDefault(); + break; + + case "U+0046": // F key + if (isMac) + var isFindKey = event.metaKey && !event.ctrlKey && !event.altKey && !event.shiftKey; + else + var isFindKey = event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey; + + if (isFindKey) { + var searchField = document.getElementById("search"); + searchField.focus(); + searchField.select(); + event.preventDefault(); + } + + break; + + case "U+0047": // G key + if (isMac) + var isFindAgainKey = event.metaKey && !event.ctrlKey && !event.altKey; + else + var isFindAgainKey = event.ctrlKey && !event.metaKey && !event.altKey; + + if (isFindAgainKey) { + if (event.shiftKey) { + if (this.currentPanel.jumpToPreviousSearchResult) + this.currentPanel.jumpToPreviousSearchResult(); + } else if (this.currentPanel.jumpToNextSearchResult) + this.currentPanel.jumpToNextSearchResult(); + event.preventDefault(); + } + + break; + + case "U+005B": // [ key + if (isMac) + var isRotateLeft = event.metaKey && !event.shiftKey && !event.ctrlKey && !event.altKey; + else + var isRotateLeft = event.ctrlKey && !event.shiftKey && !event.metaKey && !event.altKey; + + if (isRotateLeft) { + var index = this.panelOrder.indexOf(this.currentPanel); + index = (index === 0) ? this.panelOrder.length - 1 : index - 1; + this.panelOrder[index].toolbarItem.click(); + event.preventDefault(); + } + + break; + + case "U+005D": // ] key + if (isMac) + var isRotateRight = event.metaKey && !event.shiftKey && !event.ctrlKey && !event.altKey; + else + var isRotateRight = event.ctrlKey && !event.shiftKey && !event.metaKey && !event.altKey; + + if (isRotateRight) { + var index = this.panelOrder.indexOf(this.currentPanel); + index = (index + 1) % this.panelOrder.length; + this.panelOrder[index].toolbarItem.click(); + event.preventDefault(); + } + + break; + } + } +} + +WebInspector.documentKeyUp = function(event) +{ + if (!this.currentFocusElement || !this.currentFocusElement.handleKeyUpEvent) + return; + this.currentFocusElement.handleKeyUpEvent(event); +} + +WebInspector.documentCanCopy = function(event) +{ + if (!this.currentFocusElement) + return; + // Calling preventDefault() will say "we support copying, so enable the Copy menu". + if (this.currentFocusElement.handleCopyEvent) + event.preventDefault(); + else if (this.currentFocusElement.id && this.currentFocusElement.id.length && WebInspector[this.currentFocusElement.id + "Copy"]) + event.preventDefault(); +} + +WebInspector.documentCopy = function(event) +{ + if (!this.currentFocusElement) + return; + if (this.currentFocusElement.handleCopyEvent) + this.currentFocusElement.handleCopyEvent(event); + else if (this.currentFocusElement.id && this.currentFocusElement.id.length && WebInspector[this.currentFocusElement.id + "Copy"]) + WebInspector[this.currentFocusElement.id + "Copy"](event); +} + +WebInspector.contextMenu = function(event) +{ + if (event.handled || event.target.hasStyleClass("popup-glasspane")) + event.preventDefault(); +} + +WebInspector.mainKeyDown = function(event) +{ + if (this.currentPanel && this.currentPanel.handleKeyEvent) + this.currentPanel.handleKeyEvent(event); +} + +WebInspector.mainKeyUp = function(event) +{ + if (this.currentPanel && this.currentPanel.handleKeyUpEvent) + this.currentPanel.handleKeyUpEvent(event); +} + +WebInspector.mainCopy = function(event) +{ + if (this.currentPanel && this.currentPanel.handleCopyEvent) + this.currentPanel.handleCopyEvent(event); +} + +WebInspector.animateStyle = function(animations, duration, callback, complete) +{ + if (complete === undefined) + complete = 0; + var slice = (1000 / 30); // 30 frames per second + + var defaultUnit = "px"; + var propertyUnit = {opacity: ""}; + + for (var i = 0; i < animations.length; ++i) { + var animation = animations[i]; + var element = null; + var start = null; + var current = null; + var end = null; + var key = null; + for (key in animation) { + if (key === "element") + element = animation[key]; + else if (key === "start") + start = animation[key]; + else if (key === "current") + current = animation[key]; + else if (key === "end") + end = animation[key]; + } + + if (!element || !end) + continue; + + var computedStyle = element.ownerDocument.defaultView.getComputedStyle(element); + if (!start) { + start = {}; + for (key in end) + start[key] = parseInt(computedStyle.getPropertyValue(key)); + animation.start = start; + } else if (complete == 0) + for (key in start) + element.style.setProperty(key, start[key] + (key in propertyUnit ? propertyUnit[key] : defaultUnit)); + + if (!current) { + current = {}; + for (key in start) + current[key] = start[key]; + animation.current = current; + } + + function cubicInOut(t, b, c, d) + { + if ((t/=d/2) < 1) return c/2*t*t*t + b; + return c/2*((t-=2)*t*t + 2) + b; + } + + var style = element.style; + for (key in end) { + var startValue = start[key]; + var currentValue = current[key]; + var endValue = end[key]; + if ((complete + slice) < duration) { + var delta = (endValue - startValue) / (duration / slice); + var newValue = cubicInOut(complete, startValue, endValue - startValue, duration); + style.setProperty(key, newValue + (key in propertyUnit ? propertyUnit[key] : defaultUnit)); + current[key] = newValue; + } else { + style.setProperty(key, endValue + (key in propertyUnit ? propertyUnit[key] : defaultUnit)); + } + } + } + + if (complete < duration) + setTimeout(WebInspector.animateStyle, slice, animations, duration, callback, complete + slice); + else if (callback) + callback(); +} + +WebInspector.updateSearchLabel = function() +{ + if (!this.currentPanel) + return; + + var newLabel = WebInspector.UIString("Search %s", this.currentPanel.toolbarItemLabel); + if (this.attached) + document.getElementById("search").setAttribute("placeholder", newLabel); + else { + document.getElementById("search").removeAttribute("placeholder"); + document.getElementById("search-toolbar-label").textContent = newLabel; + } +} + +WebInspector.toggleAttach = function() +{ + this.attached = !this.attached; +} + +WebInspector.toolbarDragStart = function(event) +{ + if (!WebInspector.attached && InspectorController.platform() !== "mac-leopard") + return; + + var target = event.target; + if (target.hasStyleClass("toolbar-item") && target.hasStyleClass("toggleable")) + return; + + var toolbar = document.getElementById("toolbar"); + if (target !== toolbar && !target.hasStyleClass("toolbar-item")) + return; + + toolbar.lastScreenX = event.screenX; + toolbar.lastScreenY = event.screenY; + + WebInspector.elementDragStart(toolbar, WebInspector.toolbarDrag, WebInspector.toolbarDragEnd, event, (WebInspector.attached ? "row-resize" : "default")); +} + +WebInspector.toolbarDragEnd = function(event) +{ + var toolbar = document.getElementById("toolbar"); + + WebInspector.elementDragEnd(event); + + delete toolbar.lastScreenX; + delete toolbar.lastScreenY; +} + +WebInspector.toolbarDrag = function(event) +{ + var toolbar = document.getElementById("toolbar"); + + if (WebInspector.attached) { + var height = window.innerHeight - (event.screenY - toolbar.lastScreenY); + + InspectorController.setAttachedWindowHeight(height); + } else { + var x = event.screenX - toolbar.lastScreenX; + var y = event.screenY - toolbar.lastScreenY; + + // We cannot call window.moveBy here because it restricts the movement + // of the window at the edges. + InspectorController.moveByUnrestricted(x, y); + } + + toolbar.lastScreenX = event.screenX; + toolbar.lastScreenY = event.screenY; + + event.preventDefault(); +} + +WebInspector.elementDragStart = function(element, dividerDrag, elementDragEnd, event, cursor) +{ + if (this._elementDraggingEventListener || this._elementEndDraggingEventListener) + this.elementDragEnd(event); + + this._elementDraggingEventListener = dividerDrag; + this._elementEndDraggingEventListener = elementDragEnd; + + document.addEventListener("mousemove", dividerDrag, true); + document.addEventListener("mouseup", elementDragEnd, true); + + document.body.style.cursor = cursor; + + event.preventDefault(); +} + +WebInspector.elementDragEnd = function(event) +{ + document.removeEventListener("mousemove", this._elementDraggingEventListener, true); + document.removeEventListener("mouseup", this._elementEndDraggingEventListener, true); + + document.body.style.removeProperty("cursor"); + + delete this._elementDraggingEventListener; + delete this._elementEndDraggingEventListener; + + event.preventDefault(); +} + +WebInspector.showConsole = function() +{ + this.drawer.showView(this.console); +} + +WebInspector.showChanges = function() +{ + this.drawer.showView(this.changes); +} + +WebInspector.showElementsPanel = function() +{ + this.currentPanel = this.panels.elements; +} + +WebInspector.showResourcesPanel = function() +{ + this.currentPanel = this.panels.resources; +} + +WebInspector.showScriptsPanel = function() +{ + this.currentPanel = this.panels.scripts; +} + +WebInspector.showProfilesPanel = function() +{ + this.currentPanel = this.panels.profiles; +} + +WebInspector.showStoragePanel = function() +{ + this.currentPanel = this.panels.storage; +} + +WebInspector.addResource = function(identifier, payload) +{ + var resource = new WebInspector.Resource( + payload.requestHeaders, + payload.requestURL, + payload.host, + payload.path, + payload.lastPathComponent, + identifier, + payload.isMainResource, + payload.cached, + payload.requestMethod, + payload.requestFormData); + this.resources[identifier] = resource; + this.resourceURLMap[resource.url] = resource; + + if (resource.mainResource) { + this.mainResource = resource; + this.panels.elements.reset(); + } + + if (this.panels.resources) + this.panels.resources.addResource(resource); +} + +WebInspector.clearConsoleMessages = function() +{ + WebInspector.console.clearMessages(false); +} + +WebInspector.selectDatabase = function(o) +{ + WebInspector.showStoragePanel(); + WebInspector.panels.storage.selectDatabase(o); +} + +WebInspector.selectDOMStorage = function(o) +{ + WebInspector.showStoragePanel(); + WebInspector.panels.storage.selectDOMStorage(o); +} + +WebInspector.updateResource = function(identifier, payload) +{ + var resource = this.resources[identifier]; + if (!resource) + return; + + if (payload.didRequestChange) { + resource.url = payload.url; + resource.domain = payload.domain; + resource.path = payload.path; + resource.lastPathComponent = payload.lastPathComponent; + resource.requestHeaders = payload.requestHeaders; + resource.mainResource = payload.mainResource; + resource.requestMethod = payload.requestMethod; + resource.requestFormData = payload.requestFormData; + } + + if (payload.didResponseChange) { + resource.mimeType = payload.mimeType; + resource.suggestedFilename = payload.suggestedFilename; + resource.expectedContentLength = payload.expectedContentLength; + resource.statusCode = payload.statusCode; + resource.suggestedFilename = payload.suggestedFilename; + resource.responseHeaders = payload.responseHeaders; + } + + if (payload.didTypeChange) { + resource.type = payload.type; + } + + if (payload.didLengthChange) { + resource.contentLength = payload.contentLength; + } + + if (payload.didCompletionChange) { + resource.failed = payload.failed; + resource.finished = payload.finished; + } + + if (payload.didTimingChange) { + if (payload.startTime) + resource.startTime = payload.startTime; + if (payload.responseReceivedTime) + resource.responseReceivedTime = payload.responseReceivedTime; + if (payload.endTime) + resource.endTime = payload.endTime; + } +} + +WebInspector.removeResource = function(identifier) +{ + var resource = this.resources[identifier]; + if (!resource) + return; + + resource.category.removeResource(resource); + delete this.resourceURLMap[resource.url]; + delete this.resources[identifier]; + + if (this.panels.resources) + this.panels.resources.removeResource(resource); +} + +WebInspector.addDatabase = function(payload) +{ + var database = new WebInspector.Database( + payload.database, + payload.domain, + payload.name, + payload.version); + this.panels.storage.addDatabase(database); +} + +WebInspector.addDOMStorage = function(payload) +{ + var domStorage = new WebInspector.DOMStorage( + payload.domStorage, + payload.host, + payload.isLocalStorage); + this.panels.storage.addDOMStorage(domStorage); +} + +WebInspector.resourceTrackingWasEnabled = function() +{ + this.panels.resources.resourceTrackingWasEnabled(); +} + +WebInspector.resourceTrackingWasDisabled = function() +{ + this.panels.resources.resourceTrackingWasDisabled(); +} + +WebInspector.attachDebuggerWhenShown = function() +{ + this.panels.scripts.attachDebuggerWhenShown(); +} + +WebInspector.debuggerWasEnabled = function() +{ + this.panels.scripts.debuggerWasEnabled(); +} + +WebInspector.debuggerWasDisabled = function() +{ + this.panels.scripts.debuggerWasDisabled(); +} + +WebInspector.profilerWasEnabled = function() +{ + this.panels.profiles.profilerWasEnabled(); +} + +WebInspector.profilerWasDisabled = function() +{ + this.panels.profiles.profilerWasDisabled(); +} + +WebInspector.parsedScriptSource = function(sourceID, sourceURL, source, startingLine) +{ + this.panels.scripts.addScript(sourceID, sourceURL, source, startingLine); +} + +WebInspector.failedToParseScriptSource = function(sourceURL, source, startingLine, errorLine, errorMessage) +{ + this.panels.scripts.addScript(null, sourceURL, source, startingLine, errorLine, errorMessage); +} + +WebInspector.pausedScript = function(callFrames) +{ + this.panels.scripts.debuggerPaused(callFrames); +} + +WebInspector.resumedScript = function() +{ + this.panels.scripts.debuggerResumed(); +} + +WebInspector.populateInterface = function() +{ + for (var panelName in this.panels) { + var panel = this.panels[panelName]; + if ("populateInterface" in panel) + panel.populateInterface(); + } +} + +WebInspector.reset = function() +{ + for (var panelName in this.panels) { + var panel = this.panels[panelName]; + if ("reset" in panel) + panel.reset(); + } + + for (var category in this.resourceCategories) + this.resourceCategories[category].removeAllResources(); + + this.resources = {}; + this.resourceURLMap = {}; + this.hoveredDOMNode = null; + + delete this.mainResource; + + this.console.clearMessages(); +} + +WebInspector.resourceURLChanged = function(resource, oldURL) +{ + delete this.resourceURLMap[oldURL]; + this.resourceURLMap[resource.url] = resource; +} + +WebInspector.addMessageToConsole = function(payload) +{ + var consoleMessage = new WebInspector.ConsoleMessage( + payload.source, + payload.type, + payload.level, + payload.line, + payload.url, + payload.groupLevel, + payload.repeatCount); + consoleMessage.setMessageBody(Array.prototype.slice.call(arguments, 1)); + this.console.addMessage(consoleMessage); +} + +WebInspector.log = function(message) +{ + var msg = new WebInspector.ConsoleMessage( + WebInspector.ConsoleMessage.MessageSource.Other, + WebInspector.ConsoleMessage.MessageType.Log, + WebInspector.ConsoleMessage.MessageLevel.Debug, + -1, + null, + null, + 1, + message); + this.console.addMessage(msg); +} + +WebInspector.addProfile = function(profile) +{ + this.panels.profiles.addProfile(profile); +} + +WebInspector.setRecordingProfile = function(isProfiling) +{ + this.panels.profiles.setRecordingProfile(isProfiling); +} + +WebInspector.drawLoadingPieChart = function(canvas, percent) { + var g = canvas.getContext("2d"); + var darkColor = "rgb(122, 168, 218)"; + var lightColor = "rgb(228, 241, 251)"; + var cx = 8; + var cy = 8; + var r = 7; + + g.beginPath(); + g.arc(cx, cy, r, 0, Math.PI * 2, false); + g.closePath(); + + g.lineWidth = 1; + g.strokeStyle = darkColor; + g.fillStyle = lightColor; + g.fill(); + g.stroke(); + + var startangle = -Math.PI / 2; + var endangle = startangle + (percent * Math.PI * 2); + + g.beginPath(); + g.moveTo(cx, cy); + g.arc(cx, cy, r, startangle, endangle, false); + g.closePath(); + + g.fillStyle = darkColor; + g.fill(); +} + +WebInspector.updateFocusedNode = function(nodeId) +{ + var node = WebInspector.domAgent.nodeForId(nodeId); + if (!node) + // FIXME: Should we deselect if null is passed in? + return; + + this.currentPanel = this.panels.elements; + this.panels.elements.focusedDOMNode = node; +} + +WebInspector.displayNameForURL = function(url) +{ + if (!url) + return ""; + var resource = this.resourceURLMap[url]; + if (resource) + return resource.displayName; + return url.trimURL(WebInspector.mainResource ? WebInspector.mainResource.domain : ""); +} + +WebInspector.resourceForURL = function(url) +{ + if (url in this.resourceURLMap) + return this.resourceURLMap[url]; + + // No direct match found. Search for resources that contain + // a substring of the URL. + for (var resourceURL in this.resourceURLMap) { + if (resourceURL.hasSubstring(url)) + return this.resourceURLMap[resourceURL]; + } + + return null; +} + +WebInspector.showResourceForURL = function(url, line, preferredPanel) +{ + var resource = this.resourceForURL(url); + if (!resource) + return false; + + if (preferredPanel && preferredPanel in WebInspector.panels) { + var panel = this.panels[preferredPanel]; + if (!("showResource" in panel)) + panel = null; + else if ("canShowResource" in panel && !panel.canShowResource(resource)) + panel = null; + } + + this.currentPanel = panel || this.panels.resources; + if (!this.currentPanel) + return false; + this.currentPanel.showResource(resource, line); + return true; +} + +WebInspector.linkifyStringAsFragment = function(string) +{ + var container = document.createDocumentFragment(); + var linkStringRegEx = new RegExp("(?:[a-zA-Z][a-zA-Z0-9+.-]{2,}://|www\\.)[\\w$\\-_+*'=\\|/\\\\(){}[\\]%@&#~,:;.!?]{2,}[\\w$\\-_+*=\\|/\\\\({%@&#~]"); + + while (string) { + var linkString = linkStringRegEx.exec(string); + if (!linkString) + break; + + linkString = linkString[0]; + var title = linkString; + var linkIndex = string.indexOf(linkString); + var nonLink = string.substring(0, linkIndex); + container.appendChild(document.createTextNode(nonLink)); + + var profileStringRegEx = new RegExp("webkit-profile://(.+)/[0-9]+"); + var profileStringMatches = profileStringRegEx.exec(title); + var profileTitle; + if (profileStringMatches) + profileTitle = profileStringMatches[1]; + if (profileTitle) + title = WebInspector.panels.profiles.displayTitleForProfileLink(profileTitle); + + var realURL = (linkString.indexOf("www.") === 0 ? "http://" + linkString : linkString); + container.appendChild(WebInspector.linkifyURLAsNode(realURL, title, null, (realURL in WebInspector.resourceURLMap))); + string = string.substring(linkIndex + linkString.length, string.length); + } + + if (string) + container.appendChild(document.createTextNode(string)); + + return container; +} + +WebInspector.showProfileById = function(uid) { + WebInspector.showProfilesPanel(); + WebInspector.panels.profiles.showProfileById(uid); +} + +WebInspector.linkifyURLAsNode = function(url, linkText, classes, isExternal) +{ + if (!linkText) + linkText = url; + classes = (classes ? classes + " " : ""); + classes += isExternal ? "webkit-html-external-link" : "webkit-html-resource-link"; + + var a = document.createElement("a"); + a.href = url; + a.className = classes; + a.title = url; + a.target = "_blank"; + a.textContent = linkText; + + return a; +} + +WebInspector.linkifyURL = function(url, linkText, classes, isExternal) +{ + // Use the DOM version of this function so as to avoid needing to escape attributes. + // FIXME: Get rid of linkifyURL entirely. + return WebInspector.linkifyURLAsNode(url, linkText, classes, isExternal).outerHTML; +} + +WebInspector.addMainEventListeners = function(doc) +{ + doc.defaultView.addEventListener("focus", this.windowFocused.bind(this), true); + doc.defaultView.addEventListener("blur", this.windowBlured.bind(this), true); + doc.addEventListener("click", this.documentClick.bind(this), true); +} + +WebInspector.searchKeyDown = function(event) +{ + if (event.keyIdentifier !== "Enter") + return; + + // Call preventDefault since this was the Enter key. This prevents a "search" event + // from firing for key down. We handle the Enter key on key up in searchKeyUp. This + // stops performSearch from being called twice in a row. + event.preventDefault(); +} + +WebInspector.searchKeyUp = function(event) +{ + if (event.keyIdentifier !== "Enter") + return; + + // Select all of the text so the user can easily type an entirely new query. + event.target.select(); + + // Only call performSearch if the Enter key was pressed. Otherwise the search + // performance is poor because of searching on every key. The search field has + // the incremental attribute set, so we still get incremental searches. + this.performSearch(event); +} + +WebInspector.performSearch = function(event) +{ + var query = event.target.value; + var forceSearch = event.keyIdentifier === "Enter"; + + if (!query || !query.length || (!forceSearch && query.length < 3)) { + delete this.currentQuery; + + for (var panelName in this.panels) { + var panel = this.panels[panelName]; + if (panel.currentQuery && panel.searchCanceled) + panel.searchCanceled(); + delete panel.currentQuery; + } + + this.updateSearchMatchesCount(); + + return; + } + + if (query === this.currentPanel.currentQuery && this.currentPanel.currentQuery === this.currentQuery) { + // When this is the same query and a forced search, jump to the next + // search result for a good user experience. + if (forceSearch && this.currentPanel.jumpToNextSearchResult) + this.currentPanel.jumpToNextSearchResult(); + return; + } + + this.currentQuery = query; + + this.updateSearchMatchesCount(); + + if (!this.currentPanel.performSearch) + return; + + this.currentPanel.currentQuery = query; + this.currentPanel.performSearch(query); +} + +WebInspector.addNodesToSearchResult = function(nodeIds) +{ + WebInspector.panels.elements.addNodesToSearchResult(nodeIds); +} + +WebInspector.updateSearchMatchesCount = function(matches, panel) +{ + if (!panel) + panel = this.currentPanel; + + panel.currentSearchMatches = matches; + + if (panel !== this.currentPanel) + return; + + if (!this.currentPanel.currentQuery) { + document.getElementById("search-results-matches").addStyleClass("hidden"); + return; + } + + if (matches) { + if (matches === 1) + var matchesString = WebInspector.UIString("1 match"); + else + var matchesString = WebInspector.UIString("%d matches", matches); + } else + var matchesString = WebInspector.UIString("Not Found"); + + var matchesToolbarElement = document.getElementById("search-results-matches"); + matchesToolbarElement.removeStyleClass("hidden"); + matchesToolbarElement.textContent = matchesString; +} + +WebInspector.UIString = function(string) +{ + if (window.localizedStrings && string in window.localizedStrings) + string = window.localizedStrings[string]; + else { + if (!(string in this.missingLocalizedStrings)) { + console.error("Localized string \"" + string + "\" not found."); + this.missingLocalizedStrings[string] = true; + } + + if (Preferences.showMissingLocalizedStrings) + string += " (not localized)"; + } + + return String.vsprintf(string, Array.prototype.slice.call(arguments, 1)); +} + +WebInspector.isBeingEdited = function(element) +{ + return element.__editing; +} + +WebInspector.startEditing = function(element, committedCallback, cancelledCallback, context) +{ + if (element.__editing) + return; + element.__editing = true; + + var oldText = getContent(element); + var oldHandleKeyEvent = element.handleKeyEvent; + var moveDirection = ""; + + element.addStyleClass("editing"); + + var oldTabIndex = element.tabIndex; + if (element.tabIndex < 0) + element.tabIndex = 0; + + function blurEventListener() { + editingCommitted.call(element); + } + + function getContent(element) { + if (element.tagName === "INPUT" && element.type === "text") + return element.value; + else + return element.textContent; + } + + function cleanUpAfterEditing() { + delete this.__editing; + + this.removeStyleClass("editing"); + this.tabIndex = oldTabIndex; + this.scrollTop = 0; + this.scrollLeft = 0; + + this.handleKeyEvent = oldHandleKeyEvent; + element.removeEventListener("blur", blurEventListener, false); + + if (element === WebInspector.currentFocusElement || element.isAncestor(WebInspector.currentFocusElement)) + WebInspector.currentFocusElement = WebInspector.previousFocusElement; + } + + function editingCancelled() { + if (this.tagName === "INPUT" && this.type === "text") + this.value = oldText; + else + this.textContent = oldText; + + cleanUpAfterEditing.call(this); + + if (cancelledCallback) + cancelledCallback(this, context); + } + + function editingCommitted() { + cleanUpAfterEditing.call(this); + + if (committedCallback) + committedCallback(this, getContent(this), oldText, context, moveDirection); + } + + element.handleKeyEvent = function(event) { + if (oldHandleKeyEvent) + oldHandleKeyEvent(event); + if (event.handled) + return; + + if (event.keyIdentifier === "Enter") { + editingCommitted.call(element); + event.preventDefault(); + } else if (event.keyCode === 27) { // Escape key + editingCancelled.call(element); + event.preventDefault(); + event.handled = true; + } else if (event.keyIdentifier === "U+0009") // Tab key + moveDirection = (event.shiftKey ? "backward" : "forward"); + } + + element.addEventListener("blur", blurEventListener, false); + + WebInspector.currentFocusElement = element; +} + +WebInspector._toolbarItemClicked = function(event) +{ + var toolbarItem = event.currentTarget; + this.currentPanel = toolbarItem.panel; +} + +// This table maps MIME types to the Resource.Types which are valid for them. +// The following line: +// "text/html": {0: 1}, +// means that text/html is a valid MIME type for resources that have type +// WebInspector.Resource.Type.Document (which has a value of 0). +WebInspector.MIMETypes = { + "text/html": {0: true}, + "text/xml": {0: true}, + "text/plain": {0: true}, + "application/xhtml+xml": {0: true}, + "text/css": {1: true}, + "text/xsl": {1: true}, + "image/jpeg": {2: true}, + "image/png": {2: true}, + "image/gif": {2: true}, + "image/bmp": {2: true}, + "image/vnd.microsoft.icon": {2: true}, + "image/x-icon": {2: true}, + "image/x-xbitmap": {2: true}, + "font/ttf": {3: true}, + "font/opentype": {3: true}, + "application/x-font-type1": {3: true}, + "application/x-font-ttf": {3: true}, + "application/x-truetype-font": {3: true}, + "text/javascript": {4: true}, + "text/ecmascript": {4: true}, + "application/javascript": {4: true}, + "application/ecmascript": {4: true}, + "application/x-javascript": {4: true}, + "text/javascript1.1": {4: true}, + "text/javascript1.2": {4: true}, + "text/javascript1.3": {4: true}, + "text/jscript": {4: true}, + "text/livescript": {4: true}, +} diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/inspector_controller.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/inspector_controller.js new file mode 100644 index 0000000..383cba4 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/inspector_controller.js @@ -0,0 +1,506 @@ +// Copyright (c) 2009 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 Stub implementation of the InspectorController API. + * This stub class is supposed to make front-end a standalone WebApp + * that can be implemented/refactored in isolation from the Web browser + * backend. Clients need to subclass it in order to wire calls to the + * non-stub backends. + */ +goog.provide('devtools.InspectorController'); + + +/** + * Creates inspector controller stub instance. + * @constructor. + */ +devtools.InspectorController = function() { + /** + * @type {boolean} + */ + this.searchingForNode_ = false; + + /** + * @type {boolean} + */ + this.windowVisible_ = true; + + /** + * @type {number} + */ + this.attachedWindowHeight_ = 0; + + /** + * @type {boolean} + */ + this.debuggerEnabled_ = true; + + /** + * @type {boolean} + */ + this.profilerEnabled_ = true; + + /** + * @type {boolean} + */ + this.resourceTrackingEnabled_ = false; + + /** + * @type {boolean} + */ + this.timelineEnabled_ = false; + + /** + * @type {Object} + */ + this.settings_ = {}; +}; + + +/** + * Wraps javascript callback. + * @param {function():undefined} func The callback to wrap. + * @return {function():undefined} Callback wrapper. + */ +devtools.InspectorController.prototype.wrapCallback = function f(func) { + // Just return as is. + return func; +}; + + +/** + * @return {boolean} True iff inspector window is currently visible. + */ +devtools.InspectorController.prototype.isWindowVisible = function() { + return this.windowVisible_; +}; + + +/** + * @return {string} Platform identifier. + */ +devtools.InspectorController.prototype.platform = function() { + return 'windows'; +}; + + +/** + * Closes inspector window. + */ +devtools.InspectorController.prototype.closeWindow = function() { + this.windowVisible_ = false; +}; + + +/** + * Attaches frontend to the backend. + */ +devtools.InspectorController.prototype.attach = function() { +}; + + +/** + * Detaches frontend from the backend. + */ +devtools.InspectorController.prototype.detach = function() { +}; + + +/** + * Tell host that the active panel has changed. + * @param {string} panel Panel name that was last active. + */ +devtools.InspectorController.prototype.storeLastActivePanel = function(panel) { +}; + + +/** + * Clears console message log in the backend. + */ +devtools.InspectorController.prototype.clearMessages = function() { +}; + + +/** + * Returns true iff browser is currently in the search for node mode. + * @return {boolean} True is currently searching for a node. + */ +devtools.InspectorController.prototype.searchingForNode = function() { + return this.searchingForNode_; +}; + + +/** + * Initiates search for a given query starting on a given row. + * @param {number} sourceRow Row to start searching from. + * @param {string} query Query string for search for. + */ +devtools.InspectorController.prototype.search = function(sourceRow, query) { +}; + + +/** + * Toggles node search mode on/off. + */ +devtools.InspectorController.prototype.toggleNodeSearch = function() { + this.searchingForNode_ = !this.searchingForNode_; +}; + + +/** + * Sets the inspector window height while in the attached mode. + * @param {number} height Window height being set. + */ +devtools.InspectorController.prototype.setAttachedWindowHeight = + function(height) { + this.attachedWindowHeight_ = height; +}; + + +/** + * Moves window by the given offset. + * @param {number} x X offset. + * @param {number} y Y offset. + */ +devtools.InspectorController.prototype.moveByUnrestricted = function(x, y) { +}; + + +/** + * Adds resource with given identifier into the given iframe element. + * @param {number} identifier Identifier of the resource to add into the frame. + * @param {Element} element Element to add resource content to. + */ +devtools.InspectorController.prototype.addResourceSourceToFrame = + function(identifier, element) { +}; + + +/** + * Adds given source of a given mimeType into the given iframe element. + * @param {string} mimeType MIME type of the content to be added. + * @param {string} source String content to be added. + * @param {Element} element Element to add resource content to. + */ +devtools.InspectorController.prototype.addSourceToFrame = + function(mimeType, source, element) { + return false; +}; + + +/** + * Returns document node corresponding to the resource with given id. + * @return {Node} Node containing the resource. + */ +devtools.InspectorController.prototype.getResourceDocumentNode = + function(identifier) { + return undefined; +}; + + +/** + * Highlights the given node on the page. + * @param {Node} node Node to highlight. + */ +devtools.InspectorController.prototype.highlightDOMNode = function(node) { + // Does nothing in stub. +}; + + +/** + * Clears current highlight. + */ +devtools.InspectorController.prototype.hideDOMNodeHighlight = function() { + // Does nothing in stub. +}; + + +/** + * @return {window} Inspectable window instance. + */ +devtools.InspectorController.prototype.inspectedWindow = function() { + return window; +}; + + +/** + * Notifies backend that the frontend has been successfully loaded. + */ +devtools.InspectorController.prototype.loaded = function() { + // Does nothing in stub. +}; + + +/** + * @return {string} Url of the i18n-ed strings map. + */ +devtools.InspectorController.prototype.localizedStringsURL = function() { + return undefined; +}; + + +/** + * @return {boolean} True iff window is currently unloading. + */ +devtools.InspectorController.prototype.windowUnloading = function() { + return false; +}; + + +/** + * @return {string} Identifiers of the panels that should be hidden. + */ +devtools.InspectorController.prototype.hiddenPanels = function() { + return ''; +}; + + +/** + * @return {boolean} True iff debugger is enabled. + */ +devtools.InspectorController.prototype.debuggerEnabled = function() { + return this.debuggerEnabled_; +}; + + +/** + * Enables resource tracking. + */ +devtools.InspectorController.prototype.enableResourceTracking = function() { + this.resourceTrackingEnabled_ = true; + WebInspector.resourceTrackingWasEnabled(); +}; + + +/** + * Disables resource tracking. + */ +devtools.InspectorController.prototype.disableResourceTracking = function() { + this.resourceTrackingEnabled_ = false; + WebInspector.resourceTrackingWasDisabled(); +}; + + +/** + * @return {boolean} True iff resource tracking is enabled. + */ +devtools.InspectorController.prototype.resourceTrackingEnabled = function() { + return this.resourceTrackingEnabled_; +}; + + +/** + * Enables timeline. + */ +devtools.InspectorController.prototype.enableTimeline = function() { + this.timelineEnabled_ = true; + WebInspector.timelineWasEnabled(); +}; + + +/** + * Disables timeline. + */ +devtools.InspectorController.prototype.disableTimeline = function() { + this.timelineEnabled_ = false; + WebInspector.timelineWasDisabled(); +}; + +/** + * @return {boolean} True iff timeline is enabled. + */ +devtools.InspectorController.prototype.timelineEnabled = function() { + return this.timelineEnabled_; +}; + + +/** + * Enables debugger. + */ +devtools.InspectorController.prototype.enableDebugger = function() { + this.debuggerEnabled_ = true; +}; + + +/** + * Disables debugger. + */ +devtools.InspectorController.prototype.disableDebugger = function() { + this.debuggerEnabled_ = false; +}; + + +/** + * Adds breakpoint to the given line of the source with given ID. + * @param {string} sourceID Source Id to add breakpoint to. + * @param {number} line Line number to add breakpoint to. + * @param {?string} condition The breakpoint condition. + */ +devtools.InspectorController.prototype.addBreakpoint = + function(sourceID, line, condition) { +}; + + +/** + * Removes breakpoint from the given line of the source with given ID. + * @param {string} sourceID Source Id to remove breakpoint from. + * @param {number} line Line number to remove breakpoint from. + */ +devtools.InspectorController.prototype.removeBreakpoint = + function(sourceID, line) { +}; + + +/** + * Sets a breakpoint condition given a line of the source and an ID. + * @param {string} sourceID Source Id to remove breakpoint from. + * @param {number} line Line number to remove breakpoint from. + * @param {?string} condition New breakpoint condition. + */ +devtools.InspectorController.prototype.updateBreakpoint = + function(sourceID, line, condition) { +}; + + +/** + * Tells backend to pause in the debugger. + */ +devtools.InspectorController.prototype.pauseInDebugger = function() { + // Does nothing in stub. +}; + + +/** + * @return {boolean} True iff the debugger will pause execution on the + * exceptions. + */ +devtools.InspectorController.prototype.pauseOnExceptions = function() { + // Does nothing in stub. + return false; +}; + + +/** + * Tells whether to pause in the debugger on the exceptions or not. + * @param {boolean} value True iff execution should be stopped in the debugger + * on the exceptions. + */ +devtools.InspectorController.prototype.setPauseOnExceptions = function(value) { +}; + + +/** + * Tells backend to resume execution. + */ +devtools.InspectorController.prototype.resumeDebugger = function() { +}; + + +/** + * @return {boolean} True iff profiler is enabled. + */ +devtools.InspectorController.prototype.profilerEnabled = function() { + return true; +}; + + +/** + * Enables profiler. + */ +devtools.InspectorController.prototype.enableProfiler = function() { + this.profilerEnabled_ = true; +}; + + +/** + * Disables profiler. + */ +devtools.InspectorController.prototype.disableProfiler = function() { + this.profilerEnabled_ = false; +}; + + +/** + * Returns given callframe while on a debugger break. + * @return {Object} Current call frame. + */ +devtools.InspectorController.prototype.currentCallFrame = function() { + return undefined; +}; + + +/** + * Tells backend to start collecting profiler data. + */ +devtools.InspectorController.prototype.startProfiling = function() { +}; + + +/** + * Tells backend to stop collecting profiler data. + */ +devtools.InspectorController.prototype.stopProfiling = function() { +}; + + +/** + * @return {Array.<Object>} Profile snapshots array. + */ +devtools.InspectorController.prototype.profiles = function() { + return []; +}; + + +/** + * @return {Array.<string>} Database table names available offline. + */ +devtools.InspectorController.prototype.databaseTableNames = + function(database) { + return []; +}; + + +/** + * Tells backend to step into the function in debugger. + */ +devtools.InspectorController.prototype.stepIntoStatementInDebugger = + function() { +}; + + +/** + * Tells backend to step out of the function in debugger. + */ +devtools.InspectorController.prototype.stepOutOfFunctionInDebugger = + function() {}; + + +/** + * Tells backend to step over the statement in debugger. + */ +devtools.InspectorController.prototype.stepOverStatementInDebugger = + function() { +}; + + +/** + * Sets a setting value in backend. + */ +devtools.InspectorController.prototype.setSetting = + function(setting, value) { + this.settings_[setting] = value; +}; + + +/** + * Retrieves a setting value stored in backend. + */ +devtools.InspectorController.prototype.setting = + function(setting) { + return this.settings_[setting]; +}; + + +var InspectorController = new devtools.InspectorController(); diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/inspector_controller_impl.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/inspector_controller_impl.js new file mode 100644 index 0000000..5bf19b7 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/inspector_controller_impl.js @@ -0,0 +1,286 @@ +// Copyright (c) 2009 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 DevTools' implementation of the InspectorController API. + */ +goog.require('devtools.InspectorController'); + +goog.provide('devtools.InspectorControllerImpl'); + +devtools.InspectorControllerImpl = function() { + devtools.InspectorController.call(this); + this.frame_element_id_ = 1; + + this.installInspectorControllerDelegate_('clearMessages'); + this.installInspectorControllerDelegate_('storeLastActivePanel'); + this.installInspectorControllerDelegate_('highlightDOMNode'); + this.installInspectorControllerDelegate_('hideDOMNodeHighlight'); + this.installInspectorControllerDelegate_('getChildNodes'); + this.installInspectorControllerDelegate_('setAttribute'); + this.installInspectorControllerDelegate_('removeAttribute'); + this.installInspectorControllerDelegate_('setTextNodeValue'); + this.installInspectorControllerDelegate_('enableResourceTracking'); + this.installInspectorControllerDelegate_('disableResourceTracking'); + this.installInspectorControllerDelegate_('enableTimeline'); + this.installInspectorControllerDelegate_('disableTimeline'); + this.installInspectorControllerDelegate_('setting'); + this.installInspectorControllerDelegate_('setSetting'); +}; +goog.inherits(devtools.InspectorControllerImpl, + devtools.InspectorController); + + +/** + * {@inheritDoc}. + */ +devtools.InspectorControllerImpl.prototype.platform = function() { + return DevToolsHost.getPlatform(); +}; + + +/** + * {@inheritDoc}. + */ +devtools.InspectorControllerImpl.prototype.closeWindow = function() { + DevToolsHost.closeWindow(); +}; + + +/** + * {@inheritDoc}. + */ +devtools.InspectorControllerImpl.prototype.attach = function() { + DevToolsHost.dockWindow(); +}; + + +/** + * {@inheritDoc}. + */ +devtools.InspectorControllerImpl.prototype.detach = function() { + DevToolsHost.undockWindow(); +}; + + +/** + * {@inheritDoc}. + */ +devtools.InspectorControllerImpl.prototype.hiddenPanels = function() { + return 'databases'; +}; + + +/** + * {@inheritDoc}. + */ +devtools.InspectorControllerImpl.prototype.search = function(sourceRow, query) { + return DevToolsHost.search(sourceRow, query); +}; + + +/** + * {@inheritDoc}. + */ +devtools.InspectorControllerImpl.prototype.toggleNodeSearch = function() { + devtools.InspectorController.prototype.toggleNodeSearch.call(this); + DevToolsHost.toggleInspectElementMode(this.searchingForNode()); +}; + + +/** + * {@inheritDoc}. + */ +devtools.InspectorControllerImpl.prototype.localizedStringsURL = + function(opt_prefix) { + // l10n is turned off in test mode because delayed loading of strings + // causes test failures. + if (false) { + var locale = DevToolsHost.getApplicationLocale(); + locale = locale.replace('_', '-'); + return 'l10n/localizedStrings_' + locale + '.js'; + } else { + return undefined; + } +}; + + +/** + * {@inheritDoc}. + */ +devtools.InspectorControllerImpl.prototype.addSourceToFrame = + function(mimeType, source, element) { + return DevToolsHost.addSourceToFrame(mimeType, source, element); +}; + + +/** + * {@inheritDoc}. + */ +devtools.InspectorControllerImpl.prototype.addResourceSourceToFrame = + function(identifier, element) { + var resource = WebInspector.resources[identifier]; + if (!resource) { + return; + } + DevToolsHost.addResourceSourceToFrame(identifier, resource.mimeType, element); +}; + + +/** + * {@inheritDoc}. + */ +devtools.InspectorControllerImpl.prototype.inspectedWindow = function() { + return null; +}; + + +/** + * @override + */ +devtools.InspectorControllerImpl.prototype.debuggerEnabled = function() { + return true; +}; + + +devtools.InspectorControllerImpl.prototype.addBreakpoint = function( + sourceID, line, condition) { + devtools.tools.getDebuggerAgent().addBreakpoint(sourceID, line, condition); +}; + + +devtools.InspectorControllerImpl.prototype.removeBreakpoint = function( + sourceID, line) { + devtools.tools.getDebuggerAgent().removeBreakpoint(sourceID, line); +}; + +devtools.InspectorControllerImpl.prototype.updateBreakpoint = function( + sourceID, line, condition) { + devtools.tools.getDebuggerAgent().updateBreakpoint( + sourceID, line, condition); +}; + +devtools.InspectorControllerImpl.prototype.pauseInDebugger = function() { + devtools.tools.getDebuggerAgent().pauseExecution(); +}; + + +devtools.InspectorControllerImpl.prototype.resumeDebugger = function() { + devtools.tools.getDebuggerAgent().resumeExecution(); +}; + + +devtools.InspectorControllerImpl.prototype.stepIntoStatementInDebugger = + function() { + devtools.tools.getDebuggerAgent().stepIntoStatement(); +}; + + +devtools.InspectorControllerImpl.prototype.stepOutOfFunctionInDebugger = + function() { + devtools.tools.getDebuggerAgent().stepOutOfFunction(); +}; + + +devtools.InspectorControllerImpl.prototype.stepOverStatementInDebugger = + function() { + devtools.tools.getDebuggerAgent().stepOverStatement(); +}; + + +/** + * @override + */ +devtools.InspectorControllerImpl.prototype.pauseOnExceptions = function() { + return devtools.tools.getDebuggerAgent().pauseOnExceptions(); +}; + + +/** + * @override + */ +devtools.InspectorControllerImpl.prototype.setPauseOnExceptions = function( + value) { + return devtools.tools.getDebuggerAgent().setPauseOnExceptions(value); +}; + + +/** + * @override + */ +devtools.InspectorControllerImpl.prototype.startProfiling = function() { + devtools.tools.getDebuggerAgent().startProfiling( + devtools.DebuggerAgent.ProfilerModules.PROFILER_MODULE_CPU); +}; + + +/** + * @override + */ +devtools.InspectorControllerImpl.prototype.stopProfiling = function() { + devtools.tools.getDebuggerAgent().stopProfiling( + devtools.DebuggerAgent.ProfilerModules.PROFILER_MODULE_CPU); +}; + + +/** + * @override + */ +devtools.InspectorControllerImpl.prototype.evaluateInCallFrame = + function(callFrameId, code, callback) { + devtools.tools.getDebuggerAgent().evaluateInCallFrame(callFrameId, code, + callback); +}; + + +/** + * @override + */ +devtools.InspectorControllerImpl.prototype.dispatchOnInjectedScript = function( + callId, methodName, argsString) { + var callback = function(result, isException) { + WebInspector.didDispatchOnInjectedScript(callId, + isException ? result : JSON.parse(result), + isException); + }; + RemoteToolsAgent.ExecuteUtilityFunction( + devtools.Callback.wrap(callback), + 'InjectedScript', + JSON.stringify(['dispatch', methodName, argsString])); +}; + + +/** + * Installs delegating handler into the inspector controller. + * @param {string} methodName Method to install delegating handler for. + */ +devtools.InspectorControllerImpl.prototype.installInspectorControllerDelegate_ + = function(methodName) { + this[methodName] = goog.bind(this.callInspectorController_, this, + methodName); +}; + + +/** + * Bound function with the installInjectedScriptDelegate_ actual + * implementation. + */ +devtools.InspectorControllerImpl.prototype.callInspectorController_ = + function(methodName, var_arg) { + var args = Array.prototype.slice.call(arguments); + RemoteToolsAgent.ExecuteUtilityFunction( + devtools.Callback.wrap(function(){}), + 'InspectorController', JSON.stringify(args)); +}; + + +devtools.InspectorControllerImpl.parseWrap_ = function(callback) { + return devtools.Callback.wrap( + function(data) { + callback.call(this, JSON.parse(data)); + }); +}; + + +InspectorController = new devtools.InspectorControllerImpl(); diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/logreader.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/logreader.js new file mode 100644 index 0000000..88ab907 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/logreader.js @@ -0,0 +1,320 @@ +// Copyright 2009 the V8 project authors. All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +/** + * @fileoverview Log Reader is used to process log file produced by V8. + */ + +// Initlialize namespaces +var devtools = devtools || {}; +devtools.profiler = devtools.profiler || {}; + + +/** + * Base class for processing log files. + * + * @param {Array.<Object>} dispatchTable A table used for parsing and processing + * log records. + * @constructor + */ +devtools.profiler.LogReader = function(dispatchTable) { + /** + * @type {Array.<Object>} + */ + this.dispatchTable_ = dispatchTable; + this.dispatchTable_['alias'] = + { parsers: [null, null], processor: this.processAlias_ }; + this.dispatchTable_['repeat'] = + { parsers: [parseInt, 'var-args'], processor: this.processRepeat_, + backrefs: true }; + + /** + * A key-value map for aliases. Translates short name -> full name. + * @type {Object} + */ + this.aliases_ = {}; + + /** + * A key-value map for previous address values. + * @type {Object} + */ + this.prevAddresses_ = {}; + + /** + * A key-value map for events than can be backreference-compressed. + * @type {Object} + */ + this.backRefsCommands_ = {}; + this.initBackRefsCommands_(); + + /** + * Back references for decompression. + * @type {Array.<string>} + */ + this.backRefs_ = []; +}; + + +/** + * Creates a parser for an address entry. + * + * @param {string} addressTag Address tag to perform offset decoding. + * @return {function(string):number} Address parser. + */ +devtools.profiler.LogReader.prototype.createAddressParser = function( + addressTag) { + var self = this; + return (function (str) { + var value = parseInt(str, 16); + var firstChar = str.charAt(0); + if (firstChar == '+' || firstChar == '-') { + var addr = self.prevAddresses_[addressTag]; + addr += value; + self.prevAddresses_[addressTag] = addr; + return addr; + } else if (firstChar != '0' || str.charAt(1) != 'x') { + self.prevAddresses_[addressTag] = value; + } + return value; + }); +}; + + +/** + * Expands an alias symbol, if applicable. + * + * @param {string} symbol Symbol to expand. + * @return {string} Expanded symbol, or the input symbol itself. + */ +devtools.profiler.LogReader.prototype.expandAlias = function(symbol) { + return symbol in this.aliases_ ? this.aliases_[symbol] : symbol; +}; + + +/** + * Used for printing error messages. + * + * @param {string} str Error message. + */ +devtools.profiler.LogReader.prototype.printError = function(str) { + // Do nothing. +}; + + +/** + * Processes a portion of V8 profiler event log. + * + * @param {string} chunk A portion of log. + */ +devtools.profiler.LogReader.prototype.processLogChunk = function(chunk) { + this.processLog_(chunk.split('\n')); +}; + + +/** + * Processes stack record. + * + * @param {number} pc Program counter. + * @param {Array.<string>} stack String representation of a stack. + * @return {Array.<number>} Processed stack. + */ +devtools.profiler.LogReader.prototype.processStack = function(pc, stack) { + var fullStack = [pc]; + var prevFrame = pc; + for (var i = 0, n = stack.length; i < n; ++i) { + var frame = stack[i]; + var firstChar = frame.charAt(0); + if (firstChar == '+' || firstChar == '-') { + // An offset from the previous frame. + prevFrame += parseInt(frame, 16); + fullStack.push(prevFrame); + // Filter out possible 'overflow' string. + } else if (firstChar != 'o') { + fullStack.push(parseInt(frame, 16)); + } + } + return fullStack; +}; + + +/** + * Returns whether a particular dispatch must be skipped. + * + * @param {!Object} dispatch Dispatch record. + * @return {boolean} True if dispatch must be skipped. + */ +devtools.profiler.LogReader.prototype.skipDispatch = function(dispatch) { + return false; +}; + + +/** + * Does a dispatch of a log record. + * + * @param {Array.<string>} fields Log record. + * @private + */ +devtools.profiler.LogReader.prototype.dispatchLogRow_ = function(fields) { + // Obtain the dispatch. + var command = fields[0]; + if (!(command in this.dispatchTable_)) { + throw new Error('unknown command: ' + command); + } + var dispatch = this.dispatchTable_[command]; + + if (dispatch === null || this.skipDispatch(dispatch)) { + return; + } + + // Parse fields. + var parsedFields = []; + for (var i = 0; i < dispatch.parsers.length; ++i) { + var parser = dispatch.parsers[i]; + if (parser === null) { + parsedFields.push(fields[1 + i]); + } else if (typeof parser == 'function') { + parsedFields.push(parser(fields[1 + i])); + } else { + // var-args + parsedFields.push(fields.slice(1 + i)); + break; + } + } + + // Run the processor. + dispatch.processor.apply(this, parsedFields); +}; + + +/** + * Decompresses a line if it was backreference-compressed. + * + * @param {string} line Possibly compressed line. + * @return {string} Decompressed line. + * @private + */ +devtools.profiler.LogReader.prototype.expandBackRef_ = function(line) { + var backRefPos; + // Filter out case when a regexp is created containing '#'. + if (line.charAt(line.length - 1) != '"' + && (backRefPos = line.lastIndexOf('#')) != -1) { + var backRef = line.substr(backRefPos + 1); + var backRefIdx = parseInt(backRef, 10) - 1; + var colonPos = backRef.indexOf(':'); + var backRefStart = + colonPos != -1 ? parseInt(backRef.substr(colonPos + 1), 10) : 0; + line = line.substr(0, backRefPos) + + this.backRefs_[backRefIdx].substr(backRefStart); + } + this.backRefs_.unshift(line); + if (this.backRefs_.length > 10) { + this.backRefs_.length = 10; + } + return line; +}; + + +/** + * Initializes the map of backward reference compressible commands. + * @private + */ +devtools.profiler.LogReader.prototype.initBackRefsCommands_ = function() { + for (var event in this.dispatchTable_) { + var dispatch = this.dispatchTable_[event]; + if (dispatch && dispatch.backrefs) { + this.backRefsCommands_[event] = true; + } + } +}; + + +/** + * Processes alias log record. Adds an alias to a corresponding map. + * + * @param {string} symbol Short name. + * @param {string} expansion Long name. + * @private + */ +devtools.profiler.LogReader.prototype.processAlias_ = function( + symbol, expansion) { + if (expansion in this.dispatchTable_) { + this.dispatchTable_[symbol] = this.dispatchTable_[expansion]; + if (expansion in this.backRefsCommands_) { + this.backRefsCommands_[symbol] = true; + } + } else { + this.aliases_[symbol] = expansion; + } +}; + + +/** + * Processes log lines. + * + * @param {Array.<string>} lines Log lines. + * @private + */ +devtools.profiler.LogReader.prototype.processLog_ = function(lines) { + var csvParser = new devtools.profiler.CsvParser(); + try { + for (var i = 0, n = lines.length; i < n; ++i) { + var line = lines[i]; + if (!line) { + continue; + } + if (line.charAt(0) == '#' || + line.substr(0, line.indexOf(',')) in this.backRefsCommands_) { + line = this.expandBackRef_(line); + } + var fields = csvParser.parseLine(line); + this.dispatchLogRow_(fields); + } + } catch (e) { + // An error on the last line is acceptable since log file can be truncated. + if (i < n - 1) { + this.printError('line ' + (i + 1) + ': ' + (e.message || e)); + throw e; + } + } +}; + + +/** + * Processes repeat log record. Expands it according to calls count and + * invokes processing. + * + * @param {number} count Count. + * @param {Array.<string>} cmd Parsed command. + * @private + */ +devtools.profiler.LogReader.prototype.processRepeat_ = function(count, cmd) { + // Replace the repeat-prefixed command from backrefs list with a non-prefixed. + this.backRefs_[0] = cmd.join(','); + for (var i = 0; i < count; ++i) { + this.dispatchLogRow_(cmd); + } +}; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/profile.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/profile.js new file mode 100644 index 0000000..db4b542 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/profile.js @@ -0,0 +1,621 @@ +// Copyright 2009 the V8 project authors. All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +// Initlialize namespaces +var devtools = devtools || {}; +devtools.profiler = devtools.profiler || {}; + + +/** + * Creates a profile object for processing profiling-related events + * and calculating function execution times. + * + * @constructor + */ +devtools.profiler.Profile = function() { + this.codeMap_ = new devtools.profiler.CodeMap(); + this.topDownTree_ = new devtools.profiler.CallTree(); + this.bottomUpTree_ = new devtools.profiler.CallTree(); +}; + + +/** + * Returns whether a function with the specified name must be skipped. + * Should be overriden by subclasses. + * + * @param {string} name Function name. + */ +devtools.profiler.Profile.prototype.skipThisFunction = function(name) { + return false; +}; + + +/** + * Enum for profiler operations that involve looking up existing + * code entries. + * + * @enum {number} + */ +devtools.profiler.Profile.Operation = { + MOVE: 0, + DELETE: 1, + TICK: 2 +}; + + +/** + * Called whenever the specified operation has failed finding a function + * containing the specified address. Should be overriden by subclasses. + * See the devtools.profiler.Profile.Operation enum for the list of + * possible operations. + * + * @param {number} operation Operation. + * @param {number} addr Address of the unknown code. + * @param {number} opt_stackPos If an unknown address is encountered + * during stack strace processing, specifies a position of the frame + * containing the address. + */ +devtools.profiler.Profile.prototype.handleUnknownCode = function( + operation, addr, opt_stackPos) { +}; + + +/** + * Registers a library. + * + * @param {string} name Code entry name. + * @param {number} startAddr Starting address. + * @param {number} endAddr Ending address. + */ +devtools.profiler.Profile.prototype.addLibrary = function( + name, startAddr, endAddr) { + var entry = new devtools.profiler.CodeMap.CodeEntry( + endAddr - startAddr, name); + this.codeMap_.addLibrary(startAddr, entry); + return entry; +}; + + +/** + * Registers statically compiled code entry. + * + * @param {string} name Code entry name. + * @param {number} startAddr Starting address. + * @param {number} endAddr Ending address. + */ +devtools.profiler.Profile.prototype.addStaticCode = function( + name, startAddr, endAddr) { + var entry = new devtools.profiler.CodeMap.CodeEntry( + endAddr - startAddr, name); + this.codeMap_.addStaticCode(startAddr, entry); + return entry; +}; + + +/** + * Registers dynamic (JIT-compiled) code entry. + * + * @param {string} type Code entry type. + * @param {string} name Code entry name. + * @param {number} start Starting address. + * @param {number} size Code entry size. + */ +devtools.profiler.Profile.prototype.addCode = function( + type, name, start, size) { + var entry = new devtools.profiler.Profile.DynamicCodeEntry(size, type, name); + this.codeMap_.addCode(start, entry); + return entry; +}; + + +/** + * Reports about moving of a dynamic code entry. + * + * @param {number} from Current code entry address. + * @param {number} to New code entry address. + */ +devtools.profiler.Profile.prototype.moveCode = function(from, to) { + try { + this.codeMap_.moveCode(from, to); + } catch (e) { + this.handleUnknownCode(devtools.profiler.Profile.Operation.MOVE, from); + } +}; + + +/** + * Reports about deletion of a dynamic code entry. + * + * @param {number} start Starting address. + */ +devtools.profiler.Profile.prototype.deleteCode = function(start) { + try { + this.codeMap_.deleteCode(start); + } catch (e) { + this.handleUnknownCode(devtools.profiler.Profile.Operation.DELETE, start); + } +}; + + +/** + * Records a tick event. Stack must contain a sequence of + * addresses starting with the program counter value. + * + * @param {Array<number>} stack Stack sample. + */ +devtools.profiler.Profile.prototype.recordTick = function(stack) { + var processedStack = this.resolveAndFilterFuncs_(stack); + this.bottomUpTree_.addPath(processedStack); + processedStack.reverse(); + this.topDownTree_.addPath(processedStack); +}; + + +/** + * Translates addresses into function names and filters unneeded + * functions. + * + * @param {Array<number>} stack Stack sample. + */ +devtools.profiler.Profile.prototype.resolveAndFilterFuncs_ = function(stack) { + var result = []; + for (var i = 0; i < stack.length; ++i) { + var entry = this.codeMap_.findEntry(stack[i]); + if (entry) { + var name = entry.getName(); + if (!this.skipThisFunction(name)) { + result.push(name); + } + } else { + this.handleUnknownCode( + devtools.profiler.Profile.Operation.TICK, stack[i], i); + } + } + return result; +}; + + +/** + * Performs a BF traversal of the top down call graph. + * + * @param {function(devtools.profiler.CallTree.Node)} f Visitor function. + */ +devtools.profiler.Profile.prototype.traverseTopDownTree = function(f) { + this.topDownTree_.traverse(f); +}; + + +/** + * Performs a BF traversal of the bottom up call graph. + * + * @param {function(devtools.profiler.CallTree.Node)} f Visitor function. + */ +devtools.profiler.Profile.prototype.traverseBottomUpTree = function(f) { + this.bottomUpTree_.traverse(f); +}; + + +/** + * Calculates a top down profile for a node with the specified label. + * If no name specified, returns the whole top down calls tree. + * + * @param {string} opt_label Node label. + */ +devtools.profiler.Profile.prototype.getTopDownProfile = function(opt_label) { + return this.getTreeProfile_(this.topDownTree_, opt_label); +}; + + +/** + * Calculates a bottom up profile for a node with the specified label. + * If no name specified, returns the whole bottom up calls tree. + * + * @param {string} opt_label Node label. + */ +devtools.profiler.Profile.prototype.getBottomUpProfile = function(opt_label) { + return this.getTreeProfile_(this.bottomUpTree_, opt_label); +}; + + +/** + * Helper function for calculating a tree profile. + * + * @param {devtools.profiler.Profile.CallTree} tree Call tree. + * @param {string} opt_label Node label. + */ +devtools.profiler.Profile.prototype.getTreeProfile_ = function(tree, opt_label) { + if (!opt_label) { + tree.computeTotalWeights(); + return tree; + } else { + var subTree = tree.cloneSubtree(opt_label); + subTree.computeTotalWeights(); + return subTree; + } +}; + + +/** + * Calculates a flat profile of callees starting from a node with + * the specified label. If no name specified, starts from the root. + * + * @param {string} opt_label Starting node label. + */ +devtools.profiler.Profile.prototype.getFlatProfile = function(opt_label) { + var counters = new devtools.profiler.CallTree(); + var rootLabel = opt_label || devtools.profiler.CallTree.ROOT_NODE_LABEL; + var precs = {}; + precs[rootLabel] = 0; + var root = counters.findOrAddChild(rootLabel); + + this.topDownTree_.computeTotalWeights(); + this.topDownTree_.traverseInDepth( + function onEnter(node) { + if (!(node.label in precs)) { + precs[node.label] = 0; + } + var nodeLabelIsRootLabel = node.label == rootLabel; + if (nodeLabelIsRootLabel || precs[rootLabel] > 0) { + if (precs[rootLabel] == 0) { + root.selfWeight += node.selfWeight; + root.totalWeight += node.totalWeight; + } else { + var rec = root.findOrAddChild(node.label); + rec.selfWeight += node.selfWeight; + if (nodeLabelIsRootLabel || precs[node.label] == 0) { + rec.totalWeight += node.totalWeight; + } + } + precs[node.label]++; + } + }, + function onExit(node) { + if (node.label == rootLabel || precs[rootLabel] > 0) { + precs[node.label]--; + } + }, + null); + + if (!opt_label) { + // If we have created a flat profile for the whole program, we don't + // need an explicit root in it. Thus, replace the counters tree + // root with the node corresponding to the whole program. + counters.root_ = root; + } else { + // Propagate weights so percents can be calculated correctly. + counters.getRoot().selfWeight = root.selfWeight; + counters.getRoot().totalWeight = root.totalWeight; + } + return counters; +}; + + +/** + * Creates a dynamic code entry. + * + * @param {number} size Code size. + * @param {string} type Code type. + * @param {string} name Function name. + * @constructor + */ +devtools.profiler.Profile.DynamicCodeEntry = function(size, type, name) { + devtools.profiler.CodeMap.CodeEntry.call(this, size, name); + this.type = type; +}; + + +/** + * Returns node name. + */ +devtools.profiler.Profile.DynamicCodeEntry.prototype.getName = function() { + var name = this.name; + if (name.length == 0) { + name = '<anonymous>'; + } else if (name.charAt(0) == ' ') { + // An anonymous function with location: " aaa.js:10". + name = '<anonymous>' + name; + } + return this.type + ': ' + name; +}; + + +/** + * Constructs a call graph. + * + * @constructor + */ +devtools.profiler.CallTree = function() { + this.root_ = new devtools.profiler.CallTree.Node( + devtools.profiler.CallTree.ROOT_NODE_LABEL); +}; + + +/** + * The label of the root node. + */ +devtools.profiler.CallTree.ROOT_NODE_LABEL = ''; + + +/** + * @private + */ +devtools.profiler.CallTree.prototype.totalsComputed_ = false; + + +/** + * Returns the tree root. + */ +devtools.profiler.CallTree.prototype.getRoot = function() { + return this.root_; +}; + + +/** + * Adds the specified call path, constructing nodes as necessary. + * + * @param {Array<string>} path Call path. + */ +devtools.profiler.CallTree.prototype.addPath = function(path) { + if (path.length == 0) { + return; + } + var curr = this.root_; + for (var i = 0; i < path.length; ++i) { + curr = curr.findOrAddChild(path[i]); + } + curr.selfWeight++; + this.totalsComputed_ = false; +}; + + +/** + * Finds an immediate child of the specified parent with the specified + * label, creates a child node if necessary. If a parent node isn't + * specified, uses tree root. + * + * @param {string} label Child node label. + */ +devtools.profiler.CallTree.prototype.findOrAddChild = function(label) { + return this.root_.findOrAddChild(label); +}; + + +/** + * Creates a subtree by cloning and merging all subtrees rooted at nodes + * with a given label. E.g. cloning the following call tree on label 'A' + * will give the following result: + * + * <A>--<B> <B> + * / / + * <root> == clone on 'A' ==> <root>--<A> + * \ \ + * <C>--<A>--<D> <D> + * + * And <A>'s selfWeight will be the sum of selfWeights of <A>'s from the + * source call tree. + * + * @param {string} label The label of the new root node. + */ +devtools.profiler.CallTree.prototype.cloneSubtree = function(label) { + var subTree = new devtools.profiler.CallTree(); + this.traverse(function(node, parent) { + if (!parent && node.label != label) { + return null; + } + var child = (parent ? parent : subTree).findOrAddChild(node.label); + child.selfWeight += node.selfWeight; + return child; + }); + return subTree; +}; + + +/** + * Computes total weights in the call graph. + */ +devtools.profiler.CallTree.prototype.computeTotalWeights = function() { + if (this.totalsComputed_) { + return; + } + this.root_.computeTotalWeight(); + this.totalsComputed_ = true; +}; + + +/** + * Traverses the call graph in preorder. This function can be used for + * building optionally modified tree clones. This is the boilerplate code + * for this scenario: + * + * callTree.traverse(function(node, parentClone) { + * var nodeClone = cloneNode(node); + * if (parentClone) + * parentClone.addChild(nodeClone); + * return nodeClone; + * }); + * + * @param {function(devtools.profiler.CallTree.Node, *)} f Visitor function. + * The second parameter is the result of calling 'f' on the parent node. + */ +devtools.profiler.CallTree.prototype.traverse = function(f) { + var pairsToProcess = new ConsArray(); + pairsToProcess.concat([{node: this.root_, param: null}]); + while (!pairsToProcess.atEnd()) { + var pair = pairsToProcess.next(); + var node = pair.node; + var newParam = f(node, pair.param); + var morePairsToProcess = []; + node.forEachChild(function (child) { + morePairsToProcess.push({node: child, param: newParam}); }); + pairsToProcess.concat(morePairsToProcess); + } +}; + + +/** + * Performs an indepth call graph traversal. + * + * @param {function(devtools.profiler.CallTree.Node)} enter A function called + * prior to visiting node's children. + * @param {function(devtools.profiler.CallTree.Node)} exit A function called + * after visiting node's children. + */ +devtools.profiler.CallTree.prototype.traverseInDepth = function(enter, exit) { + function traverse(node) { + enter(node); + node.forEachChild(traverse); + exit(node); + } + traverse(this.root_); +}; + + +/** + * Constructs a call graph node. + * + * @param {string} label Node label. + * @param {devtools.profiler.CallTree.Node} opt_parent Node parent. + */ +devtools.profiler.CallTree.Node = function(label, opt_parent) { + this.label = label; + this.parent = opt_parent; + this.children = {}; +}; + + +/** + * Node self weight (how many times this node was the last node in + * a call path). + * @type {number} + */ +devtools.profiler.CallTree.Node.prototype.selfWeight = 0; + + +/** + * Node total weight (includes weights of all children). + * @type {number} + */ +devtools.profiler.CallTree.Node.prototype.totalWeight = 0; + + +/** + * Adds a child node. + * + * @param {string} label Child node label. + */ +devtools.profiler.CallTree.Node.prototype.addChild = function(label) { + var child = new devtools.profiler.CallTree.Node(label, this); + this.children[label] = child; + return child; +}; + + +/** + * Computes node's total weight. + */ +devtools.profiler.CallTree.Node.prototype.computeTotalWeight = + function() { + var totalWeight = this.selfWeight; + this.forEachChild(function(child) { + totalWeight += child.computeTotalWeight(); }); + return this.totalWeight = totalWeight; +}; + + +/** + * Returns all node's children as an array. + */ +devtools.profiler.CallTree.Node.prototype.exportChildren = function() { + var result = []; + this.forEachChild(function (node) { result.push(node); }); + return result; +}; + + +/** + * Finds an immediate child with the specified label. + * + * @param {string} label Child node label. + */ +devtools.profiler.CallTree.Node.prototype.findChild = function(label) { + return this.children[label] || null; +}; + + +/** + * Finds an immediate child with the specified label, creates a child + * node if necessary. + * + * @param {string} label Child node label. + */ +devtools.profiler.CallTree.Node.prototype.findOrAddChild = function(label) { + return this.findChild(label) || this.addChild(label); +}; + + +/** + * Calls the specified function for every child. + * + * @param {function(devtools.profiler.CallTree.Node)} f Visitor function. + */ +devtools.profiler.CallTree.Node.prototype.forEachChild = function(f) { + for (var c in this.children) { + f(this.children[c]); + } +}; + + +/** + * Walks up from the current node up to the call tree root. + * + * @param {function(devtools.profiler.CallTree.Node)} f Visitor function. + */ +devtools.profiler.CallTree.Node.prototype.walkUpToRoot = function(f) { + for (var curr = this; curr != null; curr = curr.parent) { + f(curr); + } +}; + + +/** + * Tries to find a node with the specified path. + * + * @param {Array<string>} labels The path. + * @param {function(devtools.profiler.CallTree.Node)} opt_f Visitor function. + */ +devtools.profiler.CallTree.Node.prototype.descendToChild = function( + labels, opt_f) { + for (var pos = 0, curr = this; pos < labels.length && curr != null; pos++) { + var child = curr.findChild(labels[pos]); + if (opt_f) { + opt_f(child, pos); + } + curr = child; + } + return curr; +}; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/profile_view.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/profile_view.js new file mode 100644 index 0000000..bdea631 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/profile_view.js @@ -0,0 +1,224 @@ +// Copyright 2009 the V8 project authors. All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +// Initlialize namespaces +var devtools = devtools || {}; +devtools.profiler = devtools.profiler || {}; + + +/** + * Creates a Profile View builder object. + * + * @param {number} samplingRate Number of ms between profiler ticks. + * @constructor + */ +devtools.profiler.ViewBuilder = function(samplingRate) { + this.samplingRate = samplingRate; +}; + + +/** + * Builds a profile view for the specified call tree. + * + * @param {devtools.profiler.CallTree} callTree A call tree. + * @param {boolean} opt_bottomUpViewWeights Whether remapping + * of self weights for a bottom up view is needed. + */ +devtools.profiler.ViewBuilder.prototype.buildView = function( + callTree, opt_bottomUpViewWeights) { + var head; + var samplingRate = this.samplingRate; + var createViewNode = this.createViewNode; + callTree.traverse(function(node, viewParent) { + var totalWeight = node.totalWeight * samplingRate; + var selfWeight = node.selfWeight * samplingRate; + if (opt_bottomUpViewWeights === true) { + if (viewParent === head) { + selfWeight = totalWeight; + } else { + selfWeight = 0; + } + } + var viewNode = createViewNode(node.label, totalWeight, selfWeight, head); + if (viewParent) { + viewParent.addChild(viewNode); + } else { + head = viewNode; + } + return viewNode; + }); + var view = this.createView(head); + return view; +}; + + +/** + * Factory method for a profile view. + * + * @param {devtools.profiler.ProfileView.Node} head View head node. + * @return {devtools.profiler.ProfileView} Profile view. + */ +devtools.profiler.ViewBuilder.prototype.createView = function(head) { + return new devtools.profiler.ProfileView(head); +}; + + +/** + * Factory method for a profile view node. + * + * @param {string} internalFuncName A fully qualified function name. + * @param {number} totalTime Amount of time that application spent in the + * corresponding function and its descendants (not that depending on + * profile they can be either callees or callers.) + * @param {number} selfTime Amount of time that application spent in the + * corresponding function only. + * @param {devtools.profiler.ProfileView.Node} head Profile view head. + * @return {devtools.profiler.ProfileView.Node} Profile view node. + */ +devtools.profiler.ViewBuilder.prototype.createViewNode = function( + funcName, totalTime, selfTime, head) { + return new devtools.profiler.ProfileView.Node( + funcName, totalTime, selfTime, head); +}; + + +/** + * Creates a Profile View object. It allows to perform sorting + * and filtering actions on the profile. + * + * @param {devtools.profiler.ProfileView.Node} head Head (root) node. + * @constructor + */ +devtools.profiler.ProfileView = function(head) { + this.head = head; +}; + + +/** + * Sorts the profile view using the specified sort function. + * + * @param {function(devtools.profiler.ProfileView.Node, + * devtools.profiler.ProfileView.Node):number} sortFunc A sorting + * functions. Must comply with Array.sort sorting function requirements. + */ +devtools.profiler.ProfileView.prototype.sort = function(sortFunc) { + this.traverse(function (node) { + node.sortChildren(sortFunc); + }); +}; + + +/** + * Traverses profile view nodes in preorder. + * + * @param {function(devtools.profiler.ProfileView.Node)} f Visitor function. + */ +devtools.profiler.ProfileView.prototype.traverse = function(f) { + var nodesToTraverse = new ConsArray(); + nodesToTraverse.concat([this.head]); + while (!nodesToTraverse.atEnd()) { + var node = nodesToTraverse.next(); + f(node); + nodesToTraverse.concat(node.children); + } +}; + + +/** + * Constructs a Profile View node object. Each node object corresponds to + * a function call. + * + * @param {string} internalFuncName A fully qualified function name. + * @param {number} totalTime Amount of time that application spent in the + * corresponding function and its descendants (not that depending on + * profile they can be either callees or callers.) + * @param {number} selfTime Amount of time that application spent in the + * corresponding function only. + * @param {devtools.profiler.ProfileView.Node} head Profile view head. + * @constructor + */ +devtools.profiler.ProfileView.Node = function( + internalFuncName, totalTime, selfTime, head) { + this.internalFuncName = internalFuncName; + this.totalTime = totalTime; + this.selfTime = selfTime; + this.head = head; + this.parent = null; + this.children = []; +}; + + +/** + * Returns a share of the function's total time in application's total time. + */ +devtools.profiler.ProfileView.Node.prototype.__defineGetter__( + 'totalPercent', + function() { return this.totalTime / + (this.head ? this.head.totalTime : this.totalTime) * 100.0; }); + + +/** + * Returns a share of the function's self time in application's total time. + */ +devtools.profiler.ProfileView.Node.prototype.__defineGetter__( + 'selfPercent', + function() { return this.selfTime / + (this.head ? this.head.totalTime : this.totalTime) * 100.0; }); + + +/** + * Returns a share of the function's total time in its parent's total time. + */ +devtools.profiler.ProfileView.Node.prototype.__defineGetter__( + 'parentTotalPercent', + function() { return this.totalTime / + (this.parent ? this.parent.totalTime : this.totalTime) * 100.0; }); + + +/** + * Adds a child to the node. + * + * @param {devtools.profiler.ProfileView.Node} node Child node. + */ +devtools.profiler.ProfileView.Node.prototype.addChild = function(node) { + node.parent = this; + this.children.push(node); +}; + + +/** + * Sorts all the node's children recursively. + * + * @param {function(devtools.profiler.ProfileView.Node, + * devtools.profiler.ProfileView.Node):number} sortFunc A sorting + * functions. Must comply with Array.sort sorting function requirements. + */ +devtools.profiler.ProfileView.Node.prototype.sortChildren = function( + sortFunc) { + this.children.sort(sortFunc); +}; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/profiler_processor.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/profiler_processor.js new file mode 100644 index 0000000..13aeee7 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/profiler_processor.js @@ -0,0 +1,449 @@ +// Copyright (c) 2009 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 Profiler processor is used to process log file produced + * by V8 and produce an internal profile representation which is used + * for building profile views in 'Profiles' tab. + */ +goog.provide('devtools.profiler.Processor'); + + +/** + * Creates a Profile View builder object compatible with WebKit Profiler UI. + * + * @param {number} samplingRate Number of ms between profiler ticks. + * @constructor + */ +devtools.profiler.WebKitViewBuilder = function(samplingRate) { + devtools.profiler.ViewBuilder.call(this, samplingRate); +}; +goog.inherits(devtools.profiler.WebKitViewBuilder, + devtools.profiler.ViewBuilder); + + +/** + * @override + */ +devtools.profiler.WebKitViewBuilder.prototype.createViewNode = function( + funcName, totalTime, selfTime, head) { + return new devtools.profiler.WebKitViewNode( + funcName, totalTime, selfTime, head); +}; + + +/** + * Constructs a Profile View node object for displaying in WebKit Profiler UI. + * + * @param {string} internalFuncName A fully qualified function name. + * @param {number} totalTime Amount of time that application spent in the + * corresponding function and its descendants (not that depending on + * profile they can be either callees or callers.) + * @param {number} selfTime Amount of time that application spent in the + * corresponding function only. + * @param {devtools.profiler.ProfileView.Node} head Profile view head. + * @constructor + */ +devtools.profiler.WebKitViewNode = function( + internalFuncName, totalTime, selfTime, head) { + devtools.profiler.ProfileView.Node.call(this, + internalFuncName, totalTime, selfTime, head); + this.initFuncInfo_(); + this.callUID = internalFuncName; +}; +goog.inherits(devtools.profiler.WebKitViewNode, + devtools.profiler.ProfileView.Node); + + +/** + * RegEx for stripping V8's prefixes of compiled functions. + */ +devtools.profiler.WebKitViewNode.FUNC_NAME_STRIP_RE = + /^(?:LazyCompile|Function): (.*)$/; + + +/** + * RegEx for extracting script source URL and line number. + */ +devtools.profiler.WebKitViewNode.FUNC_NAME_PARSE_RE = + /^([^ ]+) (.*):(\d+)( \{\d+\})?$/; + + +/** + * Inits 'functionName', 'url', and 'lineNumber' fields using 'internalFuncName' + * field. + * @private + */ +devtools.profiler.WebKitViewNode.prototype.initFuncInfo_ = function() { + var nodeAlias = devtools.profiler.WebKitViewNode; + this.functionName = this.internalFuncName; + + var strippedName = nodeAlias.FUNC_NAME_STRIP_RE.exec(this.functionName); + if (strippedName) { + this.functionName = strippedName[1]; + } + + var parsedName = nodeAlias.FUNC_NAME_PARSE_RE.exec(this.functionName); + if (parsedName) { + this.functionName = parsedName[1]; + if (parsedName[4]) { + this.functionName += parsedName[4]; + } + this.url = parsedName[2]; + this.lineNumber = parsedName[3]; + } else { + this.url = ''; + this.lineNumber = 0; + } +}; + + +/** + * Ancestor of a profile object that leaves out only JS-related functions. + * @constructor + */ +devtools.profiler.JsProfile = function() { + devtools.profiler.Profile.call(this); +}; +goog.inherits(devtools.profiler.JsProfile, devtools.profiler.Profile); + + +/** + * RegExp that leaves only JS functions. + * @type {RegExp} + */ +devtools.profiler.JsProfile.JS_FUNC_RE = /^(LazyCompile|Function|Script):/; + +/** + * RegExp that filters out native code (ending with "native src.js:xxx"). + * @type {RegExp} + */ +devtools.profiler.JsProfile.JS_NATIVE_FUNC_RE = /\ native\ \w+\.js:\d+$/; + +/** + * RegExp that filters out native scripts. + * @type {RegExp} + */ +devtools.profiler.JsProfile.JS_NATIVE_SCRIPT_RE = /^Script:\ native/; + +/** + * RegExp that filters out devtools functions. See inject.js and + * inject_dispatch.js. + * @type {RegExp} + */ +devtools.profiler.JsProfile.JS_DEVTOOLS_FUNC_RE = + /^\w+:\ devtools(\$\$|\.Injected)/; + + +/** + * @override + */ +devtools.profiler.JsProfile.prototype.skipThisFunction = function(name) { + return !devtools.profiler.JsProfile.JS_FUNC_RE.test(name) || + // To profile V8's natives comment out two lines below and '||' above. + devtools.profiler.JsProfile.JS_NATIVE_FUNC_RE.test(name) || + devtools.profiler.JsProfile.JS_NATIVE_SCRIPT_RE.test(name) || + devtools.profiler.JsProfile.JS_DEVTOOLS_FUNC_RE.test(name); +}; + + +/** + * Profiler processor. Consumes profiler log and builds profile views. + * + * @param {function(devtools.profiler.ProfileView)} newProfileCallback Callback + * that receives a new processed profile. + * @constructor + */ +devtools.profiler.Processor = function() { + devtools.profiler.LogReader.call(this, { + 'code-creation': { + parsers: [null, this.createAddressParser('code'), parseInt, null], + processor: this.processCodeCreation_, backrefs: true, + needsProfile: true }, + 'code-move': { parsers: [this.createAddressParser('code'), + this.createAddressParser('code-move-to')], + processor: this.processCodeMove_, backrefs: true, + needsProfile: true }, + 'code-delete': { parsers: [this.createAddressParser('code')], + processor: this.processCodeDelete_, backrefs: true, + needsProfile: true }, + 'tick': { parsers: [this.createAddressParser('code'), + this.createAddressParser('stack'), parseInt, 'var-args'], + processor: this.processTick_, backrefs: true, needProfile: true }, + 'profiler': { parsers: [null, 'var-args'], + processor: this.processProfiler_, needsProfile: false }, + 'heap-sample-begin': { parsers: [null, null, parseInt], + processor: this.processHeapSampleBegin_ }, + 'heap-sample-stats': { parsers: [null, null, parseInt, parseInt], + processor: this.processHeapSampleStats_ }, + 'heap-sample-item': { parsers: [null, parseInt, parseInt], + processor: this.processHeapSampleItem_ }, + 'heap-js-cons-item': { parsers: [null, parseInt, parseInt], + processor: this.processHeapJsConsItem_ }, + 'heap-sample-end': { parsers: [null, null], + processor: this.processHeapSampleEnd_ }, + // Not used in DevTools Profiler. + 'shared-library': null, + 'heap-js-ret-item': null, + // Obsolete row types. + 'code-allocate': null, + 'begin-code-region': null, + 'end-code-region': null}); + + + /** + * Callback that is called when a new profile is encountered in the log. + * @type {function()} + */ + this.startedProfileProcessing_ = null; + + /** + * Callback that is called periodically to display processing status. + * @type {function()} + */ + this.profileProcessingStatus_ = null; + + /** + * Callback that is called when a profile has been processed and is ready + * to be shown. + * @type {function(devtools.profiler.ProfileView)} + */ + this.finishedProfileProcessing_ = null; + + /** + * The current profile. + * @type {devtools.profiler.JsProfile} + */ + this.currentProfile_ = null; + + /** + * Builder of profile views. Created during "profiler,begin" event processing. + * @type {devtools.profiler.WebKitViewBuilder} + */ + this.viewBuilder_ = null; + + /** + * Next profile id. + * @type {number} + */ + this.profileId_ = 1; + + /** + * Counter for processed ticks. + * @type {number} + */ + this.ticksCount_ = 0; + + /** + * The current heap snapshot. + * @type {string} + */ + this.currentHeapSnapshot_ = null; + + /** + * Next heap snapshot id. + * @type {number} + */ + this.heapSnapshotId_ = 1; +}; +goog.inherits(devtools.profiler.Processor, devtools.profiler.LogReader); + + +/** + * @override + */ +devtools.profiler.Processor.prototype.printError = function(str) { + debugPrint(str); +}; + + +/** + * @override + */ +devtools.profiler.Processor.prototype.skipDispatch = function(dispatch) { + return dispatch.needsProfile && this.currentProfile_ == null; +}; + + +/** + * Sets profile processing callbacks. + * + * @param {function()} started Started processing callback. + * @param {function(devtools.profiler.ProfileView)} finished Finished + * processing callback. + */ +devtools.profiler.Processor.prototype.setCallbacks = function( + started, processing, finished) { + this.startedProfileProcessing_ = started; + this.profileProcessingStatus_ = processing; + this.finishedProfileProcessing_ = finished; +}; + + +/** + * An address for the fake "(program)" entry. WebKit's visualisation + * has assumptions on how the top of the call tree should look like, + * and we need to add a fake entry as the topmost function. This + * address is chosen because it's the end address of the first memory + * page, which is never used for code or data, but only as a guard + * page for catching AV errors. + * + * @type {number} + */ +devtools.profiler.Processor.PROGRAM_ENTRY = 0xffff; +/** + * @type {string} + */ +devtools.profiler.Processor.PROGRAM_ENTRY_STR = '0xffff'; + + +/** + * Sets new profile callback. + * @param {function(devtools.profiler.ProfileView)} callback Callback function. + */ +devtools.profiler.Processor.prototype.setNewProfileCallback = function( + callback) { + this.newProfileCallback_ = callback; +}; + + +devtools.profiler.Processor.prototype.processProfiler_ = function( + state, params) { + var processingInterval = null; + switch (state) { + case 'resume': + if (this.currentProfile_ == null) { + this.currentProfile_ = new devtools.profiler.JsProfile(); + // see the comment for devtools.profiler.Processor.PROGRAM_ENTRY + this.currentProfile_.addCode( + 'Function', '(program)', + devtools.profiler.Processor.PROGRAM_ENTRY, 1); + if (this.startedProfileProcessing_) { + this.startedProfileProcessing_(); + } + this.ticksCount_ = 0; + var self = this; + if (this.profileProcessingStatus_) { + processingInterval = window.setInterval( + function() { self.profileProcessingStatus_(self.ticksCount_); }, + 1000); + } + } + break; + case 'pause': + if (this.currentProfile_ != null) { + window.clearInterval(processingInterval); + if (this.finishedProfileProcessing_) { + this.finishedProfileProcessing_(this.createProfileForView()); + } + this.currentProfile_ = null; + } + break; + case 'begin': + var samplingRate = NaN; + if (params.length > 0) { + samplingRate = parseInt(params[0]); + } + if (isNaN(samplingRate)) { + samplingRate = 1; + } + this.viewBuilder_ = new devtools.profiler.WebKitViewBuilder(samplingRate); + break; + // These events are valid but aren't used. + case 'compression': + case 'end': break; + default: + throw new Error('unknown profiler state: ' + state); + } +}; + + +devtools.profiler.Processor.prototype.processCodeCreation_ = function( + type, start, size, name) { + this.currentProfile_.addCode(this.expandAlias(type), name, start, size); +}; + + +devtools.profiler.Processor.prototype.processCodeMove_ = function(from, to) { + this.currentProfile_.moveCode(from, to); +}; + + +devtools.profiler.Processor.prototype.processCodeDelete_ = function(start) { + this.currentProfile_.deleteCode(start); +}; + + +devtools.profiler.Processor.prototype.processTick_ = function( + pc, sp, vmState, stack) { + // see the comment for devtools.profiler.Processor.PROGRAM_ENTRY + stack.push(devtools.profiler.Processor.PROGRAM_ENTRY_STR); + this.currentProfile_.recordTick(this.processStack(pc, stack)); + this.ticksCount_++; +}; + + +devtools.profiler.Processor.prototype.processHeapSampleBegin_ = function( + space, state, ticks) { + if (space != 'Heap') return; + this.currentHeapSnapshot_ = { + number: this.heapSnapshotId_++, + entries: {}, + lowlevels: {}, + ticks: ticks + }; +}; + + +devtools.profiler.Processor.prototype.processHeapSampleStats_ = function( + space, state, capacity, used) { + if (space != 'Heap') return; + this.currentHeapSnapshot_.capacity = capacity; + this.currentHeapSnapshot_.used = used; +}; + + +devtools.profiler.Processor.prototype.processHeapSampleItem_ = function( + item, number, size) { + if (!this.currentHeapSnapshot_) return; + this.currentHeapSnapshot_.lowlevels[item] = { + type: item, count: number, size: size + }; +}; + + +devtools.profiler.Processor.prototype.processHeapJsConsItem_ = function( + item, number, size) { + if (!this.currentHeapSnapshot_) return; + this.currentHeapSnapshot_.entries[item] = { + cons: item, count: number, size: size + }; +}; + + +devtools.profiler.Processor.prototype.processHeapSampleEnd_ = function( + space, state) { + if (space != 'Heap') return; + var snapshot = this.currentHeapSnapshot_; + this.currentHeapSnapshot_ = null; + // For some reason, 'used' from 'heap-sample-stats' sometimes differ from + // the sum of objects sizes. To avoid discrepancy, we re-calculate 'used'. + snapshot.used = 0; + for (var item in snapshot.lowlevels) { + snapshot.used += snapshot.lowlevels[item].size; + } + WebInspector.panels.heap.addSnapshot(snapshot); +}; + + +/** + * Creates a profile for further displaying in ProfileView. + */ +devtools.profiler.Processor.prototype.createProfileForView = function() { + var profile = this.viewBuilder_.buildView( + this.currentProfile_.getTopDownProfile()); + profile.uid = this.profileId_++; + profile.title = UserInitiatedProfileName + '.' + profile.uid; + return profile; +}; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/splaytree.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/splaytree.js new file mode 100644 index 0000000..7b3af8b --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/splaytree.js @@ -0,0 +1,322 @@ +// Copyright 2009 the V8 project authors. All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +// A namespace stub. It will become more clear how to declare it properly +// during integration of this script into Dev Tools. +var goog = goog || {}; +goog.structs = goog.structs || {}; + + +/** + * Constructs a Splay tree. A splay tree is a self-balancing binary + * search tree with the additional property that recently accessed + * elements are quick to access again. It performs basic operations + * such as insertion, look-up and removal in O(log(n)) amortized time. + * + * @constructor + */ +goog.structs.SplayTree = function() { +}; + + +/** + * Pointer to the root node of the tree. + * + * @type {goog.structs.SplayTree.Node} + * @private + */ +goog.structs.SplayTree.prototype.root_ = null; + + +/** + * @return {boolean} Whether the tree is empty. + */ +goog.structs.SplayTree.prototype.isEmpty = function() { + return !this.root_; +}; + + + +/** + * Inserts a node into the tree with the specified key and value if + * the tree does not already contain a node with the specified key. If + * the value is inserted, it becomes the root of the tree. + * + * @param {number} key Key to insert into the tree. + * @param {*} value Value to insert into the tree. + */ +goog.structs.SplayTree.prototype.insert = function(key, value) { + if (this.isEmpty()) { + this.root_ = new goog.structs.SplayTree.Node(key, value); + return; + } + // Splay on the key to move the last node on the search path for + // the key to the root of the tree. + this.splay_(key); + if (this.root_.key == key) { + return; + } + var node = new goog.structs.SplayTree.Node(key, value); + if (key > this.root_.key) { + node.left = this.root_; + node.right = this.root_.right; + this.root_.right = null; + } else { + node.right = this.root_; + node.left = this.root_.left; + this.root_.left = null; + } + this.root_ = node; +}; + + +/** + * Removes a node with the specified key from the tree if the tree + * contains a node with this key. The removed node is returned. If the + * key is not found, an exception is thrown. + * + * @param {number} key Key to find and remove from the tree. + * @return {goog.structs.SplayTree.Node} The removed node. + */ +goog.structs.SplayTree.prototype.remove = function(key) { + if (this.isEmpty()) { + throw Error('Key not found: ' + key); + } + this.splay_(key); + if (this.root_.key != key) { + throw Error('Key not found: ' + key); + } + var removed = this.root_; + if (!this.root_.left) { + this.root_ = this.root_.right; + } else { + var right = this.root_.right; + this.root_ = this.root_.left; + // Splay to make sure that the new root has an empty right child. + this.splay_(key); + // Insert the original right child as the right child of the new + // root. + this.root_.right = right; + } + return removed; +}; + + +/** + * Returns the node having the specified key or null if the tree doesn't contain + * a node with the specified key. + * + * @param {number} key Key to find in the tree. + * @return {goog.structs.SplayTree.Node} Node having the specified key. + */ +goog.structs.SplayTree.prototype.find = function(key) { + if (this.isEmpty()) { + return null; + } + this.splay_(key); + return this.root_.key == key ? this.root_ : null; +}; + + +/** + * @return {goog.structs.SplayTree.Node} Node having the minimum key value. + */ +goog.structs.SplayTree.prototype.findMin = function() { + if (this.isEmpty()) { + return null; + } + var current = this.root_; + while (current.left) { + current = current.left; + } + return current; +}; + + +/** + * @return {goog.structs.SplayTree.Node} Node having the maximum key value. + */ +goog.structs.SplayTree.prototype.findMax = function(opt_startNode) { + if (this.isEmpty()) { + return null; + } + var current = opt_startNode || this.root_; + while (current.right) { + current = current.right; + } + return current; +}; + + +/** + * @return {goog.structs.SplayTree.Node} Node having the maximum key value that + * is less or equal to the specified key value. + */ +goog.structs.SplayTree.prototype.findGreatestLessThan = function(key) { + if (this.isEmpty()) { + return null; + } + // Splay on the key to move the node with the given key or the last + // node on the search path to the top of the tree. + this.splay_(key); + // Now the result is either the root node or the greatest node in + // the left subtree. + if (this.root_.key <= key) { + return this.root_; + } else if (this.root_.left) { + return this.findMax(this.root_.left); + } else { + return null; + } +}; + + +/** + * @return {Array<*>} An array containing all the values of tree's nodes. + */ +goog.structs.SplayTree.prototype.exportValues = function() { + var result = []; + this.traverse_(function(node) { result.push(node.value); }); + return result; +}; + + +/** + * Perform the splay operation for the given key. Moves the node with + * the given key to the top of the tree. If no node has the given + * key, the last node on the search path is moved to the top of the + * tree. This is the simplified top-down splaying algorithm from: + * "Self-adjusting Binary Search Trees" by Sleator and Tarjan + * + * @param {number} key Key to splay the tree on. + * @private + */ +goog.structs.SplayTree.prototype.splay_ = function(key) { + if (this.isEmpty()) { + return; + } + // Create a dummy node. The use of the dummy node is a bit + // counter-intuitive: The right child of the dummy node will hold + // the L tree of the algorithm. The left child of the dummy node + // will hold the R tree of the algorithm. Using a dummy node, left + // and right will always be nodes and we avoid special cases. + var dummy, left, right; + dummy = left = right = new goog.structs.SplayTree.Node(null, null); + var current = this.root_; + while (true) { + if (key < current.key) { + if (!current.left) { + break; + } + if (key < current.left.key) { + // Rotate right. + var tmp = current.left; + current.left = tmp.right; + tmp.right = current; + current = tmp; + if (!current.left) { + break; + } + } + // Link right. + right.left = current; + right = current; + current = current.left; + } else if (key > current.key) { + if (!current.right) { + break; + } + if (key > current.right.key) { + // Rotate left. + var tmp = current.right; + current.right = tmp.left; + tmp.left = current; + current = tmp; + if (!current.right) { + break; + } + } + // Link left. + left.right = current; + left = current; + current = current.right; + } else { + break; + } + } + // Assemble. + left.right = current.left; + right.left = current.right; + current.left = dummy.right; + current.right = dummy.left; + this.root_ = current; +}; + + +/** + * Performs a preorder traversal of the tree. + * + * @param {function(goog.structs.SplayTree.Node)} f Visitor function. + * @private + */ +goog.structs.SplayTree.prototype.traverse_ = function(f) { + var nodesToVisit = [this.root_]; + while (nodesToVisit.length > 0) { + var node = nodesToVisit.shift(); + if (node == null) { + continue; + } + f(node); + nodesToVisit.push(node.left); + nodesToVisit.push(node.right); + } +}; + + +/** + * Constructs a Splay tree node. + * + * @param {number} key Key. + * @param {*} value Value. + */ +goog.structs.SplayTree.Node = function(key, value) { + this.key = key; + this.value = value; +}; + + +/** + * @type {goog.structs.SplayTree.Node} + */ +goog.structs.SplayTree.Node.prototype.left = null; + + +/** + * @type {goog.structs.SplayTree.Node} + */ +goog.structs.SplayTree.Node.prototype.right = null; diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/tests.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/tests.js new file mode 100644 index 0000000..5691017 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/tests.js @@ -0,0 +1,628 @@ +// Copyright (c) 2009 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 contains small testing framework along with the + * test suite for the frontend. These tests are a part of the continues build + * and are executed by the devtools_sanity_unittest.cc as a part of the + * Interactive UI Test suite. + */ + +if (window.domAutomationController) { + +var ___interactiveUiTestsMode = true; + +/** + * Test suite for interactive UI tests. + * @constructor + */ +TestSuite = function() { + this.controlTaken_ = false; + this.timerId_ = -1; +}; + + +/** + * Reports test failure. + * @param {string} message Failure description. + */ +TestSuite.prototype.fail = function(message) { + if (this.controlTaken_) { + this.reportFailure_(message); + } else { + throw message; + } +}; + + +/** + * Equals assertion tests that expected == actual. + * @param {Object} expected Expected object. + * @param {Object} actual Actual object. + * @param {string} opt_message User message to print if the test fails. + */ +TestSuite.prototype.assertEquals = function(expected, actual, opt_message) { + if (expected != actual) { + var message = 'Expected: "' + expected + '", but was "' + actual + '"'; + if (opt_message) { + message = opt_message + '(' + message + ')'; + } + this.fail(message); + } +}; + + +/** + * True assertion tests that value == true. + * @param {Object} value Actual object. + * @param {string} opt_message User message to print if the test fails. + */ +TestSuite.prototype.assertTrue = function(value, opt_message) { + this.assertEquals(true, value, opt_message); +}; + + +/** + * Contains assertion tests that string contains substring. + * @param {string} string Outer. + * @param {string} substring Inner. + */ +TestSuite.prototype.assertContains = function(string, substring) { + if (string.indexOf(substring) == -1) { + this.fail('Expected to: "' + string + '" to contain "' + substring + '"'); + } +}; + + +/** + * Takes control over execution. + */ +TestSuite.prototype.takeControl = function() { + this.controlTaken_ = true; + // Set up guard timer. + var self = this; + this.timerId_ = setTimeout(function() { + self.reportFailure_('Timeout exceeded: 20 sec'); + }, 20000); +}; + + +/** + * Releases control over execution. + */ +TestSuite.prototype.releaseControl = function() { + if (this.timerId_ != -1) { + clearTimeout(this.timerId_); + this.timerId_ = -1; + } + this.reportOk_(); +}; + + +/** + * Async tests use this one to report that they are completed. + */ +TestSuite.prototype.reportOk_ = function() { + window.domAutomationController.send('[OK]'); +}; + + +/** + * Async tests use this one to report failures. + */ +TestSuite.prototype.reportFailure_ = function(error) { + if (this.timerId_ != -1) { + clearTimeout(this.timerId_); + this.timerId_ = -1; + } + window.domAutomationController.send('[FAILED] ' + error); +}; + + +/** + * Runs all global functions starting with 'test' as unit tests. + */ +TestSuite.prototype.runTest = function(testName) { + try { + this[testName](); + if (!this.controlTaken_) { + this.reportOk_(); + } + } catch (e) { + this.reportFailure_(e); + } +}; + + +/** + * @param {string} panelName Name of the panel to show. + */ +TestSuite.prototype.showPanel = function(panelName) { + // Open Scripts panel. + var toolbar = document.getElementById('toolbar'); + var button = toolbar.getElementsByClassName(panelName)[0]; + button.click(); + this.assertEquals(WebInspector.panels[panelName], + WebInspector.currentPanel); +}; + + +/** + * Overrides the method with specified name until it's called first time. + * @param {Object} receiver An object whose method to override. + * @param {string} methodName Name of the method to override. + * @param {Function} override A function that should be called right after the + * overriden method returns. + * @param {boolean} opt_sticky Whether restore original method after first run + * or not. + */ +TestSuite.prototype.addSniffer = function(receiver, methodName, override, + opt_sticky) { + var orig = receiver[methodName]; + if (typeof orig != 'function') { + this.fail('Cannot find method to override: ' + methodName); + } + var test = this; + receiver[methodName] = function(var_args) { + try { + var result = orig.apply(this, arguments); + } finally { + if (!opt_sticky) { + receiver[methodName] = orig; + } + } + // In case of exception the override won't be called. + try { + override.apply(this, arguments); + } catch (e) { + test.fail('Exception in overriden method "' + methodName + '": ' + e); + } + return result; + }; +}; + + +// UI Tests + + +/** + * Tests that the real injected host is present in the context. + */ +TestSuite.prototype.testHostIsPresent = function() { + this.assertTrue(typeof DevToolsHost == 'object' && !DevToolsHost.isStub); +}; + + +/** + * Tests elements tree has an 'HTML' root. + */ +TestSuite.prototype.testElementsTreeRoot = function() { + var doc = WebInspector.domAgent.document; + this.assertEquals('HTML', doc.documentElement.nodeName); + this.assertTrue(doc.documentElement.hasChildNodes()); +}; + + +/** + * Tests that main resource is present in the system and that it is + * the only resource. + */ +TestSuite.prototype.testMainResource = function() { + var tokens = []; + var resources = WebInspector.resources; + for (var id in resources) { + tokens.push(resources[id].lastPathComponent); + } + this.assertEquals('simple_page.html', tokens.join(',')); +}; + + +/** + * Tests that resources tab is enabled when corresponding item is selected. + */ +TestSuite.prototype.testEnableResourcesTab = function() { + this.showPanel('resources'); + + var test = this; + this.addSniffer(WebInspector, 'addResource', + function(identifier, payload) { + test.assertEquals('simple_page.html', payload.lastPathComponent); + WebInspector.panels.resources.refresh(); + WebInspector.resources[identifier]._resourcesTreeElement.select(); + + test.releaseControl(); + }); + + // Following call should lead to reload that we capture in the + // addResource override. + WebInspector.panels.resources._enableResourceTracking(); + + // We now have some time to report results to controller. + this.takeControl(); +}; + + +/** + * Tests resource headers. + */ +TestSuite.prototype.testResourceHeaders = function() { + this.showPanel('resources'); + + var test = this; + + var requestOk = false; + var responseOk = false; + var timingOk = false; + + this.addSniffer(WebInspector, 'addResource', + function(identifier, payload) { + var resource = this.resources[identifier]; + if (resource.mainResource) { + // We are only interested in secondary resources in this test. + return; + } + + var requestHeaders = JSON.stringify(resource.requestHeaders); + test.assertContains(requestHeaders, 'Accept'); + requestOk = true; + }, true); + + this.addSniffer(WebInspector, 'updateResource', + function(identifier, payload) { + var resource = this.resources[identifier]; + if (resource.mainResource) { + // We are only interested in secondary resources in this test. + return; + } + + if (payload.didResponseChange) { + var responseHeaders = JSON.stringify(resource.responseHeaders); + test.assertContains(responseHeaders, 'Content-type'); + test.assertContains(responseHeaders, 'Content-Length'); + test.assertTrue(typeof resource.responseReceivedTime != 'undefnied'); + responseOk = true; + } + + if (payload.didTimingChange) { + test.assertTrue(typeof resource.startTime != 'undefnied'); + timingOk = true; + } + + if (payload.didCompletionChange) { + test.assertTrue(requestOk); + test.assertTrue(responseOk); + test.assertTrue(timingOk); + test.assertTrue(typeof resource.endTime != 'undefnied'); + test.releaseControl(); + } + }, true); + + WebInspector.panels.resources._enableResourceTracking(); + this.takeControl(); +}; + + +/** + * Test that profiler works. + */ +TestSuite.prototype.testProfilerTab = function() { + this.showPanel('profiles'); + + var test = this; + this.addSniffer(WebInspector, 'addProfile', + function(profile) { + var panel = WebInspector.panels.profiles; + panel.showProfile(profile); + var node = panel.visibleView.profileDataGridTree.children[0]; + // Iterate over displayed functions and search for a function + // that is called 'fib' or 'eternal_fib'. If found, it will mean + // that we actually have profiled page's code. + while (node) { + if (node.functionName.indexOf('fib') != -1) { + test.releaseControl(); + } + node = node.traverseNextNode(true, null, true); + } + + test.fail(); + }); + var ticksCount = 0; + var tickRecord = '\nt,'; + this.addSniffer(RemoteDebuggerAgent, 'DidGetNextLogLines', + function(log) { + var pos = 0; + while ((pos = log.indexOf(tickRecord, pos)) != -1) { + pos += tickRecord.length; + ticksCount++; + } + if (ticksCount > 100) { + InspectorController.stopProfiling(); + } + }, true); + + InspectorController.startProfiling(); + this.takeControl(); +}; + + +/** + * Tests that scripts tab can be open and populated with inspected scripts. + */ +TestSuite.prototype.testShowScriptsTab = function() { + var parsedDebuggerTestPageHtml = false; + + // Intercept parsedScriptSource calls to check that all expected scripts are + // added to the debugger. + var test = this; + var receivedConsoleApiSource = false; + this.addSniffer(WebInspector, 'parsedScriptSource', + function(sourceID, sourceURL, source, startingLine) { + if (sourceURL == undefined) { + if (receivedConsoleApiSource) { + test.fail('Unexpected script without URL'); + } else { + receivedConsoleApiSource = true; + } + } else if (sourceURL.search(/debugger_test_page.html$/) != -1) { + if (parsedDebuggerTestPageHtml) { + test.fail('Unexpected parse event: ' + sourceURL); + } + parsedDebuggerTestPageHtml = true; + } else { + test.fail('Unexpected script URL: ' + sourceURL); + } + + if (!WebInspector.panels.scripts.visibleView) { + test.fail('No visible script view: ' + sourceURL); + } + + // There should be two scripts: one for the main page and another + // one which is source of console API(see + // InjectedScript._ensureCommandLineAPIInstalled). + if (parsedDebuggerTestPageHtml && receivedConsoleApiSource) { + test.releaseControl(); + } + }, true /* sticky */); + + this.showPanel('scripts'); + + // Wait until all scripts are added to the debugger. + this.takeControl(); +}; + + +/** + * Tests that a breakpoint can be set. + */ +TestSuite.prototype.testSetBreakpoint = function() { + var parsedDebuggerTestPageHtml = false; + var parsedDebuggerTestJs = false; + + this.showPanel('scripts'); + + var scriptUrl = null; + var breakpointLine = 12; + + var test = this; + var orig = devtools.DebuggerAgent.prototype.handleScriptsResponse_; + this.addSniffer(devtools.DebuggerAgent.prototype, 'handleScriptsResponse_', + function(msg) { + var scriptSelect = document.getElementById('scripts-files'); + var options = scriptSelect.options; + + // There should be console API source (see + // InjectedScript._ensureCommandLineAPIInstalled) and the page script. + test.assertEquals(2, options.length, 'Unexpected number of scripts.'); + + // Select page's script if it's not current option. + var scriptResource; + if (options[scriptSelect.selectedIndex].text == + 'debugger_test_page.html') { + scriptResource = + options[scriptSelect.selectedIndex].representedObject; + } else { + var pageScriptIndex = (1 - scriptSelect.selectedIndex); + test.assertEquals('debugger_test_page.html', + options[pageScriptIndex].text); + scriptResource = options[pageScriptIndex].representedObject; + // Current panel is 'Scripts'. + WebInspector.currentPanel._showScriptOrResource(scriptResource); + } + + test.assertTrue(scriptResource instanceof WebInspector.Resource, + 'Unexpected resource class.'); + test.assertTrue(!!scriptResource.url, 'Resource URL is null.'); + test.assertTrue( + scriptResource.url.search(/debugger_test_page.html$/) != -1, + 'Main HTML resource should be selected.'); + + // Store for access from setbreakpoint handler. + scriptUrl = scriptResource.url; + + var scriptsPanel = WebInspector.panels.scripts; + + var view = scriptsPanel.visibleView; + test.assertTrue(view instanceof WebInspector.SourceView); + + if (!view.sourceFrame._isContentLoaded()) { + test.addSniffer(view, '_sourceFrameSetupFinished', function(event) { + view._addBreakpoint(breakpointLine); + // Force v8 execution. + RemoteToolsAgent.ExecuteVoidJavaScript(); + }); + } else { + view._addBreakpoint(breakpointLine); + // Force v8 execution. + RemoteToolsAgent.ExecuteVoidJavaScript(); + } + }); + + this.addSniffer( + devtools.DebuggerAgent.prototype, + 'handleSetBreakpointResponse_', + function(msg) { + var bps = this.urlToBreakpoints_[scriptUrl]; + test.assertTrue(!!bps, 'No breakpoints for line ' + breakpointLine); + var line = devtools.DebuggerAgent.webkitToV8LineNumber_(breakpointLine); + test.assertTrue(!!bps[line].getV8Id(), + 'Breakpoint id was not assigned.'); + test.releaseControl(); + }); + + this.takeControl(); +}; + + +/** + * Tests 'Pause' button will pause debugger when a snippet is evaluated. + */ +TestSuite.prototype.testPauseInEval = function() { + this.showPanel('scripts'); + + var test = this; + + var pauseButton = document.getElementById('scripts-pause'); + pauseButton.click(); + + devtools.tools.evaluateJavaScript('fib(10)'); + + this.addSniffer(WebInspector, 'pausedScript', + function() { + test.releaseControl(); + }); + + test.takeControl(); +}; + + +/** + * Key event with given key identifier. + */ +TestSuite.KeyEvent = function(key) { + this.keyIdentifier = key; +}; +TestSuite.KeyEvent.prototype.preventDefault = function() {}; +TestSuite.KeyEvent.prototype.stopPropagation = function() {}; + + +/** + * Tests console eval. + */ +TestSuite.prototype.testConsoleEval = function() { + WebInspector.console.visible = true; + WebInspector.console.prompt.text = '123'; + WebInspector.console.promptElement.handleKeyEvent( + new TestSuite.KeyEvent('Enter')); + + var test = this; + this.addSniffer(WebInspector.ConsoleView.prototype, 'addMessage', + function(commandResult) { + test.assertEquals('123', commandResult.toMessageElement().textContent); + test.releaseControl(); + }); + + this.takeControl(); +}; + + +/** + * Tests console log. + */ +TestSuite.prototype.testConsoleLog = function() { + WebInspector.console.visible = true; + var messages = WebInspector.console.messages; + var index = 0; + + var test = this; + var assertNext = function(line, message, opt_class, opt_count, opt_substr) { + var elem = messages[index++].toMessageElement(); + var clazz = elem.getAttribute('class'); + var expectation = (opt_count || '') + 'console_test_page.html:' + + line + message; + if (opt_substr) { + test.assertContains(elem.textContent, expectation); + } else { + test.assertEquals(expectation, elem.textContent); + } + if (opt_class) { + test.assertContains(clazz, 'console-' + opt_class); + } + }; + + assertNext('5', 'log', 'log-level'); + assertNext('7', 'debug', 'log-level'); + assertNext('9', 'info', 'log-level'); + assertNext('11', 'warn', 'warning-level'); + assertNext('13', 'error', 'error-level'); + assertNext('15', 'Message format number 1, 2 and 3.5'); + assertNext('17', 'Message format for string'); + assertNext('19', 'Object Object'); + assertNext('22', 'repeated', 'log-level', 5); + assertNext('26', 'count: 1'); + assertNext('26', 'count: 2'); + assertNext('29', 'group', 'group-title'); + index++; + assertNext('33', 'timer:', 'log-level', '', true); +}; + + +/** + * Tests eval of global objects. + */ +TestSuite.prototype.testEvalGlobal = function() { + WebInspector.console.visible = true; + WebInspector.console.prompt.text = 'foo'; + WebInspector.console.promptElement.handleKeyEvent( + new TestSuite.KeyEvent('Enter')); + + var test = this; + this.addSniffer(WebInspector.ConsoleView.prototype, 'addMessage', + function(commandResult) { + test.assertEquals('fooValue', + commandResult.toMessageElement().textContent); + test.releaseControl(); + }); + + this.takeControl(); +}; + + +/** + * Tests eval on call frame. + */ +TestSuite.prototype.testEvalCallFrame = function() { +}; + + +/** + * Test runner for the test suite. + */ +var uiTests = {}; + + +/** + * Run each test from the test suit on a fresh instance of the suite. + */ +uiTests.runAllTests = function() { + // For debugging purposes. + for (var name in TestSuite.prototype) { + if (name.substring(0, 4) == 'test' && + typeof TestSuite.prototype[name] == 'function') { + uiTests.runTest(name); + } + } +}; + + +/** + * Run specified test on a fresh instance of the test suite. + * @param {string} name Name of a test method from TestSuite class. + */ +uiTests.runTest = function(name) { + new TestSuite().runTest(name); +}; + + +} diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/treeoutline.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/treeoutline.js new file mode 100644 index 0000000..ecc322b --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/treeoutline.js @@ -0,0 +1,849 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +function TreeOutline(listNode) +{ + this.children = []; + this.selectedTreeElement = null; + this._childrenListNode = listNode; + this._childrenListNode.removeChildren(); + this._knownTreeElements = []; + this._treeElementsExpandedState = []; + this.expandTreeElementsWhenArrowing = false; + this.root = true; + this.hasChildren = false; + this.expanded = true; + this.selected = false; + this.treeOutline = this; +} + +TreeOutline._knownTreeElementNextIdentifier = 1; + +TreeOutline._appendChild = function(child) +{ + if (!child) + throw("child can't be undefined or null"); + + var lastChild = this.children[this.children.length - 1]; + if (lastChild) { + lastChild.nextSibling = child; + child.previousSibling = lastChild; + } else { + child.previousSibling = null; + child.nextSibling = null; + } + + this.children.push(child); + this.hasChildren = true; + child.parent = this; + child.treeOutline = this.treeOutline; + child.treeOutline._rememberTreeElement(child); + + var current = child.children[0]; + while (current) { + current.treeOutline = this.treeOutline; + current.treeOutline._rememberTreeElement(current); + current = current.traverseNextTreeElement(false, child, true); + } + + if (child.hasChildren && child.treeOutline._treeElementsExpandedState[child.identifier] !== undefined) + child.expanded = child.treeOutline._treeElementsExpandedState[child.identifier]; + + if (!this._childrenListNode) { + this._childrenListNode = this.treeOutline._childrenListNode.ownerDocument.createElement("ol"); + this._childrenListNode.parentTreeElement = this; + this._childrenListNode.addStyleClass("children"); + if (this.hidden) + this._childrenListNode.addStyleClass("hidden"); + } + + child._attach(); +} + +TreeOutline._insertChild = function(child, index) +{ + if (!child) + throw("child can't be undefined or null"); + + var previousChild = (index > 0 ? this.children[index - 1] : null); + if (previousChild) { + previousChild.nextSibling = child; + child.previousSibling = previousChild; + } else { + child.previousSibling = null; + } + + var nextChild = this.children[index]; + if (nextChild) { + nextChild.previousSibling = child; + child.nextSibling = nextChild; + } else { + child.nextSibling = null; + } + + this.children.splice(index, 0, child); + this.hasChildren = true; + child.parent = this; + child.treeOutline = this.treeOutline; + child.treeOutline._rememberTreeElement(child); + + var current = child.children[0]; + while (current) { + current.treeOutline = this.treeOutline; + current.treeOutline._rememberTreeElement(current); + current = current.traverseNextTreeElement(false, child, true); + } + + if (child.hasChildren && child.treeOutline._treeElementsExpandedState[child.identifier] !== undefined) + child.expanded = child.treeOutline._treeElementsExpandedState[child.identifier]; + + if (!this._childrenListNode) { + this._childrenListNode = this.treeOutline._childrenListNode.ownerDocument.createElement("ol"); + this._childrenListNode.parentTreeElement = this; + this._childrenListNode.addStyleClass("children"); + if (this.hidden) + this._childrenListNode.addStyleClass("hidden"); + } + + child._attach(); +} + +TreeOutline._removeChildAtIndex = function(childIndex) +{ + if (childIndex < 0 || childIndex >= this.children.length) + throw("childIndex out of range"); + + var child = this.children[childIndex]; + this.children.splice(childIndex, 1); + + child.deselect(); + + if (child.previousSibling) + child.previousSibling.nextSibling = child.nextSibling; + if (child.nextSibling) + child.nextSibling.previousSibling = child.previousSibling; + + if (child.treeOutline) { + child.treeOutline._forgetTreeElement(child); + child.treeOutline._forgetChildrenRecursive(child); + } + + child._detach(); + child.treeOutline = null; + child.parent = null; + child.nextSibling = null; + child.previousSibling = null; +} + +TreeOutline._removeChild = function(child) +{ + if (!child) + throw("child can't be undefined or null"); + + var childIndex = this.children.indexOf(child); + if (childIndex === -1) + throw("child not found in this node's children"); + + TreeOutline._removeChildAtIndex.call(this, childIndex); +} + +TreeOutline._removeChildren = function() +{ + for (var i = 0; i < this.children.length; ++i) { + var child = this.children[i]; + child.deselect(); + + if (child.treeOutline) { + child.treeOutline._forgetTreeElement(child); + child.treeOutline._forgetChildrenRecursive(child); + } + + child._detach(); + child.treeOutline = null; + child.parent = null; + child.nextSibling = null; + child.previousSibling = null; + } + + this.children = []; +} + +TreeOutline._removeChildrenRecursive = function() +{ + var childrenToRemove = this.children; + + var child = this.children[0]; + while (child) { + if (child.children.length) + childrenToRemove = childrenToRemove.concat(child.children); + child = child.traverseNextTreeElement(false, this, true); + } + + for (var i = 0; i < childrenToRemove.length; ++i) { + var child = childrenToRemove[i]; + child.deselect(); + if (child.treeOutline) + child.treeOutline._forgetTreeElement(child); + child._detach(); + child.children = []; + child.treeOutline = null; + child.parent = null; + child.nextSibling = null; + child.previousSibling = null; + } + + this.children = []; +} + +TreeOutline.prototype._rememberTreeElement = function(element) +{ + if (!this._knownTreeElements[element.identifier]) + this._knownTreeElements[element.identifier] = []; + + // check if the element is already known + var elements = this._knownTreeElements[element.identifier]; + if (elements.indexOf(element) !== -1) + return; + + // add the element + elements.push(element); +} + +TreeOutline.prototype._forgetTreeElement = function(element) +{ + if (this._knownTreeElements[element.identifier]) + this._knownTreeElements[element.identifier].remove(element, true); +} + +TreeOutline.prototype._forgetChildrenRecursive = function(parentElement) +{ + var child = parentElement.children[0]; + while (child) { + this._forgetTreeElement(child); + child = child.traverseNextTreeElement(false, this, true); + } +} + +TreeOutline.prototype.getCachedTreeElement = function(representedObject) +{ + if (!representedObject) + return null; + + if ("__treeElementIdentifier" in representedObject) { + // If this representedObject has a tree element identifier, and it is a known TreeElement + // in our tree we can just return that tree element. + var elements = this._knownTreeElements[representedObject.__treeElementIdentifier]; + if (elements) { + for (var i = 0; i < elements.length; ++i) + if (elements[i].representedObject === representedObject) + return elements[i]; + } + } + return null; +} + +TreeOutline.prototype.findTreeElement = function(representedObject, isAncestor, getParent) +{ + if (!representedObject) + return null; + + var cachedElement = this.getCachedTreeElement(representedObject); + if (cachedElement) + return cachedElement; + + // The representedObject isn't know, so we start at the top of the tree and work down to find the first + // tree element that represents representedObject or one of its ancestors. + var item; + var found = false; + for (var i = 0; i < this.children.length; ++i) { + item = this.children[i]; + if (item.representedObject === representedObject || isAncestor(item.representedObject, representedObject)) { + found = true; + break; + } + } + + if (!found) + return null; + + // Make sure the item that we found is connected to the root of the tree. + // Build up a list of representedObject's ancestors that aren't already in our tree. + var ancestors = []; + var currentObject = representedObject; + while (currentObject) { + ancestors.unshift(currentObject); + if (currentObject === item.representedObject) + break; + currentObject = getParent(currentObject); + } + + // For each of those ancestors we populate them to fill in the tree. + for (var i = 0; i < ancestors.length; ++i) { + // Make sure we don't call findTreeElement with the same representedObject + // again, to prevent infinite recursion. + if (ancestors[i] === representedObject) + continue; + // FIXME: we could do something faster than findTreeElement since we will know the next + // ancestor exists in the tree. + item = this.findTreeElement(ancestors[i], isAncestor, getParent); + if (item && item.onpopulate) + item.onpopulate(item); + } + + return this.getCachedTreeElement(representedObject); +} + +TreeOutline.prototype.treeElementFromPoint = function(x, y) +{ + var node = this._childrenListNode.ownerDocument.elementFromPoint(x, y); + var listNode = node.enclosingNodeOrSelfWithNodeNameInArray(["ol", "li"]); + if (listNode) + return listNode.parentTreeElement || listNode.treeElement; + return null; +} + +TreeOutline.prototype.handleKeyEvent = function(event) +{ + if (!this.selectedTreeElement || event.shiftKey || event.metaKey || event.ctrlKey) + return false; + + var handled = false; + var nextSelectedElement; + if (event.keyIdentifier === "Up" && !event.altKey) { + nextSelectedElement = this.selectedTreeElement.traversePreviousTreeElement(true); + while (nextSelectedElement && !nextSelectedElement.selectable) + nextSelectedElement = nextSelectedElement.traversePreviousTreeElement(!this.expandTreeElementsWhenArrowing); + handled = nextSelectedElement ? true : false; + } else if (event.keyIdentifier === "Down" && !event.altKey) { + nextSelectedElement = this.selectedTreeElement.traverseNextTreeElement(true); + while (nextSelectedElement && !nextSelectedElement.selectable) + nextSelectedElement = nextSelectedElement.traverseNextTreeElement(!this.expandTreeElementsWhenArrowing); + handled = nextSelectedElement ? true : false; + } else if (event.keyIdentifier === "Left") { + if (this.selectedTreeElement.expanded) { + if (event.altKey) + this.selectedTreeElement.collapseRecursively(); + else + this.selectedTreeElement.collapse(); + handled = true; + } else if (this.selectedTreeElement.parent && !this.selectedTreeElement.parent.root) { + handled = true; + if (this.selectedTreeElement.parent.selectable) { + nextSelectedElement = this.selectedTreeElement.parent; + handled = nextSelectedElement ? true : false; + } else if (this.selectedTreeElement.parent) + this.selectedTreeElement.parent.collapse(); + } + } else if (event.keyIdentifier === "Right") { + if (!this.selectedTreeElement.revealed()) { + this.selectedTreeElement.reveal(); + handled = true; + } else if (this.selectedTreeElement.hasChildren) { + handled = true; + if (this.selectedTreeElement.expanded) { + nextSelectedElement = this.selectedTreeElement.children[0]; + handled = nextSelectedElement ? true : false; + } else { + if (event.altKey) + this.selectedTreeElement.expandRecursively(); + else + this.selectedTreeElement.expand(); + } + } + } + + if (nextSelectedElement) { + nextSelectedElement.reveal(); + nextSelectedElement.select(); + } + + if (handled) { + event.preventDefault(); + event.stopPropagation(); + } + + return handled; +} + +TreeOutline.prototype.expand = function() +{ + // this is the root, do nothing +} + +TreeOutline.prototype.collapse = function() +{ + // this is the root, do nothing +} + +TreeOutline.prototype.revealed = function() +{ + return true; +} + +TreeOutline.prototype.reveal = function() +{ + // this is the root, do nothing +} + +TreeOutline.prototype.appendChild = TreeOutline._appendChild; +TreeOutline.prototype.insertChild = TreeOutline._insertChild; +TreeOutline.prototype.removeChild = TreeOutline._removeChild; +TreeOutline.prototype.removeChildAtIndex = TreeOutline._removeChildAtIndex; +TreeOutline.prototype.removeChildren = TreeOutline._removeChildren; +TreeOutline.prototype.removeChildrenRecursive = TreeOutline._removeChildrenRecursive; + +function TreeElement(title, representedObject, hasChildren) +{ + this._title = title; + this.representedObject = (representedObject || {}); + + if (this.representedObject.__treeElementIdentifier) + this.identifier = this.representedObject.__treeElementIdentifier; + else { + this.identifier = TreeOutline._knownTreeElementNextIdentifier++; + this.representedObject.__treeElementIdentifier = this.identifier; + } + + this._hidden = false; + this.expanded = false; + this.selected = false; + this.hasChildren = hasChildren; + this.children = []; + this.treeOutline = null; + this.parent = null; + this.previousSibling = null; + this.nextSibling = null; + this._listItemNode = null; +} + +TreeElement.prototype = { + selectable: true, + arrowToggleWidth: 10, + + get listItemElement() { + return this._listItemNode; + }, + + get childrenListElement() { + return this._childrenListNode; + }, + + get title() { + return this._title; + }, + + set title(x) { + this._title = x; + if (this._listItemNode) + this._listItemNode.innerHTML = x; + }, + + get tooltip() { + return this._tooltip; + }, + + set tooltip(x) { + this._tooltip = x; + if (this._listItemNode) + this._listItemNode.title = x ? x : ""; + }, + + get hasChildren() { + return this._hasChildren; + }, + + set hasChildren(x) { + if (this._hasChildren === x) + return; + + this._hasChildren = x; + + if (!this._listItemNode) + return; + + if (x) + this._listItemNode.addStyleClass("parent"); + else { + this._listItemNode.removeStyleClass("parent"); + this.collapse(); + } + }, + + get hidden() { + return this._hidden; + }, + + set hidden(x) { + if (this._hidden === x) + return; + + this._hidden = x; + + if (x) { + if (this._listItemNode) + this._listItemNode.addStyleClass("hidden"); + if (this._childrenListNode) + this._childrenListNode.addStyleClass("hidden"); + } else { + if (this._listItemNode) + this._listItemNode.removeStyleClass("hidden"); + if (this._childrenListNode) + this._childrenListNode.removeStyleClass("hidden"); + } + }, + + get shouldRefreshChildren() { + return this._shouldRefreshChildren; + }, + + set shouldRefreshChildren(x) { + this._shouldRefreshChildren = x; + if (x && this.expanded) + this.expand(); + } +} + +TreeElement.prototype.appendChild = TreeOutline._appendChild; +TreeElement.prototype.insertChild = TreeOutline._insertChild; +TreeElement.prototype.removeChild = TreeOutline._removeChild; +TreeElement.prototype.removeChildAtIndex = TreeOutline._removeChildAtIndex; +TreeElement.prototype.removeChildren = TreeOutline._removeChildren; +TreeElement.prototype.removeChildrenRecursive = TreeOutline._removeChildrenRecursive; + +TreeElement.prototype._attach = function() +{ + if (!this._listItemNode || this.parent._shouldRefreshChildren) { + if (this._listItemNode && this._listItemNode.parentNode) + this._listItemNode.parentNode.removeChild(this._listItemNode); + + this._listItemNode = this.treeOutline._childrenListNode.ownerDocument.createElement("li"); + this._listItemNode.treeElement = this; + this._listItemNode.innerHTML = this._title; + this._listItemNode.title = this._tooltip ? this._tooltip : ""; + + if (this.hidden) + this._listItemNode.addStyleClass("hidden"); + if (this.hasChildren) + this._listItemNode.addStyleClass("parent"); + if (this.expanded) + this._listItemNode.addStyleClass("expanded"); + if (this.selected) + this._listItemNode.addStyleClass("selected"); + + this._listItemNode.addEventListener("mousedown", TreeElement.treeElementSelected, false); + this._listItemNode.addEventListener("click", TreeElement.treeElementToggled, false); + this._listItemNode.addEventListener("dblclick", TreeElement.treeElementDoubleClicked, false); + + if (this.onattach) + this.onattach(this); + } + + var nextSibling = null; + if (this.nextSibling && this.nextSibling._listItemNode && this.nextSibling._listItemNode.parentNode === this.parent._childrenListNode) + nextSibling = this.nextSibling._listItemNode; + this.parent._childrenListNode.insertBefore(this._listItemNode, nextSibling); + if (this._childrenListNode) + this.parent._childrenListNode.insertBefore(this._childrenListNode, this._listItemNode.nextSibling); + if (this.selected) + this.select(); + if (this.expanded) + this.expand(); +} + +TreeElement.prototype._detach = function() +{ + if (this._listItemNode && this._listItemNode.parentNode) + this._listItemNode.parentNode.removeChild(this._listItemNode); + if (this._childrenListNode && this._childrenListNode.parentNode) + this._childrenListNode.parentNode.removeChild(this._childrenListNode); +} + +TreeElement.treeElementSelected = function(event) +{ + var element = event.currentTarget; + if (!element || !element.treeElement || !element.treeElement.selectable) + return; + + if (element.treeElement.isEventWithinDisclosureTriangle(event)) + return; + + element.treeElement.select(); +} + +TreeElement.treeElementToggled = function(event) +{ + var element = event.currentTarget; + if (!element || !element.treeElement) + return; + + if (!element.treeElement.isEventWithinDisclosureTriangle(event)) + return; + + if (element.treeElement.expanded) { + if (event.altKey) + element.treeElement.collapseRecursively(); + else + element.treeElement.collapse(); + } else { + if (event.altKey) + element.treeElement.expandRecursively(); + else + element.treeElement.expand(); + } +} + +TreeElement.treeElementDoubleClicked = function(event) +{ + var element = event.currentTarget; + if (!element || !element.treeElement) + return; + + if (element.treeElement.ondblclick) + element.treeElement.ondblclick(element.treeElement, event); + else if (element.treeElement.hasChildren && !element.treeElement.expanded) + element.treeElement.expand(); +} + +TreeElement.prototype.collapse = function() +{ + if (this._listItemNode) + this._listItemNode.removeStyleClass("expanded"); + if (this._childrenListNode) + this._childrenListNode.removeStyleClass("expanded"); + + this.expanded = false; + if (this.treeOutline) + this.treeOutline._treeElementsExpandedState[this.identifier] = true; + + if (this.oncollapse) + this.oncollapse(this); +} + +TreeElement.prototype.collapseRecursively = function() +{ + var item = this; + while (item) { + if (item.expanded) + item.collapse(); + item = item.traverseNextTreeElement(false, this, true); + } +} + +TreeElement.prototype.expand = function() +{ + if (!this.hasChildren || (this.expanded && !this._shouldRefreshChildren && this._childrenListNode)) + return; + + if (this.treeOutline && (!this._childrenListNode || this._shouldRefreshChildren)) { + if (this._childrenListNode && this._childrenListNode.parentNode) + this._childrenListNode.parentNode.removeChild(this._childrenListNode); + + this._childrenListNode = this.treeOutline._childrenListNode.ownerDocument.createElement("ol"); + this._childrenListNode.parentTreeElement = this; + this._childrenListNode.addStyleClass("children"); + + if (this.hidden) + this._childrenListNode.addStyleClass("hidden"); + + if (this.onpopulate) + this.onpopulate(this); + + for (var i = 0; i < this.children.length; ++i) + this.children[i]._attach(); + + delete this._shouldRefreshChildren; + } + + if (this._listItemNode) { + this._listItemNode.addStyleClass("expanded"); + if (this._childrenListNode && this._childrenListNode.parentNode != this._listItemNode.parentNode) + this.parent._childrenListNode.insertBefore(this._childrenListNode, this._listItemNode.nextSibling); + } + + if (this._childrenListNode) + this._childrenListNode.addStyleClass("expanded"); + + this.expanded = true; + if (this.treeOutline) + this.treeOutline._treeElementsExpandedState[this.identifier] = true; + + if (this.onexpand) + this.onexpand(this); +} + +TreeElement.prototype.expandRecursively = function(maxDepth) +{ + var item = this; + var info = {}; + var depth = 0; + + // The Inspector uses TreeOutlines to represents object properties, so recursive expansion + // in some case can be infinite, since JavaScript objects can hold circular references. + // So default to a recursion cap of 3 levels, since that gives fairly good results. + if (typeof maxDepth === "undefined" || typeof maxDepth === "null") + maxDepth = 3; + + while (item) { + if (depth < maxDepth) + item.expand(); + item = item.traverseNextTreeElement(false, this, (depth >= maxDepth), info); + depth += info.depthChange; + } +} + +TreeElement.prototype.hasAncestor = function(ancestor) { + if (!ancestor) + return false; + + var currentNode = this.parent; + while (currentNode) { + if (ancestor === currentNode) + return true; + currentNode = currentNode.parent; + } + + return false; +} + +TreeElement.prototype.reveal = function() +{ + var currentAncestor = this.parent; + while (currentAncestor && !currentAncestor.root) { + if (!currentAncestor.expanded) + currentAncestor.expand(); + currentAncestor = currentAncestor.parent; + } + + if (this.onreveal) + this.onreveal(this); +} + +TreeElement.prototype.revealed = function() +{ + var currentAncestor = this.parent; + while (currentAncestor && !currentAncestor.root) { + if (!currentAncestor.expanded) + return false; + currentAncestor = currentAncestor.parent; + } + + return true; +} + +TreeElement.prototype.select = function(supressOnSelect) +{ + if (!this.treeOutline || !this.selectable || this.selected) + return; + + if (this.treeOutline.selectedTreeElement) + this.treeOutline.selectedTreeElement.deselect(); + + this.selected = true; + this.treeOutline.selectedTreeElement = this; + if (this._listItemNode) + this._listItemNode.addStyleClass("selected"); + + if (this.onselect && !supressOnSelect) + this.onselect(this); +} + +TreeElement.prototype.deselect = function(supressOnDeselect) +{ + if (!this.treeOutline || this.treeOutline.selectedTreeElement !== this || !this.selected) + return; + + this.selected = false; + this.treeOutline.selectedTreeElement = null; + if (this._listItemNode) + this._listItemNode.removeStyleClass("selected"); + + if (this.ondeselect && !supressOnDeselect) + this.ondeselect(this); +} + +TreeElement.prototype.traverseNextTreeElement = function(skipHidden, stayWithin, dontPopulate, info) +{ + if (!dontPopulate && this.hasChildren && this.onpopulate) + this.onpopulate(this); + + if (info) + info.depthChange = 0; + + var element = skipHidden ? (this.revealed() ? this.children[0] : null) : this.children[0]; + if (element && (!skipHidden || (skipHidden && this.expanded))) { + if (info) + info.depthChange = 1; + return element; + } + + if (this === stayWithin) + return null; + + element = skipHidden ? (this.revealed() ? this.nextSibling : null) : this.nextSibling; + if (element) + return element; + + element = this; + while (element && !element.root && !(skipHidden ? (element.revealed() ? element.nextSibling : null) : element.nextSibling) && element.parent !== stayWithin) { + if (info) + info.depthChange -= 1; + element = element.parent; + } + + if (!element) + return null; + + return (skipHidden ? (element.revealed() ? element.nextSibling : null) : element.nextSibling); +} + +TreeElement.prototype.traversePreviousTreeElement = function(skipHidden, dontPopulate) +{ + var element = skipHidden ? (this.revealed() ? this.previousSibling : null) : this.previousSibling; + if (!dontPopulate && element && element.hasChildren && element.onpopulate) + element.onpopulate(element); + + while (element && (skipHidden ? (element.revealed() && element.expanded ? element.children[element.children.length - 1] : null) : element.children[element.children.length - 1])) { + if (!dontPopulate && element.hasChildren && element.onpopulate) + element.onpopulate(element); + element = (skipHidden ? (element.revealed() && element.expanded ? element.children[element.children.length - 1] : null) : element.children[element.children.length - 1]); + } + + if (element) + return element; + + if (!this.parent || this.parent.root) + return null; + + return this.parent; +} + +TreeElement.prototype.isEventWithinDisclosureTriangle = function(event) +{ + var left = this._listItemNode.totalOffsetLeft; + return event.pageX >= left && event.pageX <= left + this.arrowToggleWidth && this.hasChildren; +} diff --git a/chrome_frame/tools/test/reference_build/chrome/resources/inspector/utilities.js b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/utilities.js new file mode 100644 index 0000000..e831abd --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/resources/inspector/utilities.js @@ -0,0 +1,905 @@ +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +Object.proxyType = function(objectProxy) +{ + if (objectProxy === null) + return "null"; + + var type = typeof objectProxy; + if (type !== "object" && type !== "function") + return type; + + return objectProxy.type; +} + +Object.properties = function(obj) +{ + var properties = []; + for (var prop in obj) + properties.push(prop); + return properties; +} + +Object.sortedProperties = function(obj, sortFunc) +{ + return Object.properties(obj).sort(sortFunc); +} + +Function.prototype.bind = function(thisObject) +{ + var func = this; + var args = Array.prototype.slice.call(arguments, 1); + return function() { return func.apply(thisObject, args.concat(Array.prototype.slice.call(arguments, 0))) }; +} + +Node.prototype.rangeOfWord = function(offset, stopCharacters, stayWithinNode, direction) +{ + var startNode; + var startOffset = 0; + var endNode; + var endOffset = 0; + + if (!stayWithinNode) + stayWithinNode = this; + + if (!direction || direction === "backward" || direction === "both") { + var node = this; + while (node) { + if (node === stayWithinNode) { + if (!startNode) + startNode = stayWithinNode; + break; + } + + if (node.nodeType === Node.TEXT_NODE) { + var start = (node === this ? (offset - 1) : (node.nodeValue.length - 1)); + for (var i = start; i >= 0; --i) { + if (stopCharacters.indexOf(node.nodeValue[i]) !== -1) { + startNode = node; + startOffset = i + 1; + break; + } + } + } + + if (startNode) + break; + + node = node.traversePreviousNode(false, stayWithinNode); + } + + if (!startNode) { + startNode = stayWithinNode; + startOffset = 0; + } + } else { + startNode = this; + startOffset = offset; + } + + if (!direction || direction === "forward" || direction === "both") { + node = this; + while (node) { + if (node === stayWithinNode) { + if (!endNode) + endNode = stayWithinNode; + break; + } + + if (node.nodeType === Node.TEXT_NODE) { + var start = (node === this ? offset : 0); + for (var i = start; i < node.nodeValue.length; ++i) { + if (stopCharacters.indexOf(node.nodeValue[i]) !== -1) { + endNode = node; + endOffset = i; + break; + } + } + } + + if (endNode) + break; + + node = node.traverseNextNode(false, stayWithinNode); + } + + if (!endNode) { + endNode = stayWithinNode; + endOffset = stayWithinNode.nodeType === Node.TEXT_NODE ? stayWithinNode.nodeValue.length : stayWithinNode.childNodes.length; + } + } else { + endNode = this; + endOffset = offset; + } + + var result = this.ownerDocument.createRange(); + result.setStart(startNode, startOffset); + result.setEnd(endNode, endOffset); + + return result; +} + +Element.prototype.removeStyleClass = function(className) +{ + // Test for the simple case before using a RegExp. + if (this.className === className) { + this.className = ""; + return; + } + + this.removeMatchingStyleClasses(className.escapeForRegExp()); +} + +Element.prototype.removeMatchingStyleClasses = function(classNameRegex) +{ + var regex = new RegExp("(^|\\s+)" + classNameRegex + "($|\\s+)"); + if (regex.test(this.className)) + this.className = this.className.replace(regex, " "); +} + +Element.prototype.addStyleClass = function(className) +{ + if (className && !this.hasStyleClass(className)) + this.className += (this.className.length ? " " + className : className); +} + +Element.prototype.hasStyleClass = function(className) +{ + if (!className) + return false; + // Test for the simple case before using a RegExp. + if (this.className === className) + return true; + var regex = new RegExp("(^|\\s)" + className.escapeForRegExp() + "($|\\s)"); + return regex.test(this.className); +} + +Element.prototype.positionAt = function(x, y) +{ + this.style.left = x + "px"; + this.style.top = y + "px"; +} + +Node.prototype.enclosingNodeOrSelfWithNodeNameInArray = function(nameArray) +{ + for (var node = this; node && node !== this.ownerDocument; node = node.parentNode) + for (var i = 0; i < nameArray.length; ++i) + if (node.nodeName.toLowerCase() === nameArray[i].toLowerCase()) + return node; + return null; +} + +Node.prototype.enclosingNodeOrSelfWithNodeName = function(nodeName) +{ + return this.enclosingNodeOrSelfWithNodeNameInArray([nodeName]); +} + +Node.prototype.enclosingNodeOrSelfWithClass = function(className) +{ + for (var node = this; node && node !== this.ownerDocument; node = node.parentNode) + if (node.nodeType === Node.ELEMENT_NODE && node.hasStyleClass(className)) + return node; + return null; +} + +Node.prototype.enclosingNodeWithClass = function(className) +{ + if (!this.parentNode) + return null; + return this.parentNode.enclosingNodeOrSelfWithClass(className); +} + +Element.prototype.query = function(query) +{ + return this.ownerDocument.evaluate(query, this, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; +} + +Element.prototype.removeChildren = function() +{ + while (this.firstChild) + this.removeChild(this.firstChild); +} + +Element.prototype.isInsertionCaretInside = function() +{ + var selection = window.getSelection(); + if (!selection.rangeCount || !selection.isCollapsed) + return false; + var selectionRange = selection.getRangeAt(0); + return selectionRange.startContainer === this || selectionRange.startContainer.isDescendant(this); +} + +Element.prototype.__defineGetter__("totalOffsetLeft", function() +{ + var total = 0; + for (var element = this; element; element = element.offsetParent) + total += element.offsetLeft; + return total; +}); + +Element.prototype.__defineGetter__("totalOffsetTop", function() +{ + var total = 0; + for (var element = this; element; element = element.offsetParent) + total += element.offsetTop; + return total; +}); + +Element.prototype.offsetRelativeToWindow = function(targetWindow) +{ + var elementOffset = {x: 0, y: 0}; + var curElement = this; + var curWindow = this.ownerDocument.defaultView; + while (curWindow && curElement) { + elementOffset.x += curElement.totalOffsetLeft; + elementOffset.y += curElement.totalOffsetTop; + if (curWindow === targetWindow) + break; + + curElement = curWindow.frameElement; + curWindow = curWindow.parent; + } + + return elementOffset; +} + +Element.prototype.firstChildSkippingWhitespace = firstChildSkippingWhitespace; +Element.prototype.lastChildSkippingWhitespace = lastChildSkippingWhitespace; + +Node.prototype.isWhitespace = isNodeWhitespace; +Node.prototype.displayName = nodeDisplayName; +Node.prototype.isAncestor = function(node) +{ + return isAncestorNode(this, node); +}; +Node.prototype.isDescendant = isDescendantNode; +Node.prototype.nextSiblingSkippingWhitespace = nextSiblingSkippingWhitespace; +Node.prototype.previousSiblingSkippingWhitespace = previousSiblingSkippingWhitespace; +Node.prototype.traverseNextNode = traverseNextNode; +Node.prototype.traversePreviousNode = traversePreviousNode; +Node.prototype.onlyTextChild = onlyTextChild; + +String.prototype.hasSubstring = function(string, caseInsensitive) +{ + if (!caseInsensitive) + return this.indexOf(string) !== -1; + return this.match(new RegExp(string.escapeForRegExp(), "i")); +} + +String.prototype.escapeCharacters = function(chars) +{ + var foundChar = false; + for (var i = 0; i < chars.length; ++i) { + if (this.indexOf(chars.charAt(i)) !== -1) { + foundChar = true; + break; + } + } + + if (!foundChar) + return this; + + var result = ""; + for (var i = 0; i < this.length; ++i) { + if (chars.indexOf(this.charAt(i)) !== -1) + result += "\\"; + result += this.charAt(i); + } + + return result; +} + +String.prototype.escapeForRegExp = function() +{ + return this.escapeCharacters("^[]{}()\\.$*+?|"); +} + +String.prototype.escapeHTML = function() +{ + return this.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"); +} + +String.prototype.collapseWhitespace = function() +{ + return this.replace(/[\s\xA0]+/g, " "); +} + +String.prototype.trimLeadingWhitespace = function() +{ + return this.replace(/^[\s\xA0]+/g, ""); +} + +String.prototype.trimTrailingWhitespace = function() +{ + return this.replace(/[\s\xA0]+$/g, ""); +} + +String.prototype.trimWhitespace = function() +{ + return this.replace(/^[\s\xA0]+|[\s\xA0]+$/g, ""); +} + +String.prototype.trimURL = function(baseURLDomain) +{ + var result = this.replace(new RegExp("^http[s]?:\/\/", "i"), ""); + if (baseURLDomain) + result = result.replace(new RegExp("^" + baseURLDomain.escapeForRegExp(), "i"), ""); + return result; +} + +function isNodeWhitespace() +{ + if (!this || this.nodeType !== Node.TEXT_NODE) + return false; + if (!this.nodeValue.length) + return true; + return this.nodeValue.match(/^[\s\xA0]+$/); +} + +function nodeDisplayName() +{ + if (!this) + return ""; + + switch (this.nodeType) { + case Node.DOCUMENT_NODE: + return "Document"; + + case Node.ELEMENT_NODE: + var name = "<" + this.nodeName.toLowerCase(); + + if (this.hasAttributes()) { + var value = this.getAttribute("id"); + if (value) + name += " id=\"" + value + "\""; + value = this.getAttribute("class"); + if (value) + name += " class=\"" + value + "\""; + if (this.nodeName.toLowerCase() === "a") { + value = this.getAttribute("name"); + if (value) + name += " name=\"" + value + "\""; + value = this.getAttribute("href"); + if (value) + name += " href=\"" + value + "\""; + } else if (this.nodeName.toLowerCase() === "img") { + value = this.getAttribute("src"); + if (value) + name += " src=\"" + value + "\""; + } else if (this.nodeName.toLowerCase() === "iframe") { + value = this.getAttribute("src"); + if (value) + name += " src=\"" + value + "\""; + } else if (this.nodeName.toLowerCase() === "input") { + value = this.getAttribute("name"); + if (value) + name += " name=\"" + value + "\""; + value = this.getAttribute("type"); + if (value) + name += " type=\"" + value + "\""; + } else if (this.nodeName.toLowerCase() === "form") { + value = this.getAttribute("action"); + if (value) + name += " action=\"" + value + "\""; + } + } + + return name + ">"; + + case Node.TEXT_NODE: + if (isNodeWhitespace.call(this)) + return "(whitespace)"; + return "\"" + this.nodeValue + "\""; + + case Node.COMMENT_NODE: + return "<!--" + this.nodeValue + "-->"; + + case Node.DOCUMENT_TYPE_NODE: + var docType = "<!DOCTYPE " + this.nodeName; + if (this.publicId) { + docType += " PUBLIC \"" + this.publicId + "\""; + if (this.systemId) + docType += " \"" + this.systemId + "\""; + } else if (this.systemId) + docType += " SYSTEM \"" + this.systemId + "\""; + if (this.internalSubset) + docType += " [" + this.internalSubset + "]"; + return docType + ">"; + } + + return this.nodeName.toLowerCase().collapseWhitespace(); +} + +function isAncestorNode(ancestor, node) +{ + if (!node || !ancestor) + return false; + + var currentNode = node.parentNode; + while (currentNode) { + if (ancestor === currentNode) + return true; + currentNode = currentNode.parentNode; + } + return false; +} + +function isDescendantNode(descendant) +{ + return isAncestorNode(descendant, this); +} + +function nextSiblingSkippingWhitespace() +{ + if (!this) + return; + var node = this.nextSibling; + while (node && node.nodeType === Node.TEXT_NODE && isNodeWhitespace.call(node)) + node = node.nextSibling; + return node; +} + +function previousSiblingSkippingWhitespace() +{ + if (!this) + return; + var node = this.previousSibling; + while (node && node.nodeType === Node.TEXT_NODE && isNodeWhitespace.call(node)) + node = node.previousSibling; + return node; +} + +function firstChildSkippingWhitespace() +{ + if (!this) + return; + var node = this.firstChild; + while (node && node.nodeType === Node.TEXT_NODE && isNodeWhitespace.call(node)) + node = nextSiblingSkippingWhitespace.call(node); + return node; +} + +function lastChildSkippingWhitespace() +{ + if (!this) + return; + var node = this.lastChild; + while (node && node.nodeType === Node.TEXT_NODE && isNodeWhitespace.call(node)) + node = previousSiblingSkippingWhitespace.call(node); + return node; +} + +function traverseNextNode(skipWhitespace, stayWithin) +{ + if (!this) + return; + + var node = skipWhitespace ? firstChildSkippingWhitespace.call(this) : this.firstChild; + if (node) + return node; + + if (stayWithin && this === stayWithin) + return null; + + node = skipWhitespace ? nextSiblingSkippingWhitespace.call(this) : this.nextSibling; + if (node) + return node; + + node = this; + while (node && !(skipWhitespace ? nextSiblingSkippingWhitespace.call(node) : node.nextSibling) && (!stayWithin || !node.parentNode || node.parentNode !== stayWithin)) + node = node.parentNode; + if (!node) + return null; + + return skipWhitespace ? nextSiblingSkippingWhitespace.call(node) : node.nextSibling; +} + +function traversePreviousNode(skipWhitespace, stayWithin) +{ + if (!this) + return; + if (stayWithin && this === stayWithin) + return null; + var node = skipWhitespace ? previousSiblingSkippingWhitespace.call(this) : this.previousSibling; + while (node && (skipWhitespace ? lastChildSkippingWhitespace.call(node) : node.lastChild) ) + node = skipWhitespace ? lastChildSkippingWhitespace.call(node) : node.lastChild; + if (node) + return node; + return this.parentNode; +} + +function onlyTextChild(ignoreWhitespace) +{ + if (!this) + return null; + + var firstChild = ignoreWhitespace ? firstChildSkippingWhitespace.call(this) : this.firstChild; + if (!firstChild || firstChild.nodeType !== Node.TEXT_NODE) + return null; + + var sibling = ignoreWhitespace ? nextSiblingSkippingWhitespace.call(firstChild) : firstChild.nextSibling; + return sibling ? null : firstChild; +} + +function nodeTitleInfo(hasChildren, linkify) +{ + var info = {title: "", hasChildren: hasChildren}; + + switch (this.nodeType) { + case Node.DOCUMENT_NODE: + info.title = "Document"; + break; + + case Node.ELEMENT_NODE: + info.title = "<span class=\"webkit-html-tag\"><" + this.nodeName.toLowerCase().escapeHTML(); + + if (this.hasAttributes()) { + for (var i = 0; i < this.attributes.length; ++i) { + var attr = this.attributes[i]; + info.title += " <span class=\"webkit-html-attribute\"><span class=\"webkit-html-attribute-name\">" + attr.name.escapeHTML() + "</span>=​\""; + + var value = attr.value; + if (linkify && (attr.name === "src" || attr.name === "href")) { + var value = value.replace(/([\/;:\)\]\}])/g, "$1\u200B"); + info.title += linkify(attr.value, value, "webkit-html-attribute-value", this.nodeName.toLowerCase() == "a"); + } else { + var value = value.escapeHTML(); + value = value.replace(/([\/;:\)\]\}])/g, "$1​"); + info.title += "<span class=\"webkit-html-attribute-value\">" + value + "</span>"; + } + info.title += "\"</span>"; + } + } + info.title += "></span>​"; + + // If this element only has a single child that is a text node, + // just show that text and the closing tag inline rather than + // create a subtree for them + + var textChild = onlyTextChild.call(this, Preferences.ignoreWhitespace); + var showInlineText = textChild && textChild.textContent.length < Preferences.maxInlineTextChildLength; + + if (showInlineText) { + info.title += "<span class=\"webkit-html-text-node\">" + textChild.nodeValue.escapeHTML() + "</span>​<span class=\"webkit-html-tag\"></" + this.nodeName.toLowerCase().escapeHTML() + "></span>"; + info.hasChildren = false; + } + break; + + case Node.TEXT_NODE: + if (isNodeWhitespace.call(this)) + info.title = "(whitespace)"; + else + info.title = "\"<span class=\"webkit-html-text-node\">" + this.nodeValue.escapeHTML() + "</span>\""; + break + + case Node.COMMENT_NODE: + info.title = "<span class=\"webkit-html-comment\"><!--" + this.nodeValue.escapeHTML() + "--></span>"; + break; + + case Node.DOCUMENT_TYPE_NODE: + info.title = "<span class=\"webkit-html-doctype\"><!DOCTYPE " + this.nodeName; + if (this.publicId) { + info.title += " PUBLIC \"" + this.publicId + "\""; + if (this.systemId) + info.title += " \"" + this.systemId + "\""; + } else if (this.systemId) + info.title += " SYSTEM \"" + this.systemId + "\""; + if (this.internalSubset) + info.title += " [" + this.internalSubset + "]"; + info.title += "></span>"; + break; + default: + info.title = this.nodeName.toLowerCase().collapseWhitespace().escapeHTML(); + } + + return info; +} + +function getDocumentForNode(node) { + return node.nodeType == Node.DOCUMENT_NODE ? node : node.ownerDocument; +} + +function parentNode(node) { + return node.parentNode; +} + +Number.secondsToString = function(seconds, formatterFunction, higherResolution) +{ + if (!formatterFunction) + formatterFunction = String.sprintf; + + var ms = seconds * 1000; + if (higherResolution && ms < 1000) + return formatterFunction("%.3fms", ms); + else if (ms < 1000) + return formatterFunction("%.0fms", ms); + + if (seconds < 60) + return formatterFunction("%.2fs", seconds); + + var minutes = seconds / 60; + if (minutes < 60) + return formatterFunction("%.1fmin", minutes); + + var hours = minutes / 60; + if (hours < 24) + return formatterFunction("%.1fhrs", hours); + + var days = hours / 24; + return formatterFunction("%.1f days", days); +} + +Number.bytesToString = function(bytes, formatterFunction, higherResolution) +{ + if (!formatterFunction) + formatterFunction = String.sprintf; + if (typeof higherResolution === "undefined") + higherResolution = true; + + if (bytes < 1024) + return formatterFunction("%.0fB", bytes); + + var kilobytes = bytes / 1024; + if (higherResolution && kilobytes < 1024) + return formatterFunction("%.2fKB", kilobytes); + else if (kilobytes < 1024) + return formatterFunction("%.0fKB", kilobytes); + + var megabytes = kilobytes / 1024; + if (higherResolution) + return formatterFunction("%.3fMB", megabytes); + else + return formatterFunction("%.0fMB", megabytes); +} + +Number.constrain = function(num, min, max) +{ + if (num < min) + num = min; + else if (num > max) + num = max; + return num; +} + +HTMLTextAreaElement.prototype.moveCursorToEnd = function() +{ + var length = this.value.length; + this.setSelectionRange(length, length); +} + +Array.prototype.remove = function(value, onlyFirst) +{ + if (onlyFirst) { + var index = this.indexOf(value); + if (index !== -1) + this.splice(index, 1); + return; + } + + var length = this.length; + for (var i = 0; i < length; ++i) { + if (this[i] === value) + this.splice(i, 1); + } +} + +function insertionIndexForObjectInListSortedByFunction(anObject, aList, aFunction) +{ + // indexOf returns (-lowerBound - 1). Taking (-result - 1) works out to lowerBound. + return (-indexOfObjectInListSortedByFunction(anObject, aList, aFunction) - 1); +} + +function indexOfObjectInListSortedByFunction(anObject, aList, aFunction) +{ + var first = 0; + var last = aList.length - 1; + var floor = Math.floor; + var mid, c; + + while (first <= last) { + mid = floor((first + last) / 2); + c = aFunction(anObject, aList[mid]); + + if (c > 0) + first = mid + 1; + else if (c < 0) + last = mid - 1; + else { + // Return the first occurance of an item in the list. + while (mid > 0 && aFunction(anObject, aList[mid - 1]) === 0) + mid--; + first = mid; + break; + } + } + + // By returning 1 less than the negative lower search bound, we can reuse this function + // for both indexOf and insertionIndexFor, with some simple arithmetic. + return (-first - 1); +} + +String.sprintf = function(format) +{ + return String.vsprintf(format, Array.prototype.slice.call(arguments, 1)); +} + +String.tokenizeFormatString = function(format) +{ + var tokens = []; + var substitutionIndex = 0; + + function addStringToken(str) + { + tokens.push({ type: "string", value: str }); + } + + function addSpecifierToken(specifier, precision, substitutionIndex) + { + tokens.push({ type: "specifier", specifier: specifier, precision: precision, substitutionIndex: substitutionIndex }); + } + + var index = 0; + for (var precentIndex = format.indexOf("%", index); precentIndex !== -1; precentIndex = format.indexOf("%", index)) { + addStringToken(format.substring(index, precentIndex)); + index = precentIndex + 1; + + if (format[index] === "%") { + addStringToken("%"); + ++index; + continue; + } + + if (!isNaN(format[index])) { + // The first character is a number, it might be a substitution index. + var number = parseInt(format.substring(index)); + while (!isNaN(format[index])) + ++index; + // If the number is greater than zero and ends with a "$", + // then this is a substitution index. + if (number > 0 && format[index] === "$") { + substitutionIndex = (number - 1); + ++index; + } + } + + var precision = -1; + if (format[index] === ".") { + // This is a precision specifier. If no digit follows the ".", + // then the precision should be zero. + ++index; + precision = parseInt(format.substring(index)); + if (isNaN(precision)) + precision = 0; + while (!isNaN(format[index])) + ++index; + } + + addSpecifierToken(format[index], precision, substitutionIndex); + + ++substitutionIndex; + ++index; + } + + addStringToken(format.substring(index)); + + return tokens; +} + +String.standardFormatters = { + d: function(substitution) + { + substitution = parseInt(substitution); + return !isNaN(substitution) ? substitution : 0; + }, + + f: function(substitution, token) + { + substitution = parseFloat(substitution); + if (substitution && token.precision > -1) + substitution = substitution.toFixed(token.precision); + return !isNaN(substitution) ? substitution : (token.precision > -1 ? Number(0).toFixed(token.precision) : 0); + }, + + s: function(substitution) + { + return substitution; + }, +}; + +String.vsprintf = function(format, substitutions) +{ + return String.format(format, substitutions, String.standardFormatters, "", function(a, b) { return a + b; }).formattedResult; +} + +String.format = function(format, substitutions, formatters, initialValue, append) +{ + if (!format || !substitutions || !substitutions.length) + return { formattedResult: append(initialValue, format), unusedSubstitutions: substitutions }; + + function prettyFunctionName() + { + return "String.format(\"" + format + "\", \"" + substitutions.join("\", \"") + "\")"; + } + + function warn(msg) + { + console.warn(prettyFunctionName() + ": " + msg); + } + + function error(msg) + { + console.error(prettyFunctionName() + ": " + msg); + } + + var result = initialValue; + var tokens = String.tokenizeFormatString(format); + var usedSubstitutionIndexes = {}; + + for (var i = 0; i < tokens.length; ++i) { + var token = tokens[i]; + + if (token.type === "string") { + result = append(result, token.value); + continue; + } + + if (token.type !== "specifier") { + error("Unknown token type \"" + token.type + "\" found."); + continue; + } + + if (token.substitutionIndex >= substitutions.length) { + // If there are not enough substitutions for the current substitutionIndex + // just output the format specifier literally and move on. + error("not enough substitution arguments. Had " + substitutions.length + " but needed " + (token.substitutionIndex + 1) + ", so substitution was skipped."); + result = append(result, "%" + (token.precision > -1 ? token.precision : "") + token.specifier); + continue; + } + + usedSubstitutionIndexes[token.substitutionIndex] = true; + + if (!(token.specifier in formatters)) { + // Encountered an unsupported format character, treat as a string. + warn("unsupported format character \u201C" + token.specifier + "\u201D. Treating as a string."); + result = append(result, substitutions[token.substitutionIndex]); + continue; + } + + result = append(result, formatters[token.specifier](substitutions[token.substitutionIndex], token)); + } + + var unusedSubstitutions = []; + for (var i = 0; i < substitutions.length; ++i) { + if (i in usedSubstitutionIndexes) + continue; + unusedSubstitutions.push(substitutions[i]); + } + + return { formattedResult: result, unusedSubstitutions: unusedSubstitutions }; +} diff --git a/chrome_frame/tools/test/reference_build/chrome/servers/chrome_launcher.exe b/chrome_frame/tools/test/reference_build/chrome/servers/chrome_launcher.exe Binary files differnew file mode 100644 index 0000000..a979e1b --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/servers/chrome_launcher.exe diff --git a/chrome_frame/tools/test/reference_build/chrome/servers/chrome_launcher.pdb b/chrome_frame/tools/test/reference_build/chrome/servers/chrome_launcher.pdb Binary files differnew file mode 100644 index 0000000..aaa155a --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/servers/chrome_launcher.pdb diff --git a/chrome_frame/tools/test/reference_build/chrome/servers/npchrome_tab.dll b/chrome_frame/tools/test/reference_build/chrome/servers/npchrome_tab.dll Binary files differnew file mode 100644 index 0000000..f7eca43 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/servers/npchrome_tab.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/servers/npchrome_tab.pdb b/chrome_frame/tools/test/reference_build/chrome/servers/npchrome_tab.pdb Binary files differnew file mode 100644 index 0000000..6326dce --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/servers/npchrome_tab.pdb diff --git a/chrome_frame/tools/test/reference_build/chrome/setup.pdb b/chrome_frame/tools/test/reference_build/chrome/setup.pdb Binary files differnew file mode 100644 index 0000000..046e7a1 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/setup.pdb diff --git a/chrome_frame/tools/test/reference_build/chrome/syncapi.dll b/chrome_frame/tools/test/reference_build/chrome/syncapi.dll Binary files differnew file mode 100644 index 0000000..e3f7e31 --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/syncapi.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/themes/default.dll b/chrome_frame/tools/test/reference_build/chrome/themes/default.dll Binary files differnew file mode 100644 index 0000000..0261aac --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/themes/default.dll diff --git a/chrome_frame/tools/test/reference_build/chrome/wow_helper.exe b/chrome_frame/tools/test/reference_build/chrome/wow_helper.exe Binary files differnew file mode 100644 index 0000000..f9bfb4b --- /dev/null +++ b/chrome_frame/tools/test/reference_build/chrome/wow_helper.exe diff --git a/chrome_frame/unittest_precompile.cc b/chrome_frame/unittest_precompile.cc new file mode 100644 index 0000000..9fb41a7 --- /dev/null +++ b/chrome_frame/unittest_precompile.cc @@ -0,0 +1,5 @@ +// Copyright (c) 2009 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. + +#include "chrome_frame/unittest_precompile.h" diff --git a/chrome_frame/unittest_precompile.h b/chrome_frame/unittest_precompile.h new file mode 100644 index 0000000..814f508 --- /dev/null +++ b/chrome_frame/unittest_precompile.h @@ -0,0 +1,18 @@ +// Copyright (c) 2009 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. + +// precompiled.h : include file for standard system include files, +// or project specific include files that are used frequently, +// but are changed infrequently + +#ifndef CHROME_FRAME_UNITTEST_PRECOMPILE_H_ +#define CHROME_FRAME_UNITTEST_PRECOMPILE_H_ + +#include "resource.h" + +#include <atlbase.h> +#include <atlcom.h> +#include <atlctl.h> + +#endif // CHROME_FRAME_UNITTEST_PRECOMPILE_H_ diff --git a/chrome_frame/urlmon_upload_data_stream.cc b/chrome_frame/urlmon_upload_data_stream.cc new file mode 100644 index 0000000..af7a1de --- /dev/null +++ b/chrome_frame/urlmon_upload_data_stream.cc @@ -0,0 +1,99 @@ +// Copyright (c) 2009 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. + +#include "chrome_frame/urlmon_upload_data_stream.h" + +#include "net/base/io_buffer.h" + +void UrlmonUploadDataStream::Initialize(net::UploadData* upload_data) { + upload_data_ = upload_data; + request_body_stream_.reset(new net::UploadDataStream(upload_data)); +} + +STDMETHODIMP UrlmonUploadDataStream::Read(void* pv, ULONG cb, ULONG* read) { + if (pv == NULL) { + NOTREACHED(); + return E_POINTER; + } + + // Have we already read past the end of the stream? + if (request_body_stream_->position() >= request_body_stream_->size()) { + if (read) { + *read = 0; + } + return S_FALSE; + } + + uint64 total_bytes_to_copy = std::min(static_cast<uint64>(cb), + request_body_stream_->size() - request_body_stream_->position()); + uint64 initial_position = request_body_stream_->position(); + + uint64 bytes_copied = 0; + + char* write_pointer = reinterpret_cast<char*>(pv); + while (bytes_copied < total_bytes_to_copy) { + net::IOBuffer* buf = request_body_stream_->buf(); + + // Make sure our length doesn't run past the end of the available data. + size_t bytes_to_copy_now = static_cast<size_t>( + std::min(static_cast<uint64>(request_body_stream_->buf_len()), + total_bytes_to_copy - bytes_copied)); + + memcpy(write_pointer, buf->data(), bytes_to_copy_now); + + // Advance our copy tally + bytes_copied += bytes_to_copy_now; + + // Advance our write pointer + write_pointer += bytes_to_copy_now; + + // Advance the UploadDataStream read pointer: + request_body_stream_->DidConsume(bytes_to_copy_now); + } + + DCHECK(bytes_copied == total_bytes_to_copy); + DCHECK(request_body_stream_->position() == + initial_position + total_bytes_to_copy); + + if (read) { + *read = static_cast<ULONG>(total_bytes_to_copy); + } + + return S_OK; +} + +STDMETHODIMP UrlmonUploadDataStream::Seek(LARGE_INTEGER move, DWORD origin, + ULARGE_INTEGER* new_pos) { + // UploadDataStream is really not very seek-able, so for now allow + // STREAM_SEEK_SETs to work with a 0 offset, but fail on everything else. + if (origin == STREAM_SEEK_SET && move.QuadPart == 0) { + if (request_body_stream_->position() != 0) { + request_body_stream_.reset(new net::UploadDataStream(upload_data_)); + } + if (new_pos) { + new_pos->QuadPart = 0; + } + return S_OK; + } + + DCHECK(false) << __FUNCTION__; + return STG_E_INVALIDFUNCTION; +} + +STDMETHODIMP UrlmonUploadDataStream::Stat(STATSTG *stat_stg, + DWORD grf_stat_flag) { + if (stat_stg == NULL) + return E_POINTER; + + memset(stat_stg, 0, sizeof(STATSTG)); + if (0 == (grf_stat_flag & STATFLAG_NONAME)) { + const wchar_t kStreamBuffer[] = L"PostStream"; + stat_stg->pwcsName = + static_cast<wchar_t*>(::CoTaskMemAlloc(sizeof(kStreamBuffer))); + lstrcpy(stat_stg->pwcsName, kStreamBuffer); + } + stat_stg->type = STGTY_STREAM; + stat_stg->cbSize.QuadPart = upload_data_->GetContentLength(); + return S_OK; +} diff --git a/chrome_frame/urlmon_upload_data_stream.h b/chrome_frame/urlmon_upload_data_stream.h new file mode 100644 index 0000000..eacb433 --- /dev/null +++ b/chrome_frame/urlmon_upload_data_stream.h @@ -0,0 +1,89 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_FRAME_URLMON_UPLOAD_DATA_STREAM_H_ +#define CHROME_FRAME_URLMON_UPLOAD_DATA_STREAM_H_ + +#include <urlmon.h> +#include <atlbase.h> +#include <atlcom.h> + +#include "base/logging.h" +#include "base/ref_counted.h" + +#include "net/base/upload_data.h" +#include "net/base/upload_data_stream.h" + +// Provides an IStream interface to the very different UploadDataStream +// implementation. +class UrlmonUploadDataStream : public CComObjectRoot, + public IStream { + public: + UrlmonUploadDataStream() {} + + BEGIN_COM_MAP(UrlmonUploadDataStream) + COM_INTERFACE_ENTRY(ISequentialStream) + COM_INTERFACE_ENTRY(IStream) + END_COM_MAP() + + void Initialize(net::UploadData* upload_data); + + // Partial implementation of IStream. + STDMETHOD(Read)(void* pv, ULONG cb, ULONG* read); + + // E_NOTIMPL the rest and DCHECK if they get called (could also use + // IStreamImpl but we'd lose the DCHECKS(). + STDMETHOD(Write)(const void * buffer, ULONG size, ULONG* size_written) { + DCHECK(false) << __FUNCTION__; + return E_NOTIMPL; + } + + STDMETHOD(CopyTo)(IStream* stream, ULARGE_INTEGER cb, ULARGE_INTEGER* read, + ULARGE_INTEGER* written) { + DCHECK(false) << __FUNCTION__; + return E_NOTIMPL; + } + + STDMETHOD(Seek)(LARGE_INTEGER move, DWORD origin, ULARGE_INTEGER* new_pos); + + STDMETHOD(SetSize)(ULARGE_INTEGER new_size) { + DCHECK(false) << __FUNCTION__; + return E_NOTIMPL; + } + + STDMETHOD(Commit)(DWORD flags) { + DCHECK(false) << __FUNCTION__; + return E_NOTIMPL; + } + + STDMETHOD(Revert)() { + DCHECK(false) << __FUNCTION__; + return E_NOTIMPL; + } + + STDMETHOD(LockRegion)(ULARGE_INTEGER offset, ULARGE_INTEGER cb, + DWORD type) { + DCHECK(false) << __FUNCTION__; + return E_NOTIMPL; + } + + STDMETHOD(UnlockRegion)(ULARGE_INTEGER offset, ULARGE_INTEGER cb, + DWORD type) { + DCHECK(false) << __FUNCTION__; + return E_NOTIMPL; + } + + STDMETHOD(Stat)(STATSTG *pstatstg, DWORD grfStatFlag); + + STDMETHOD(Clone)(IStream** stream) { + DCHECK(false) << __FUNCTION__; + return E_NOTIMPL; + } + + private: + scoped_refptr<net::UploadData> upload_data_; + scoped_ptr<net::UploadDataStream> request_body_stream_; +}; + +#endif // CHROME_FRAME_URLMON_UPLOAD_DATA_STREAM_H_ diff --git a/chrome_frame/urlmon_upload_data_stream_unittest.cc b/chrome_frame/urlmon_upload_data_stream_unittest.cc new file mode 100644 index 0000000..a34ba2c --- /dev/null +++ b/chrome_frame/urlmon_upload_data_stream_unittest.cc @@ -0,0 +1,162 @@ +// Copyright (c) 2009 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. + +#include "gtest/gtest.h" + +#include "base/ref_counted.h" +#include "base/scoped_comptr_win.h" +#include "chrome_frame/urlmon_upload_data_stream.h" + +TEST(UrlmonUploadDataStreamTest, TestBasicRead) { + char random_string[] = "some random data, no really this totally random"; + int random_string_length = strlen(random_string); + scoped_refptr<net::UploadData> upload_data = new net::UploadData(); + upload_data->AppendBytes(random_string, random_string_length); + + CComObject<UrlmonUploadDataStream>* upload_stream = NULL; + HRESULT hr = + CComObject<UrlmonUploadDataStream>::CreateInstance(&upload_stream); + ASSERT_TRUE(SUCCEEDED(hr)); + + upload_stream->Initialize(upload_data.get()); + ScopedComPtr<IStream> upload_istream(upload_stream); + + char buffer[500]; + memset(buffer, 0, 500); + ULONG bytes_read = 0; + hr = upload_istream->Read(buffer, 500, &bytes_read); + + EXPECT_TRUE(SUCCEEDED(hr)); + EXPECT_EQ(bytes_read, random_string_length); + EXPECT_TRUE(strcmp(buffer, random_string) == 0); + + char buffer2[500]; + memset(buffer2, 0, 500); + ULONG bytes_read2 = 0; + hr = upload_istream->Read(buffer2, 500, &bytes_read2); + + EXPECT_EQ(S_FALSE, hr); + EXPECT_EQ(bytes_read2, 0); + EXPECT_FALSE(strcmp(buffer2, random_string) == 0); +} + +TEST(UrlmonUploadDataStreamTest, TestBigRead) { + const size_t kBigBufferLength = 100000; + char big_buffer[kBigBufferLength]; + memset(big_buffer, 'a', kBigBufferLength); + + scoped_refptr<net::UploadData> upload_data = new net::UploadData(); + upload_data->AppendBytes(big_buffer, kBigBufferLength); + + CComObject<UrlmonUploadDataStream>* upload_stream = NULL; + HRESULT hr = + CComObject<UrlmonUploadDataStream>::CreateInstance(&upload_stream); + ASSERT_TRUE(SUCCEEDED(hr)); + + upload_stream->Initialize(upload_data.get()); + ScopedComPtr<IStream> upload_istream(upload_stream); + + char big_rcv_buffer[kBigBufferLength]; + int write_pos = 0; + ULONG bytes_read = 0; + hr = E_UNEXPECTED; + + while ((hr = upload_istream->Read(&big_rcv_buffer[write_pos], + kBigBufferLength, + &bytes_read)) != S_FALSE) { + EXPECT_TRUE(SUCCEEDED(hr)); + EXPECT_GT(bytes_read, static_cast<ULONG>(0)); + + write_pos += bytes_read; + bytes_read = 0; + } + + EXPECT_EQ(S_FALSE, hr); + EXPECT_TRUE((write_pos + bytes_read) == kBigBufferLength); + EXPECT_EQ(0, memcmp(big_buffer, big_rcv_buffer, kBigBufferLength)); +} + +TEST(UrlmonUploadDataStreamTest, TestStat) { + char random_string[] = "some random data, no really this totally random"; + int random_string_length = strlen(random_string); + scoped_refptr<net::UploadData> upload_data = new net::UploadData(); + upload_data->AppendBytes(random_string, random_string_length); + + CComObject<UrlmonUploadDataStream>* upload_stream = NULL; + HRESULT hr = + CComObject<UrlmonUploadDataStream>::CreateInstance(&upload_stream); + ASSERT_TRUE(SUCCEEDED(hr)); + + upload_stream->Initialize(upload_data.get()); + ScopedComPtr<IStream> upload_istream(upload_stream); + + STATSTG statstg; + hr = upload_stream->Stat(&statstg, STATFLAG_NONAME); + EXPECT_TRUE(SUCCEEDED(hr)); + EXPECT_EQ(static_cast<LONGLONG>(random_string_length), + statstg.cbSize.QuadPart); +} + +TEST(UrlmonUploadDataStreamTest, TestRepeatedRead) { + char random_string[] = "some random data, no really this totally random"; + int random_string_length = strlen(random_string); + scoped_refptr<net::UploadData> upload_data = new net::UploadData(); + upload_data->AppendBytes(random_string, random_string_length); + + CComObject<UrlmonUploadDataStream>* upload_stream = NULL; + HRESULT hr = + CComObject<UrlmonUploadDataStream>::CreateInstance(&upload_stream); + ASSERT_TRUE(SUCCEEDED(hr)); + + upload_stream->Initialize(upload_data.get()); + ScopedComPtr<IStream> upload_istream(upload_stream); + + char buffer[500]; + memset(buffer, 0, 500); + ULONG bytes_read = 0; + hr = upload_istream->Read(buffer, 500, &bytes_read); + + EXPECT_TRUE(SUCCEEDED(hr)); + EXPECT_EQ(bytes_read, random_string_length); + EXPECT_EQ(0, strcmp(buffer, random_string)); + + char buffer2[500]; + memset(buffer2, 0, 500); + ULONG bytes_read2 = 0; + + for (int i = 0; i < 10; i++) { + hr = upload_istream->Read(buffer2, 500, &bytes_read2); + EXPECT_EQ(S_FALSE, hr); + EXPECT_EQ(bytes_read2, 0); + EXPECT_NE(0, strcmp(buffer2, random_string)); + } +} + +TEST(UrlmonUploadDataStreamTest, TestZeroRead) { + char random_string[] = "some random data, no really this totally random"; + int random_string_length = strlen(random_string); + scoped_refptr<net::UploadData> upload_data = new net::UploadData(); + upload_data->AppendBytes(random_string, random_string_length); + + CComObject<UrlmonUploadDataStream>* upload_stream = NULL; + HRESULT hr = + CComObject<UrlmonUploadDataStream>::CreateInstance(&upload_stream); + ASSERT_TRUE(SUCCEEDED(hr)); + + upload_stream->Initialize(upload_data.get()); + ScopedComPtr<IStream> upload_istream(upload_stream); + + char buffer[500]; + memset(buffer, 0, 500); + ULONG bytes_read = 42; + hr = upload_istream->Read(&buffer[0], 0, &bytes_read); + + EXPECT_EQ(S_OK, hr); + EXPECT_EQ(0, bytes_read); + + char buffer2[500]; + memset(&buffer2[0], 0, 500); + EXPECT_EQ(0, memcmp(buffer, buffer2, 500)); +} + diff --git a/chrome_frame/urlmon_url_request.cc b/chrome_frame/urlmon_url_request.cc new file mode 100644 index 0000000..f51da418 --- /dev/null +++ b/chrome_frame/urlmon_url_request.cc @@ -0,0 +1,751 @@ +// Copyright (c) 2009 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. + +#include "chrome_frame/urlmon_url_request.h" + +#include <wininet.h> + +#include "base/scoped_ptr.h" +#include "base/string_util.h" +#include "base/logging.h" +#include "chrome_frame/urlmon_upload_data_stream.h" +#include "net/http/http_util.h" +#include "net/http/http_response_headers.h" + +static const LARGE_INTEGER kZero = {0}; +static const ULARGE_INTEGER kUnsignedZero = {0}; +int UrlmonUrlRequest::instance_count_ = 0; +const char kXFrameOptionsHeader[] = "X-Frame-Options"; + +UrlmonUrlRequest::UrlmonUrlRequest() + : pending_read_size_(0), + status_(URLRequestStatus::FAILED, net::ERR_FAILED), + thread_(PlatformThread::CurrentId()), + is_request_started_(false), + post_data_len_(0), + redirect_status_(0), + parent_window_(NULL) { + DLOG(INFO) << StringPrintf("Created request. Obj: %X", this) + << " Count: " << ++instance_count_; +} + +UrlmonUrlRequest::~UrlmonUrlRequest() { + DLOG(INFO) << StringPrintf("Deleted request. Obj: %X", this) + << " Count: " << --instance_count_; +} + +bool UrlmonUrlRequest::Start() { + DCHECK_EQ(PlatformThread::CurrentId(), thread_); + + status_.set_status(URLRequestStatus::IO_PENDING); + HRESULT hr = StartAsyncDownload(); + if (FAILED(hr)) { + // Do not call EndRequest() here since it will attempt to free references + // that have not been established. + status_.set_os_error(HresultToNetError(hr)); + status_.set_status(URLRequestStatus::FAILED); + DLOG(ERROR) << "StartAsyncDownload failed"; + OnResponseEnd(status_); + return false; + } + + // Take a self reference to maintain COM lifetime. This will be released + // in EndRequest. Set a flag indicating that we have an additional + // reference here + is_request_started_ = true; + AddRef(); + request_handler()->AddRequest(this); + return true; +} + +void UrlmonUrlRequest::Stop() { + DCHECK_EQ(PlatformThread::CurrentId(), thread_); + + if (binding_) { + binding_->Abort(); + } else { + status_.set_status(URLRequestStatus::CANCELED); + status_.set_os_error(net::ERR_FAILED); + EndRequest(); + } +} + +bool UrlmonUrlRequest::Read(int bytes_to_read) { + DCHECK_EQ(PlatformThread::CurrentId(), thread_); + + DLOG(INFO) << StringPrintf("URL: %s Obj: %X", url().c_str(), this); + + // Send cached data if available. + CComObjectStackEx<SendStream> send_stream; + send_stream.Initialize(this); + size_t bytes_copied = 0; + if (cached_data_.is_valid() && cached_data_.Read(&send_stream, bytes_to_read, + &bytes_copied)) { + DLOG(INFO) << StringPrintf("URL: %s Obj: %X - bytes read from cache: %d", + url().c_str(), this, bytes_copied); + return true; + } + + // if the request is finished or there's nothing more to read + // then end the request + if (!status_.is_io_pending() || !binding_) { + DLOG(INFO) << StringPrintf("URL: %s Obj: %X. Response finished. Status: %d", + url().c_str(), this, status_.status()); + EndRequest(); + return true; + } + + pending_read_size_ = bytes_to_read; + DLOG(INFO) << StringPrintf("URL: %s Obj: %X", url().c_str(), this) << + "- Read pending for: " << bytes_to_read; + return true; +} + +STDMETHODIMP UrlmonUrlRequest::OnStartBinding( + DWORD reserved, IBinding *binding) { + binding_ = binding; + return S_OK; +} + +STDMETHODIMP UrlmonUrlRequest::GetPriority(LONG *priority) { + if (!priority) + return E_POINTER; + *priority = THREAD_PRIORITY_NORMAL; + return S_OK; +} + +STDMETHODIMP UrlmonUrlRequest::OnLowResource(DWORD reserved) { + return S_OK; +} + +STDMETHODIMP UrlmonUrlRequest::OnProgress(ULONG progress, ULONG max_progress, + ULONG status_code, LPCWSTR status_text) { + switch (status_code) { + case BINDSTATUS_REDIRECTING: + DCHECK(status_text != NULL); + DLOG(INFO) << "URL: " << url() << " redirected to " + << status_text; + redirect_url_ = status_text; + // Fetch the redirect status as they aren't all equal (307 in particular + // retains the HTTP request verb). + redirect_status_ = GetHttpResponseStatus(); + break; + + default: + DLOG(INFO) << " Obj: " << std::hex << this << " OnProgress(" << url() + << StringPrintf(L") code: %i status: %ls", status_code, status_text); + break; + } + + return S_OK; +} + +STDMETHODIMP UrlmonUrlRequest::OnStopBinding(HRESULT result, LPCWSTR error) { + DCHECK_EQ(PlatformThread::CurrentId(), thread_); + + DLOG(INFO) << StringPrintf("URL: %s Obj: %X", url().c_str(), this) << + " - Request stopped, Result: " << std::hex << result << + " Status: " << status_.status(); + if (FAILED(result)) { + status_.set_status(URLRequestStatus::FAILED); + status_.set_os_error(HresultToNetError(result)); + EndRequest(); + } else { + status_.set_status(URLRequestStatus::SUCCESS); + status_.set_os_error(0); + } + + // Release these variables after reporting EndRequest since we might need to + // access their state. + binding_ = NULL; + bind_context_ = NULL; + + return S_OK; +} + +STDMETHODIMP UrlmonUrlRequest::GetBindInfo(DWORD* bind_flags, + BINDINFO *bind_info) { + DCHECK_EQ(PlatformThread::CurrentId(), thread_); + + if ((bind_info == NULL) || (bind_info->cbSize == 0) || (bind_flags == NULL)) + return E_INVALIDARG; + + *bind_flags = BINDF_ASYNCHRONOUS | BINDF_ASYNCSTORAGE | BINDF_PULLDATA; + if (LowerCaseEqualsASCII(method(), "get")) { + bind_info->dwBindVerb = BINDVERB_GET; + } else if (LowerCaseEqualsASCII(method(), "post")) { + bind_info->dwBindVerb = BINDVERB_POST; + + // Bypass caching proxies on POSTs and avoid writing responses to POST + // requests to the browser's cache. + *bind_flags |= BINDF_GETNEWESTVERSION | BINDF_NOWRITECACHE | + BINDF_PRAGMA_NO_CACHE; + + // Initialize the STGMEDIUM. + memset(&bind_info->stgmedData, 0, sizeof(STGMEDIUM)); + bind_info->grfBindInfoF = 0; + bind_info->szCustomVerb = NULL; + + scoped_refptr<net::UploadData> upload_data(upload_data()); + post_data_len_ = upload_data.get() ? upload_data->GetContentLength() : 0; + if (post_data_len_) { + DLOG(INFO) << " Obj: " << std::hex << this << " POST request with " + << Int64ToString(post_data_len_) << " bytes"; + CComObject<UrlmonUploadDataStream>* upload_stream = NULL; + HRESULT hr = + CComObject<UrlmonUploadDataStream>::CreateInstance(&upload_stream); + if (FAILED(hr)) { + NOTREACHED(); + return hr; + } + upload_stream->Initialize(upload_data.get()); + + // Fill the STGMEDIUM with the data to post + bind_info->stgmedData.tymed = TYMED_ISTREAM; + bind_info->stgmedData.pstm = static_cast<IStream*>(upload_stream); + bind_info->stgmedData.pstm->AddRef(); + } else { + DLOG(INFO) << " Obj: " << std::hex << this + << "POST request with no data!"; + } + } else { + NOTREACHED() << "Unknown HTTP method."; + status_.set_status(URLRequestStatus::FAILED); + status_.set_os_error(net::ERR_INVALID_URL); + EndRequest(); + return E_FAIL; + } + + return S_OK; +} + +STDMETHODIMP UrlmonUrlRequest::OnDataAvailable(DWORD flags, DWORD size, + FORMATETC* formatetc, + STGMEDIUM* storage) { + DCHECK_EQ(PlatformThread::CurrentId(), thread_); + DLOG(INFO) << StringPrintf("URL: %s Obj: %X - Bytes available: %d", + url().c_str(), this, size); + + if (!storage || (storage->tymed != TYMED_ISTREAM)) { + NOTREACHED(); + return E_INVALIDARG; + } + + IStream* read_stream = storage->pstm; + if (!read_stream) { + NOTREACHED(); + return E_UNEXPECTED; + } + + HRESULT hr = S_OK; + if (BSCF_FIRSTDATANOTIFICATION & flags) { + DCHECK(!cached_data_.is_valid()); + cached_data_.Create(); + } + + // Always read data into cache. We have to read all the data here at this + // time or it won't be available later. Since the size of the data could + // be more than pending read size, it's not straightforward (or might even + // be impossible) to implement a true data pull model. + size_t bytes_available = 0; + cached_data_.Append(read_stream, &bytes_available); + DLOG(INFO) << StringPrintf("URL: %s Obj: %X", url().c_str(), this) << + " - Bytes read into cache: " << bytes_available; + + if (pending_read_size_) { + CComObjectStackEx<SendStream> send_stream; + send_stream.Initialize(this); + cached_data_.Read(&send_stream, pending_read_size_, &pending_read_size_); + DLOG(INFO) << StringPrintf("URL: %s Obj: %X", url().c_str(), this) << + " - size read: " << pending_read_size_; + pending_read_size_ = 0; + } else { + DLOG(INFO) << StringPrintf("URL: %s Obj: %X", url().c_str(), this) << + " - waiting for remote read"; + } + + if (BSCF_LASTDATANOTIFICATION & flags) { + status_.set_status(URLRequestStatus::SUCCESS); + DLOG(INFO) << StringPrintf("URL: %s Obj: %X", url().c_str(), this) << + " - end of data."; + } + + return S_OK; +} + +STDMETHODIMP UrlmonUrlRequest::OnObjectAvailable(REFIID iid, IUnknown *object) { + // We are calling BindToStorage on the moniker we should always get called + // back on OnDataAvailable and should never get OnObjectAvailable + NOTREACHED(); + return E_NOTIMPL; +} + +STDMETHODIMP UrlmonUrlRequest::BeginningTransaction(const wchar_t* url, + const wchar_t* current_headers, DWORD reserved, + wchar_t** additional_headers) { + DCHECK_EQ(PlatformThread::CurrentId(), thread_); + if (!additional_headers) { + NOTREACHED(); + return E_POINTER; + } + + DLOG(INFO) << "URL: " << url << " Obj: " << std::hex << this << + " - Request headers: \n" << current_headers; + + HRESULT hr = S_OK; + + std::string new_headers; + if (post_data_len_ > 0) { + // Tack on the Content-Length header since when using an IStream type + // STGMEDIUM, it looks like it doesn't get set for us :( + new_headers = StringPrintf("Content-Length: %s\r\n", + Int64ToString(post_data_len_).c_str()); + } + + if (!extra_headers().empty()) { + // TODO(robertshield): We may need to sanitize headers on POST here. + new_headers += extra_headers(); + } + + if (!referrer().empty()) { + // Referrer is famously misspelled in HTTP: + new_headers += StringPrintf("Referer: %s\r\n", referrer().c_str()); + } + + if (!new_headers.empty()) { + *additional_headers = reinterpret_cast<wchar_t*>( + CoTaskMemAlloc((new_headers.size() + 1) * sizeof(wchar_t))); + + if (*additional_headers == NULL) { + NOTREACHED(); + hr = E_OUTOFMEMORY; + } else { + lstrcpynW(*additional_headers, ASCIIToWide(new_headers).c_str(), + new_headers.size()); + } + } + + return hr; +} + +STDMETHODIMP UrlmonUrlRequest::OnResponse(DWORD dwResponseCode, + const wchar_t* response_headers, const wchar_t* request_headers, + wchar_t** additional_headers) { + DCHECK_EQ(PlatformThread::CurrentId(), thread_); + + std::string raw_headers = WideToUTF8(response_headers); + + // Security check for frame busting headers. We don't honor the headers + // as-such, but instead simply kill requests which we've been asked to + // look for. This puts the onus on the user of the UrlRequest to specify + // whether or not requests should be inspected. For ActiveDocuments, the + // answer is "no", since WebKit's detection/handling is sufficient and since + // ActiveDocuments cannot be hosted as iframes. For NPAPI and ActiveX + // documents, the Initialize() function of the PluginUrlRequest object + // allows them to specify how they'd like requests handled. Both should + // set enable_frame_busting_ to true to avoid CSRF attacks. + // Should WebKit's handling of this ever change, we will need to re-visit + // how and when frames are killed to better mirror a policy which may + // do something other than kill the sub-document outright. + + // NOTE(slightlyoff): We don't use net::HttpResponseHeaders here because + // of lingering ICU/base_noicu issues. + if (frame_busting_enabled_ && + net::HttpUtil::HasHeader(raw_headers, kXFrameOptionsHeader)) { + DLOG(ERROR) << "X-Frame-Options header detected, navigation canceled"; + return E_FAIL; + } + + std::wstring url_for_persistent_cookies = + redirect_url_.empty() ? UTF8ToWide(url()) : redirect_url_; + + std::string persistent_cookies; + + DWORD cookie_size = 0; // NOLINT + InternetGetCookie(url_for_persistent_cookies.c_str(), NULL, NULL, + &cookie_size); + if (cookie_size) { + scoped_ptr<wchar_t> cookies(new wchar_t[cookie_size + 1]); + if (!InternetGetCookie(url_for_persistent_cookies.c_str(), NULL, + cookies.get(), &cookie_size)) { + NOTREACHED() << "InternetGetCookie failed. Error: " << GetLastError(); + } else { + persistent_cookies = WideToUTF8(cookies.get()); + } + } + + OnResponseStarted("", + raw_headers.c_str(), + 0, + base::Time(), + persistent_cookies, + redirect_url_.empty() ? std::string() : + WideToUTF8(redirect_url_), + redirect_status_); + + return S_OK; +} + +STDMETHODIMP UrlmonUrlRequest::GetWindow(const GUID& guid_reason, + HWND* parent_window) { + if (!parent_window) { + return E_INVALIDARG; + } + +#ifndef NDEBUG + wchar_t guid[40] = {0}; + ::StringFromGUID2(guid_reason, guid, arraysize(guid)); + + DLOG(INFO) << " Obj: " << std::hex << this << " GetWindow: " << + (guid_reason == IID_IAuthenticate ? L" - IAuthenticate" : + (guid_reason == IID_IHttpSecurity ? L"IHttpSecurity" : + (guid_reason == IID_IWindowForBindingUI ? L"IWindowForBindingUI" : + guid))); +#endif + + // TODO(iyengar): This hits when running the URL request tests. + DLOG_IF(ERROR, !::IsWindow(parent_window_)) + << "UrlmonUrlRequest::GetWindow - no window!"; + *parent_window = parent_window_; + return S_OK; +} + +STDMETHODIMP UrlmonUrlRequest::Authenticate(HWND* parent_window, + LPWSTR* user_name, + LPWSTR* password) { + if (!parent_window) { + return E_INVALIDARG; + } + + DCHECK(::IsWindow(parent_window_)); + *parent_window = parent_window_; + return S_OK; +} + +STDMETHODIMP UrlmonUrlRequest::OnSecurityProblem(DWORD problem) { + // Urlmon notifies the client of authentication problems, certificate + // errors, etc by querying the object implementing the IBindStatusCallback + // interface for the IHttpSecurity interface. If this interface is not + // implemented then Urlmon checks for the problem codes defined below + // and performs actions as defined below:- + // It invokes the ReportProgress method of the protocol sink with + // these problem codes and eventually invokes the ReportResult method + // on the protocol sink which ends up in a call to the OnStopBinding + // method of the IBindStatusCallBack interface. + + // MSHTML's implementation of the IBindStatusCallback interface does not + // implement the IHttpSecurity interface. However it handles the + // OnStopBinding call with a HRESULT of 0x800c0019 and navigates to + // an interstitial page which presents the user with a choice of whether + // to abort the navigation. + + // In our OnStopBinding implementation we stop the navigation and inform + // Chrome about the result. Ideally Chrome should behave in a manner similar + // to IE, i.e. display the SSL error interstitial page and if the user + // decides to proceed anyway we would turn off SSL warnings for that + // particular navigation and allow IE to download the content. + // We would need to return the certificate information to Chrome for display + // purposes. Currently we only return a dummy certificate to Chrome. + // At this point we decided that it is a lot of work at this point and + // decided to go with the easier option of implementing the IHttpSecurity + // interface and replicating the checks performed by Urlmon. This + // causes Urlmon to display a dialog box on the same lines as IE6. + DLOG(INFO) << __FUNCTION__ << " Security problem : " << problem; + + HRESULT hr = E_ABORT; + + switch (problem) { + case ERROR_INTERNET_SEC_CERT_REV_FAILED: { + hr = RPC_E_RETRY; + break; + } + + case ERROR_INTERNET_SEC_CERT_DATE_INVALID: + case ERROR_INTERNET_SEC_CERT_CN_INVALID: + case ERROR_INTERNET_INVALID_CA: { + hr = S_FALSE; + break; + } + + default: { + NOTREACHED() << "Unhandled security problem : " << problem; + break; + } + } + return hr; +} + +HRESULT UrlmonUrlRequest::ConnectToExistingMoniker(IMoniker* moniker, + IBindCtx* context, + const std::wstring& url) { + if (!moniker || url.empty()) { + NOTREACHED() << "Invalid arguments"; + return E_INVALIDARG; + } + + DCHECK(moniker_.get() == NULL); + DCHECK(bind_context_.get() == NULL); + + moniker_ = moniker; + bind_context_ = context; + set_url(WideToUTF8(url)); + return S_OK; +} + +HRESULT UrlmonUrlRequest::StartAsyncDownload() { + HRESULT hr = E_FAIL; + if (moniker_.get() == NULL) { + std::wstring wide_url = UTF8ToWide(url()); + hr = CreateURLMonikerEx(NULL, wide_url.c_str(), moniker_.Receive(), + URL_MK_UNIFORM); + if (FAILED(hr)) { + NOTREACHED() << "CreateURLMonikerEx failed. Error: " << hr; + } else { + hr = CreateAsyncBindCtx(0, this, NULL, bind_context_.Receive()); + DCHECK(SUCCEEDED(hr)) << "CreateAsyncBindCtx failed. Error: " << hr; + } + } else { + DCHECK(bind_context_.get() != NULL); + hr = RegisterBindStatusCallback(bind_context_, this, NULL, 0); + } + + if (SUCCEEDED(hr)) { + ScopedComPtr<IStream> stream; + hr = moniker_->BindToStorage(bind_context_, NULL, __uuidof(IStream), + reinterpret_cast<void**>(stream.Receive())); + if (FAILED(hr)) { + // TODO(joshia): Look into. This currently fails for: + // http://user2:secret@localhost:1337/auth-basic?set-cookie-if-challenged + // when running the UrlRequest unit tests. + DLOG(ERROR) << + StringPrintf("IUrlMoniker::BindToStorage failed. Error: 0x%08X.", hr) + << std::endl << url(); + DCHECK(hr == MK_E_SYNTAX); + } + } + + DLOG_IF(ERROR, FAILED(hr)) + << StringPrintf(L"StartAsyncDownload failed: 0x%08X", hr); + + return hr; +} + +void UrlmonUrlRequest::EndRequest() { + DLOG(INFO) << __FUNCTION__; + // Special case. If the last request was a redirect and the current OS + // error value is E_ACCESSDENIED, that means an unsafe redirect was attempted. + // In that case, correct the OS error value to be the more specific + // ERR_UNSAFE_REDIRECT error value. + if (!status_.is_success() && status_.os_error() == net::ERR_ACCESS_DENIED) { + int status = GetHttpResponseStatus(); + if (status >= 300 && status < 400) { + redirect_status_ = status; // store the latest redirect status value. + status_.set_os_error(net::ERR_UNSAFE_REDIRECT); + } + } + request_handler()->RemoveRequest(this); + OnResponseEnd(status_); + + // If the request was started then we must have an additional reference on the + // request. + if (is_request_started_) { + is_request_started_ = false; + Release(); + } +} + +int UrlmonUrlRequest::GetHttpResponseStatus() const { + if (binding_ == NULL) { + DLOG(WARNING) << "GetHttpResponseStatus - no binding_"; + return 0; + } + + int http_status = 0; + + ScopedComPtr<IWinInetHttpInfo> info; + if (SUCCEEDED(info.QueryFrom(binding_))) { + char status[10] = {0}; + DWORD buf_size = sizeof(status); + if (SUCCEEDED(info->QueryInfo(HTTP_QUERY_STATUS_CODE, status, &buf_size, + 0, NULL))) { + http_status = StringToInt(status); + } else { + NOTREACHED() << "Failed to get HTTP status"; + } + } else { + NOTREACHED() << "failed to get IWinInetHttpInfo from binding_"; + } + + return http_status; +} + +// +// UrlmonUrlRequest::Cache implementation. +// + +size_t UrlmonUrlRequest::Cache::Size() { + size_t size = 0; + if (stream_) { + STATSTG cache_stat = {0}; + stream_->Stat(&cache_stat, STATFLAG_NONAME); + + DCHECK_EQ(0, cache_stat.cbSize.HighPart); + size = cache_stat.cbSize.LowPart; + } + + return size; +} + +size_t UrlmonUrlRequest::Cache::CurrentPos() { + size_t pos = 0; + if (stream_) { + ULARGE_INTEGER current_index = {0}; + stream_->Seek(kZero, STREAM_SEEK_CUR, ¤t_index); + + DCHECK_EQ(0, current_index.HighPart); + pos = current_index.LowPart; + } + + return pos; +} + +size_t UrlmonUrlRequest::Cache::SizeRemaining() { + size_t size = Size(); + size_t pos = CurrentPos(); + size_t size_remaining = 0; + + if (size) { + DCHECK(pos <= size); + size_remaining = size - pos; + } + return size_remaining; +} + +void UrlmonUrlRequest::Cache::Clear() { + if (!stream_) { + NOTREACHED(); + return; + } + + HRESULT hr = stream_->SetSize(kUnsignedZero); + DCHECK(SUCCEEDED(hr)); +} + +bool UrlmonUrlRequest::Cache::Read(IStream* dest, size_t size, + size_t* bytes_copied) { + if (!dest || !size) { + NOTREACHED(); + return false; + } + + // Copy the data and clear cache if there is no more data to copy. + ULARGE_INTEGER size_to_copy = {size, 0}; + ULARGE_INTEGER size_written = {0}; + stream_->CopyTo(dest, size_to_copy, NULL, &size_written); + + if (size_written.LowPart && bytes_copied) + *bytes_copied = size_written.LowPart; + + if (!SizeRemaining()) { + Clear(); + stream_->Seek(kZero, STREAM_SEEK_SET, NULL); + } + + return (size_written.LowPart != 0); +} + +bool UrlmonUrlRequest::Cache::Append(IStream* source, + size_t* bytes_copied) { + if (!source) { + NOTREACHED(); + return false; + } + + size_t current_pos = CurrentPos(); + stream_->Seek(kZero, STREAM_SEEK_END, NULL); + + HRESULT hr = S_OK; + while (SUCCEEDED(hr)) { + DWORD chunk_read = 0; // NOLINT + hr = source->Read(read_buffer_, sizeof(read_buffer_), &chunk_read); + if (!chunk_read) + break; + + DWORD chunk_written = 0; // NOLINT + stream_->Write(read_buffer_, chunk_read, &chunk_written); + DCHECK_EQ(chunk_read, chunk_written); + + if (bytes_copied) + *bytes_copied += chunk_written; + } + + LARGE_INTEGER last_read_position = {current_pos, 0}; + stream_->Seek(last_read_position, STREAM_SEEK_SET, NULL); + return SUCCEEDED(hr); +} + +bool UrlmonUrlRequest::Cache::Create() { + DCHECK(stream_ == NULL); + bool ret = SUCCEEDED(CreateStreamOnHGlobal(NULL, TRUE, stream_.Receive())); + DCHECK(ret && stream_); + return ret; +} + +net::Error UrlmonUrlRequest::HresultToNetError(HRESULT hr) { + // Useful reference: + // http://msdn.microsoft.com/en-us/library/ms775145(VS.85).aspx + + net::Error ret = net::ERR_UNEXPECTED; + + switch (hr) { + case S_OK: + ret = net::OK; + break; + + case MK_E_SYNTAX: + ret = net::ERR_INVALID_URL; + break; + + case INET_E_CANNOT_CONNECT: + ret = net::ERR_CONNECTION_FAILED; + break; + + case INET_E_DOWNLOAD_FAILURE: + case INET_E_CONNECTION_TIMEOUT: + case E_ABORT: + ret = net::ERR_CONNECTION_ABORTED; + break; + + case INET_E_DATA_NOT_AVAILABLE: + ret = net::ERR_EMPTY_RESPONSE; + break; + + case INET_E_RESOURCE_NOT_FOUND: + // To behave more closely to the chrome network stack, we translate this
+ // error value as tunnel connection failed. This error value is tested
+ // in the ProxyTunnelRedirectTest and UnexpectedServerAuthTest tests. + ret = net::ERR_TUNNEL_CONNECTION_FAILED; + break; + + case INET_E_INVALID_URL: + case INET_E_UNKNOWN_PROTOCOL: + case INET_E_REDIRECT_FAILED: + ret = net::ERR_INVALID_URL; + break; + + case INET_E_INVALID_CERTIFICATE: + ret = net::ERR_CERT_INVALID; + break; + + case E_ACCESSDENIED: + ret = net::ERR_ACCESS_DENIED; + break; + + default: + DLOG(WARNING) + << StringPrintf("TODO: translate HRESULT 0x%08X to net::Error", hr); + break; + } + return ret; +} diff --git a/chrome_frame/urlmon_url_request.h b/chrome_frame/urlmon_url_request.h new file mode 100644 index 0000000..114ee6b --- /dev/null +++ b/chrome_frame/urlmon_url_request.h @@ -0,0 +1,231 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_FRAME_URLMON_URL_REQUEST_H_ +#define CHROME_FRAME_URLMON_URL_REQUEST_H_ + +#include <urlmon.h> +#include <atlbase.h> +#include <atlcom.h> + +#include <algorithm> + +#include "base/lock.h" +#include "base/platform_thread.h" +#include "base/scoped_comptr_win.h" +#include "chrome_frame/plugin_url_request.h" + +#include "net/base/net_errors.h" +#include "net/base/upload_data.h" + +class UrlmonUrlRequest + : public CComObjectRootEx<CComSingleThreadModel>, + public PluginUrlRequest, + public IServiceProviderImpl<UrlmonUrlRequest>, + public IBindStatusCallback, + public IHttpNegotiate, + public IAuthenticate, + public IHttpSecurity { + public: + UrlmonUrlRequest(); + ~UrlmonUrlRequest(); + +BEGIN_COM_MAP(UrlmonUrlRequest) + COM_INTERFACE_ENTRY(IHttpNegotiate) + COM_INTERFACE_ENTRY(IServiceProvider) + COM_INTERFACE_ENTRY(IBindStatusCallback) + COM_INTERFACE_ENTRY(IWindowForBindingUI) + COM_INTERFACE_ENTRY(IAuthenticate) + COM_INTERFACE_ENTRY(IHttpSecurity) +END_COM_MAP() + +BEGIN_SERVICE_MAP(UrlmonUrlRequest) + SERVICE_ENTRY(IID_IHttpNegotiate); +END_SERVICE_MAP() + + // PluginUrlRequest implementation + virtual bool Start(); + virtual void Stop(); + virtual bool Read(int bytes_to_read); + + // IBindStatusCallback implementation + STDMETHOD(OnStartBinding)(DWORD reserved, IBinding* binding); + STDMETHOD(GetPriority)(LONG* priority); + STDMETHOD(OnLowResource)(DWORD reserved); + STDMETHOD(OnProgress)(ULONG progress, ULONG max_progress, + ULONG status_code, LPCWSTR status_text); + STDMETHOD(OnStopBinding)(HRESULT result, LPCWSTR error); + STDMETHOD(GetBindInfo)(DWORD* bind_flags, BINDINFO* bind_info); + STDMETHOD(OnDataAvailable)(DWORD flags, DWORD size, FORMATETC* formatetc, + STGMEDIUM* storage); + STDMETHOD(OnObjectAvailable)(REFIID iid, IUnknown* object); + + // IHttpNegotiate implementation + STDMETHOD(BeginningTransaction)(const wchar_t* url, + const wchar_t* current_headers, DWORD reserved, + wchar_t** additional_headers); + STDMETHOD(OnResponse)(DWORD dwResponseCode, const wchar_t* response_headers, + const wchar_t* request_headers, wchar_t** additional_headers); + + // IWindowForBindingUI implementation. This interface is used typically to + // query the window handle which URLMON uses as the parent of error dialogs. + STDMETHOD(GetWindow)(REFGUID guid_reason, HWND* parent_window); + + // IAuthenticate implementation. Used to return the parent window for the + // dialog displayed by IE for authenticating with a proxy. + STDMETHOD(Authenticate)(HWND* parent_window, LPWSTR* user_name, + LPWSTR* password); + + // IHttpSecurity implementation. + STDMETHOD(OnSecurityProblem)(DWORD problem); + + HRESULT ConnectToExistingMoniker(IMoniker* moniker, IBindCtx* context, + const std::wstring& url); + + void set_parent_window(HWND parent_window) { + parent_window_ = parent_window; + } + + protected: + static const size_t kCopyChunkSize = 32 * 1024; + + // A fake stream class to make it easier to copy received data using + // IStream::CopyTo instead of allocating temporary buffers and keeping + // track of data copied so far. + class SendStream + : public CComObjectRoot, + public IStream { + public: + SendStream() { + } + + BEGIN_COM_MAP(SendStream) + COM_INTERFACE_ENTRY(IStream) + COM_INTERFACE_ENTRY(ISequentialStream) + END_COM_MAP() + + void Initialize(UrlmonUrlRequest* request) { + request_ = request; + } + + STDMETHOD(Read)(void* pv, ULONG cb, ULONG* read) { + DCHECK(false) << __FUNCTION__; + return E_NOTIMPL; + } + + STDMETHOD(Write)(const void * buffer, ULONG size, ULONG* size_written) { + DCHECK(request_); + int size_to_write = static_cast<int>( + std::min(static_cast<ULONG>(MAXINT), size)); + request_->OnReadComplete(buffer, size_to_write); + if (size_written) + *size_written = size_to_write; + return S_OK; + } + + STDMETHOD(Seek)(LARGE_INTEGER move, DWORD origin, ULARGE_INTEGER* new_pos) { + DCHECK(false) << __FUNCTION__; + return E_NOTIMPL; + } + + STDMETHOD(SetSize)(ULARGE_INTEGER new_size) { + DCHECK(false) << __FUNCTION__; + return E_NOTIMPL; + } + + STDMETHOD(CopyTo)(IStream* stream, ULARGE_INTEGER cb, ULARGE_INTEGER* read, + ULARGE_INTEGER* written) { + DCHECK(false) << __FUNCTION__; + return E_NOTIMPL; + } + + STDMETHOD(Commit)(DWORD flags) { + DCHECK(false) << __FUNCTION__; + return E_NOTIMPL; + } + + STDMETHOD(Revert)() { + DCHECK(false) << __FUNCTION__; + return E_NOTIMPL; + } + + STDMETHOD(LockRegion)(ULARGE_INTEGER offset, ULARGE_INTEGER cb, + DWORD type) { + DCHECK(false) << __FUNCTION__; + return E_NOTIMPL; + } + + STDMETHOD(UnlockRegion)(ULARGE_INTEGER offset, ULARGE_INTEGER cb, + DWORD type) { + DCHECK(false) << __FUNCTION__; + return E_NOTIMPL; + } + + STDMETHOD(Stat)(STATSTG *pstatstg, DWORD grfStatFlag) { + return E_NOTIMPL; + } + + STDMETHOD(Clone)(IStream** stream) { + DCHECK(false) << __FUNCTION__; + return E_NOTIMPL; + } + + protected: + scoped_refptr<UrlmonUrlRequest> request_; + DISALLOW_COPY_AND_ASSIGN(SendStream); + }; + + // Manage data caching. Note: this class supports cache + // size less than 2GB + class Cache { + public: + bool Create(); + + // Adds data to the end of the cache. + bool Append(IStream* source, size_t* bytes_copied); + + // Reads from the cache. + bool Read(IStream* dest, size_t size, size_t* bytes_copied); + + size_t Size(); + size_t CurrentPos(); + size_t SizeRemaining(); + void Clear(); + bool is_valid() const { + return (stream_ != NULL); + } + + protected: + ScopedComPtr<IStream> stream_; + char read_buffer_[kCopyChunkSize]; + }; + + HRESULT StartAsyncDownload(); + void EndRequest(); + + int GetHttpResponseStatus() const; + + static net::Error HresultToNetError(HRESULT hr); + + private: + std::wstring redirect_url_; + int redirect_status_; + ScopedComPtr<IBinding> binding_; + ScopedComPtr<IMoniker> moniker_; + ScopedComPtr<IBindCtx> bind_context_; + Cache cached_data_; + size_t pending_read_size_; + URLRequestStatus status_; + + uint64 post_data_len_; + + PlatformThreadId thread_; + bool is_request_started_; + static int instance_count_; + HWND parent_window_; + DISALLOW_COPY_AND_ASSIGN(UrlmonUrlRequest); +}; + +#endif // CHROME_FRAME_URLMON_URL_REQUEST_H_ + diff --git a/chrome_frame/utils.cc b/chrome_frame/utils.cc new file mode 100644 index 0000000..b445c26 --- /dev/null +++ b/chrome_frame/utils.cc @@ -0,0 +1,643 @@ +// Copyright (c) 2009 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. + +#include <shlobj.h> + +#include "chrome_frame/html_utils.h" +#include "chrome_frame/utils.h" + +#include "base/file_util.h" +#include "base/file_version_info.h" +#include "base/logging.h" +#include "base/path_service.h" +#include "base/registry.h" +#include "base/scoped_comptr_win.h" +#include "base/string_util.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/installer/util/google_update_constants.h" +#include "googleurl/src/gurl.h" +#include "grit/chrome_frame_resources.h" +#include "chrome_frame/resource.h" + +// Note that these values are all lower case and are compared to +// lower-case-transformed values. +const wchar_t kMetaTag[] = L"meta"; +const wchar_t kHttpEquivAttribName[] = L"http-equiv"; +const wchar_t kContentAttribName[] = L"content"; +const wchar_t kXUACompatValue[] = L"x-ua-compatible"; +const wchar_t kBodyTag[] = L"body"; +const wchar_t kChromeContentPrefix[] = L"chrome="; +const wchar_t kChromeProtocolPrefix[] = L"cf:"; + +static const wchar_t kChromeFrameConfigKey[] = + L"Software\\Google\\ChromeFrame"; +static const wchar_t kChromeFrameOptinUrlsKey[] = L"OptinUrls"; + +// Used to isolate chrome frame builds from google chrome release channels. +const wchar_t kChromeFrameOmahaSuffix[] = L"-cf"; +const wchar_t kDevChannelName[] = L"-dev"; + +const wchar_t kChromeAttachExternalTabPrefix[] = L"attach_external_tab"; + +HRESULT UtilRegisterTypeLib(HINSTANCE tlb_instance, + LPCOLESTR index, + bool for_current_user_only) { + CComBSTR path; + CComPtr<ITypeLib> type_lib; + HRESULT hr = AtlLoadTypeLib(tlb_instance, index, &path, &type_lib); + if (SUCCEEDED(hr)) { + hr = UtilRegisterTypeLib(type_lib, path, NULL, for_current_user_only); + } + return hr; +} + +HRESULT UtilUnRegisterTypeLib(HINSTANCE tlb_instance, + LPCOLESTR index, + bool for_current_user_only) { + CComBSTR path; + CComPtr<ITypeLib> type_lib; + HRESULT hr = AtlLoadTypeLib(tlb_instance, index, &path, &type_lib); + if (SUCCEEDED(hr)) { + hr = UtilUnRegisterTypeLib(type_lib, for_current_user_only); + } + return hr; +} + +HRESULT UtilRegisterTypeLib(LPCWSTR typelib_path, + bool for_current_user_only) { + if (NULL == typelib_path) { + return E_INVALIDARG; + } + CComBSTR path; + CComPtr<ITypeLib> type_lib; + HRESULT hr = ::LoadTypeLib(typelib_path, &type_lib); + if (SUCCEEDED(hr)) { + hr = UtilRegisterTypeLib(type_lib, + typelib_path, + NULL, + for_current_user_only); + } + return hr; +} + +HRESULT UtilUnRegisterTypeLib(LPCWSTR typelib_path, + bool for_current_user_only) { + CComPtr<ITypeLib> type_lib; + HRESULT hr = ::LoadTypeLib(typelib_path, &type_lib); + if (SUCCEEDED(hr)) { + hr = UtilUnRegisterTypeLib(type_lib, for_current_user_only); + } + return hr; +} + +HRESULT UtilRegisterTypeLib(ITypeLib* typelib, + LPCWSTR typelib_path, + LPCWSTR help_dir, + bool for_current_user_only) { + typedef HRESULT(WINAPI *RegisterTypeLibPrototype)(ITypeLib FAR* type_lib, + OLECHAR FAR* full_path, + OLECHAR FAR* help_dir); + LPCSTR function_name = + for_current_user_only ? "RegisterTypeLibForUser" : "RegisterTypeLib"; + RegisterTypeLibPrototype reg_tlb = + reinterpret_cast<RegisterTypeLibPrototype>( + GetProcAddress(GetModuleHandle(_T("oleaut32.dll")), + function_name)); + if (NULL == reg_tlb) { + return E_FAIL; + } + return reg_tlb(typelib, + const_cast<OLECHAR*>(typelib_path), + const_cast<OLECHAR*>(help_dir)); +} + +HRESULT UtilUnRegisterTypeLib(ITypeLib* typelib, + bool for_current_user_only) { + if (NULL == typelib) { + return E_INVALIDARG; + } + typedef HRESULT(WINAPI *UnRegisterTypeLibPrototype)( + REFGUID libID, + unsigned short wVerMajor, // NOLINT + unsigned short wVerMinor, // NOLINT + LCID lcid, + SYSKIND syskind); + LPCSTR function_name = + for_current_user_only ? "UnRegisterTypeLibForUser" : "UnRegisterTypeLib"; + + UnRegisterTypeLibPrototype unreg_tlb = + reinterpret_cast<UnRegisterTypeLibPrototype>( + GetProcAddress(GetModuleHandle(_T("oleaut32.dll")), + function_name)); + if (NULL == unreg_tlb) { + return E_FAIL; + } + TLIBATTR* tla = NULL; + HRESULT hr = typelib->GetLibAttr(&tla); + if (SUCCEEDED(hr)) { + hr = unreg_tlb(tla->guid, + tla->wMajorVerNum, + tla->wMinorVerNum, + tla->lcid, + tla->syskind); + typelib->ReleaseTLibAttr(tla); + } + return hr; +} + +HRESULT UtilGetXUACompatContentValue(const std::wstring& html_string, + std::wstring* content_value) { + if (!content_value) { + return E_POINTER; + } + + // Fail fast if the string X-UA-Compatible isn't in html_string + if (StringToLowerASCII(html_string).find(kXUACompatValue) == + std::wstring::npos) { + return E_FAIL; + } + + HTMLScanner scanner(html_string.c_str()); + + // Build the list of meta tags that occur before the body tag is hit. + HTMLScanner::StringRangeList tag_list; + scanner.GetTagsByName(kMetaTag, &tag_list, kBodyTag); + + // Search the list of meta tags for one with an http-equiv="X-UA-Compatible" + // attribute. + HTMLScanner::StringRange attribute; + std::string search_attribute_ascii(WideToASCII(kXUACompatValue)); + HTMLScanner::StringRangeList::const_iterator tag_list_iter(tag_list.begin()); + for (; tag_list_iter != tag_list.end(); tag_list_iter++) { + if (!tag_list_iter->GetTagAttribute(kHttpEquivAttribName, &attribute)) { + continue; + } + + // We found an http-equiv meta tag, check its value using the ascii + // case-insensitive comparison method. + if (!attribute.LowerCaseEqualsASCII(search_attribute_ascii.c_str())) { + continue; + } + + // We found our X-UA-Compatible meta tag so look for and extract + // the value of the content attribute. + if (!tag_list_iter->GetTagAttribute(kContentAttribName, &attribute)) { + continue; + } + + // Found the content string, copy and return. + content_value->assign(attribute.Copy()); + return S_OK; + } + + return E_FAIL; +} + +bool AppendSuffixToChannelName(std::wstring* string, + const std::wstring& channel_name, + const std::wstring& suffix) { + size_t pos = string->find(channel_name); + // Append the suffix only if we find the channel name. + if (pos != std::wstring::npos) { + pos += channel_name.size(); + // Append the suffix only to the channel name only if the name is not + // already followed by suffix. + if (string->find(suffix, pos) != pos) { + string->insert(pos, suffix); + return true; + } + } + return false; +} + +bool RemoveSuffixFromChannelName(std::wstring* string, + const std::wstring& channel_name, + const std::wstring& suffix) { + std::wstring decorated_channel(channel_name + suffix); + size_t pos = string->find(decorated_channel); + // TODO(robertshield): Remove the suffix iff the suffix is the last thing in + // the string or is followed by another suffix that starts with '-'. + if (pos != std::wstring::npos) { + pos += channel_name.size(); + string->erase(pos, suffix.size()); + return true; + } + return false; +} + +HRESULT UtilUpdateOmahaConfig(bool add_cf_suffix) { + HKEY reg_root = HKEY_LOCAL_MACHINE; + + RegKey key; + std::wstring ap_key_value; + std::wstring reg_key(google_update::kRegPathClientState); + reg_key.append(L"\\"); + reg_key.append(google_update::kChromeGuid); + if (!key.Open(reg_root, reg_key.c_str(), KEY_READ | KEY_WRITE) || + !key.ReadValue(google_update::kRegApField, &ap_key_value)) { + // Can't read the Omaha config. + return REGDB_E_READREGDB; + } + + HRESULT result = S_OK; + // We've read the key in, try and modify it then write it back. + if (add_cf_suffix && AppendSuffixToChannelName(&ap_key_value, + kDevChannelName, + kChromeFrameOmahaSuffix)) { + if (!key.WriteValue(google_update::kRegApField, ap_key_value.c_str())) { + DLOG(ERROR) << "Failed to add suffix to omaha ap key value."; + result = REGDB_E_WRITEREGDB; + } + } else if (!add_cf_suffix && + RemoveSuffixFromChannelName(&ap_key_value, + kDevChannelName, + kChromeFrameOmahaSuffix)) { + if (!key.WriteValue(google_update::kRegApField, ap_key_value.c_str())) { + DLOG(ERROR) << "Failed to remove suffix from omaha ap key value."; + result = REGDB_E_WRITEREGDB; + } + } else { + // Getting here means that no modifications needed to be made. + result = S_FALSE; + } + + return result; +} + +std::wstring GetResourceString(int resource_id) { + std::wstring resource_string; + HMODULE this_module = reinterpret_cast<HMODULE>(&__ImageBase); + const ATLSTRINGRESOURCEIMAGE* image = AtlGetStringResourceImage( + this_module, resource_id); + if (image) { + resource_string.assign(image->achString, image->nLength); + } else { + NOTREACHED() << "Unable to find resource id " << resource_id; + } + return resource_string; +} + +void DisplayVersionMismatchWarning(HWND parent, + const std::string& server_version) { + // Obtain the current module version. + FileVersionInfo* file_version_info = + FileVersionInfo::CreateFileVersionInfoForCurrentModule(); + DCHECK(file_version_info); + std::wstring version_string(file_version_info->file_version()); + std::wstring wide_server_version; + if (server_version.empty()) { + wide_server_version = GetResourceString(IDS_VERSIONUNKNOWN); + } else { + wide_server_version = ASCIIToWide(server_version); + } + std::wstring title = GetResourceString(IDS_VERSIONMISMATCH_HEADER); + std::wstring message; + SStringPrintf(&message, GetResourceString(IDS_VERSIONMISMATCH).c_str(), + wide_server_version.c_str(), version_string.c_str()); + + ::MessageBox(parent, message.c_str(), title.c_str(), MB_OK); +} + +std::string CreateJavascript(const std::string& function_name, + const std::string args) { + std::string script_string = "javascript:"; + script_string += function_name + "("; + if (!args.empty()) { + script_string += "'"; + script_string += args; + script_string += "'"; + } + script_string += ")"; + return script_string; +} + +AddRefModule::AddRefModule() { + // TODO(tommi): Override the module's Lock/Unlock methods to call + // npapi::SetValue(NPPVpluginKeepLibraryInMemory) and keep the dll loaded + // while the module's refcount is > 0. Only do this when we're being + // used as an NPAPI module. + _pAtlModule->Lock(); +} + + +AddRefModule::~AddRefModule() { + _pAtlModule->Unlock(); +} + +namespace { +const char kIEImageName[] = "iexplore.exe"; +const char kFirefoxImageName[] = "firefox.exe"; +const char kOperaImageName[] = "opera.exe"; +} // namespace + +std::wstring GetHostProcessName(bool include_extension) { + FilePath exe; + if (PathService::Get(base::FILE_EXE, &exe)) + exe = exe.BaseName(); + if (!include_extension) { + exe = exe.RemoveExtension(); + } + return exe.ToWStringHack(); +} + +BrowserType GetBrowserType() { + static BrowserType browser_type = BROWSER_INVALID; + + if (browser_type == BROWSER_INVALID) { + std::wstring exe(GetHostProcessName(true)); + if (!exe.empty()) { + std::wstring::const_iterator begin = exe.begin(); + std::wstring::const_iterator end = exe.end(); + if (LowerCaseEqualsASCII(begin, end, kIEImageName)) { + browser_type = BROWSER_IE; + } else if (LowerCaseEqualsASCII(begin, end, kFirefoxImageName)) { + browser_type = BROWSER_FIREFOX; + } else if (LowerCaseEqualsASCII(begin, end, kOperaImageName)) { + browser_type = BROWSER_OPERA; + } else { + browser_type = BROWSER_UNKNOWN; + } + } else { + NOTREACHED(); + } + } + + return browser_type; +} + +IEVersion GetIEVersion() { + static IEVersion ie_version = IE_INVALID; + + if (ie_version == IE_INVALID) { + wchar_t exe_path[MAX_PATH]; + HMODULE mod = GetModuleHandle(NULL); + GetModuleFileName(mod, exe_path, arraysize(exe_path) - 1); + std::wstring exe_name(file_util::GetFilenameFromPath(exe_path)); + if (!LowerCaseEqualsASCII(exe_name, kIEImageName)) { + ie_version = NON_IE; + } else { + uint32 high = 0; + uint32 low = 0; + if (GetModuleVersion(mod, &high, &low)) { + switch (HIWORD(high)) { + case 6: + ie_version = IE_6; + break; + case 7: + ie_version = IE_7; + break; + default: + ie_version = HIWORD(high) >= 8 ? IE_8 : IE_UNSUPPORTED; + break; + } + } else { + NOTREACHED() << "Can't get IE version"; + } + } + } + + return ie_version; +} + +bool IsIEInPrivate() { + typedef BOOL (WINAPI* IEIsInPrivateBrowsingPtr)(); + bool incognito_mode = false; + HMODULE h = GetModuleHandle(L"ieframe.dll"); + if (h) { + IEIsInPrivateBrowsingPtr IsInPrivate = + reinterpret_cast<IEIsInPrivateBrowsingPtr>(GetProcAddress(h, + "IEIsInPrivateBrowsing")); + if (IsInPrivate) { + incognito_mode = !!IsInPrivate(); + } + } + + return incognito_mode; +} + +bool GetModuleVersion(HMODULE module, uint32* high, uint32* low) { + DCHECK(module != NULL) + << "Please use GetModuleHandle(NULL) to get the process name"; + DCHECK(high); + + bool ok = false; + + HRSRC res = FindResource(module, + reinterpret_cast<const wchar_t*>(VS_VERSION_INFO), RT_VERSION); + if (res) { + HGLOBAL res_data = LoadResource(module, res); + DWORD version_resource_size = SizeofResource(module, res); + const void* readonly_resource_data = LockResource(res_data); + if (readonly_resource_data && version_resource_size) { + // Copy data as VerQueryValue tries to modify the data. This causes + // exceptions and heap corruption errors if debugger is attached. + scoped_ptr<char> data(new char[version_resource_size]); + memcpy(data.get(), readonly_resource_data, version_resource_size); + if (data.get()) { + VS_FIXEDFILEINFO* ver_info = NULL; + UINT info_size = 0; + if (VerQueryValue(data.get(), L"\\", + reinterpret_cast<void**>(&ver_info), &info_size)) { + *high = ver_info->dwFileVersionMS; + if (low != NULL) + *low = ver_info->dwFileVersionLS; + ok = true; + } + + UnlockResource(res_data); + } + FreeResource(res_data); + } + } + + return ok; +} + +namespace { + +const int kMaxSubmenuDepth = 10; + +// Copies original_menu and returns the copy. The caller is responsible for +// closing the returned HMENU. This does not currently copy over bitmaps +// (e.g. hbmpChecked, hbmpUnchecked or hbmpItem), so checkmarks, radio buttons, +// and custom icons won't work. +// It also copies over submenus up to a maximum depth of kMaxSubMenuDepth. +// +// TODO(robertshield): Add support for the bitmap fields if need be. +HMENU UtilCloneContextMenuImpl(HMENU original_menu, int depth) { + DCHECK(IsMenu(original_menu)); + + if (depth >= kMaxSubmenuDepth) + return NULL; + + HMENU new_menu = CreatePopupMenu(); + int item_count = GetMenuItemCount(original_menu); + if (item_count <= 0) { + NOTREACHED(); + } else { + for (int i = 0; i < item_count; i++) { + MENUITEMINFO item_info = { 0 }; + item_info.cbSize = sizeof(MENUITEMINFO); + item_info.fMask = MIIM_ID | MIIM_STRING | MIIM_FTYPE | + MIIM_STATE | MIIM_DATA | MIIM_SUBMENU | + MIIM_CHECKMARKS | MIIM_BITMAP; + + // Call GetMenuItemInfo a first time to obtain the buffer size for + // the label. + if (GetMenuItemInfo(original_menu, i, TRUE, &item_info)) { + item_info.cch++; // Increment this as per MSDN + std::vector<wchar_t> buffer(item_info.cch, 0); + item_info.dwTypeData = &buffer[0]; + + // Call GetMenuItemInfo a second time with dwTypeData set to a buffer + // of a correct size to get the label. + GetMenuItemInfo(original_menu, i, TRUE, &item_info); + + // Clone any submenus. Within reason. + if (item_info.hSubMenu) { + HMENU new_submenu = UtilCloneContextMenuImpl(item_info.hSubMenu, + depth + 1); + item_info.hSubMenu = new_submenu; + } + + // Now insert the item into the new menu. + InsertMenuItem(new_menu, i, TRUE, &item_info); + } + } + } + return new_menu; +} + +} // namespace + +HMENU UtilCloneContextMenu(HMENU original_menu) { + return UtilCloneContextMenuImpl(original_menu, 0); +} + +std::string ResolveURL(const std::string& document, + const std::string& relative) { + if (document.empty()) { + return GURL(relative).spec(); + } else { + return GURL(document).Resolve(relative).spec(); + } +} + +bool HaveSameOrigin(const std::string& url1, const std::string& url2) { + GURL a(url1), b(url2); + bool ret; + if (a.is_valid() != b.is_valid()) { + // Either (but not both) url is invalid, so they can't match. + ret = false; + } else if (!a.is_valid()) { + // Both URLs are invalid (see first check). Just check if the opaque + // strings match exactly. + ret = url1.compare(url2) == 0; + } else if (a.GetOrigin() != b.GetOrigin()) { + // The origins don't match. + ret = false; + } else { + // we have a match. + ret = true; + } + + return ret; +} + +int GetConfigInt(int default_value, const wchar_t* value_name) { + int ret = default_value; + RegKey config_key; + if (config_key.Open(HKEY_CURRENT_USER, kChromeFrameConfigKey, + KEY_QUERY_VALUE)) { + int value = FALSE; + if (config_key.ReadValueDW(value_name, reinterpret_cast<DWORD*>(&value))) { + ret = value; + } + } + + return ret; +} + +bool GetConfigBool(bool default_value, const wchar_t* value_name) { + DWORD value = GetConfigInt(default_value, value_name); + return (value != FALSE); +} + +bool IsOptInUrl(const wchar_t* url) { + RegKey config_key; + if (!config_key.Open(HKEY_CURRENT_USER, kChromeFrameConfigKey, KEY_READ)) + return false; + + RegistryValueIterator optin_urls_list(config_key.Handle(), + kChromeFrameOptinUrlsKey); + while (optin_urls_list.Valid()) { + if (MatchPattern(url, optin_urls_list.Name())) + return true; + ++optin_urls_list; + } + + return false; +} + +HRESULT GetUrlFromMoniker(IMoniker* moniker, IBindCtx* bind_context, + std::wstring* url) { + if (!moniker || !url) { + NOTREACHED(); + return E_INVALIDARG; + } + + ScopedComPtr<IBindCtx> temp_bind_context; + if (!bind_context) { + CreateBindCtx(0, temp_bind_context.Receive()); + bind_context = temp_bind_context; + } + + CComHeapPtr<WCHAR> display_name; + HRESULT hr = moniker->GetDisplayName(bind_context, NULL, &display_name); + if (display_name) + *url = display_name; + + return hr; +} + +bool IsValidUrlScheme(const std::wstring& url) { + if (url.empty()) + return false; + + GURL crack_url(url); + + if (crack_url.SchemeIs("http") || crack_url.SchemeIs("https") || + crack_url.SchemeIs("about") || crack_url.SchemeIs("view-source")) + return true; + + if (StartsWith(url, kChromeAttachExternalTabPrefix, false)) + return true; + + return false; +} + +// TODO(robertshield): Register and use Chrome's PathProviders. +// - Note that this function is used by unit tests as well to override +// PathService paths, so please test when addressing todo. +bool GetUserProfileBaseDirectory(std::wstring* path) { + DCHECK(path); + wchar_t path_buffer[MAX_PATH * 4]; + path_buffer[0] = 0; + // TODO(robertshield): Ideally we should use SHGetFolderLocation and then + // get a path via PIDL. + HRESULT hr = SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, + SHGFP_TYPE_CURRENT, path_buffer); + + if (SUCCEEDED(hr)) { + *path = path_buffer; +#if defined(GOOGLE_CHROME_BUILD) + file_util::AppendToPath(path, FILE_PATH_LITERAL("Google")); +#endif + file_util::AppendToPath(path, chrome::kBrowserAppName); + file_util::AppendToPath(path, chrome::kUserDataDirname); + return true; + } + + return false; +} diff --git a/chrome_frame/utils.h b/chrome_frame/utils.h new file mode 100644 index 0000000..80b9a53 --- /dev/null +++ b/chrome_frame/utils.h @@ -0,0 +1,227 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_FRAME_UTILS_H_ +#define CHROME_FRAME_UTILS_H_ + +#include <atlbase.h> +#include <string> + +#include "base/basictypes.h" + +// utils.h : Various utility functions and classes + +extern const wchar_t kChromeContentPrefix[]; +extern const wchar_t kChromeProtocolPrefix[]; + +// This function is very similar to the AtlRegisterTypeLib function except +// that it takes a parameter that specifies whether to register the typelib +// for the current user only or on a machine-wide basis +// Refer to the MSDN documentation for AtlRegisterTypeLib for a description of +// the arguments +HRESULT UtilRegisterTypeLib(HINSTANCE tlb_instance, + LPCOLESTR index, + bool for_current_user_only); + +// This function is very similar to the AtlUnRegisterTypeLib function except +// that it takes a parameter that specifies whether to unregister the typelib +// for the current user only or on a machine-wide basis +// Refer to the MSDN documentation for AtlUnRegisterTypeLib for a description +// of the arguments +HRESULT UtilUnRegisterTypeLib(HINSTANCE tlb_instance, + LPCOLESTR index, + bool for_current_user_only); + +HRESULT UtilRegisterTypeLib(LPCWSTR typelib_path, bool for_current_user_only); + +HRESULT UtilUnRegisterTypeLib(LPCWSTR typelib_path, bool for_current_user_only); + +HRESULT UtilRegisterTypeLib(ITypeLib* typelib, + LPCWSTR typelib_path, + LPCWSTR help_dir, + bool for_current_user_only); + +HRESULT UtilUnRegisterTypeLib(ITypeLib* typelib, + bool for_current_user_only); + +// Given an HTML fragment, this function looks for the +// <meta http-equiv="X-UA-Compatible"> tag and extracts the value of the +// "content" attribute +// This method will currently return a false positive if the tag appears +// inside a string in a <SCRIPT> block. +HRESULT UtilGetXUACompatContentValue(const std::wstring& html_string, + std::wstring* content_value); + + +// Appends |suffix| to the substring |channel_name| of |string| iff +// the first instance of |channel_name| in |string| is not already followed by +// |suffix|. +// Returns true if |string| was modified. +bool AppendSuffixToChannelName(std::wstring* string, + const std::wstring& channel_name, + const std::wstring& suffix); + +// Removes |suffix| from |string| if |string| contains |channel_name| followed +// by |suffix|. +// Returns true if |string| was modified. +bool RemoveSuffixFromChannelName(std::wstring* string, + const std::wstring& channel_name, + const std::wstring& suffix); + +// Looks for and alters if found the Omaha configuration for Chrome in the +// registry. This changes the auto-update release channel to prevent installed +// builds of Chrome that include Chrome Frame from getting replaced by +// Chrome updates without it. +// Adds the Chrome Frame suffix if add_cf_suffix is true, removes it +// otherwise. +// Returns S_OK if the Chrome Omaha configuration was found and updated. +// Returns S_FALSE if the configuration was found but didn't need updating. +// Returns REGDB_E_READREGDB if the Chrome Omaha key could not be read. +// Returns REGDB_E_WRITEREGDB if the Chrome Omaha key could not be written. +HRESULT UtilUpdateOmahaConfig(bool add_cf_suffix); + +// Returns a string from ChromeFrame's string table by resource. Must be +// provided with a valid resource id. +std::wstring GetResourceString(int resource_id); + +// Displays a message box indicating that there was a version mismatch between +// ChromeFrame and the running instance of Chrome. +// server_version is the version of the running instance of Chrome. +void DisplayVersionMismatchWarning(HWND parent, + const std::string& server_version); + +// This class provides a base implementation for ATL modules which want to +// perform all their registration under HKCU. This class overrides the +// RegisterServer and UnregisterServer methods and registers the type libraries +// under HKCU (the rest of the registation is made under HKCU by changing the +// appropriate .RGS files) +template < class BaseAtlModule > +class AtlPerUserModule : public BaseAtlModule { + public: + HRESULT RegisterServer(BOOL reg_typelib = FALSE, + const CLSID* clsid = NULL) throw() { + HRESULT hr = BaseAtlModule::RegisterServer(FALSE, clsid); + if (FAILED(hr)) { + return hr; + } + if (reg_typelib) { + hr = UtilRegisterTypeLib(_AtlComModule.m_hInstTypeLib, NULL, false); + } + return hr; + } + + HRESULT UnregisterServer(BOOL unreg_typelib, + const CLSID* clsid = NULL) throw() { + HRESULT hr = BaseAtlModule::UnregisterServer(FALSE, clsid); + if (FAILED(hr)) { + return hr; + } + if (unreg_typelib) { + hr = UtilUnRegisterTypeLib(_AtlComModule.m_hInstTypeLib, NULL, false); + } + return hr; + } +}; + +// Creates a javascript statement for execution from the function name and +// arguments passed in. +std::string CreateJavascript(const std::string& function_name, + const std::string args); + +// Use to prevent the DLL from being unloaded while there are still living +// objects with outstanding references. +class AddRefModule { + public: + AddRefModule(); + ~AddRefModule(); +}; + +// Retrieves the executable name of the process hosting us. If +// |include_extension| is false, then we strip the extension from the name. +std::wstring GetHostProcessName(bool include_extension); + +typedef enum BrowserType { + BROWSER_INVALID = -1, + BROWSER_UNKNOWN, + BROWSER_IE, + BROWSER_FIREFOX, + BROWSER_OPERA, +}; + +BrowserType GetBrowserType(); + +typedef enum IEVersion { + IE_INVALID, + NON_IE, + IE_UNSUPPORTED, + IE_6, + IE_7, + IE_8, +}; + +// To get the IE version when Chrome Frame is hosted in IE. Make sure that +// the hosting browser is IE before calling this function, otherwise NON_IE +// will be returned. +IEVersion GetIEVersion(); + +// Retrieves the file version from a module handle without extra round trips +// to the disk (as happens with the regular GetFileVersionInfo API). +// +// @param module A handle to the module for which to retrieve the version info. +// @param high On successful return holds the most significant part of the +// file version. Must be non-null. +// @param low On successful return holds the least significant part of the +// file version. May be NULL. +// @returns true if the version info was successfully retrieved. +bool GetModuleVersion(HMODULE module, uint32* high, uint32* low); + +// Return if the IEXPLORE is in private mode. The IEIsInPrivateBrowsing() checks +// whether current process is IEXPLORE. +bool IsIEInPrivate(); + +// Creates a copy of a menu. We need this when original menu comes from +// a process with higher integrity. +HMENU UtilCloneContextMenu(HMENU original_menu); + +// Uses GURL internally to append 'relative' to 'document' +std::string ResolveURL(const std::string& document, + const std::string& relative); + +// Returns true iff the two urls have the same scheme, same host and same port. +bool HaveSameOrigin(const std::string& url1, const std::string& url2); + +// Get a boolean configuration value from registry. +bool GetConfigBool(bool default_value, const wchar_t* value_name); + +// Gets an integer configuration value from the registry. +int GetConfigInt(int default_value, const wchar_t* value_name); + +// Check if this url is opting into Chrome Frame based on static settings. +bool IsOptInUrl(const wchar_t* url); + +// A shortcut for QueryService +template <typename T> +HRESULT DoQueryService(const CLSID& class_id, IUnknown* unk, T** service) { + if (!unk) + return E_INVALIDARG; + ScopedComPtr<IServiceProvider> service_provider; + HRESULT hr = service_provider.QueryFrom(unk); + if (!service_provider) + return hr; + + return service_provider->QueryService(class_id, service); +} + +// Get url (display name) from a moniker, |bind_context| is optional +HRESULT GetUrlFromMoniker(IMoniker* moniker, IBindCtx* bind_context, + std::wstring* url); + +// Returns true if the URL passed in is something which can be handled by +// Chrome. If this function returns false then we should fail the navigation. +bool IsValidUrlScheme(const std::wstring& url); + +// This returns the base directory in which to store user profiles. +bool GetUserProfileBaseDirectory(std::wstring* path); + +#endif // CHROME_FRAME_UTILS_H_ diff --git a/chrome_frame/vectored_handler-impl.h b/chrome_frame/vectored_handler-impl.h new file mode 100644 index 0000000..f850641 --- /dev/null +++ b/chrome_frame/vectored_handler-impl.h @@ -0,0 +1,106 @@ +// Copyright (c) 2009 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.
+
+#ifndef CHROME_FRAME_VECTORED_HANDLER_IMPL_H_
+#define CHROME_FRAME_VECTORED_HANDLER_IMPL_H_
+#include "chrome_frame/vectored_handler.h"
+
+#if defined(_M_IX86)
+typedef struct _EXCEPTION_REGISTRATION_RECORD {
+ struct _EXCEPTION_REGISTRATION_RECORD* Next;
+ PVOID Handler;
+} EXCEPTION_REGISTRATION_RECORD;
+#define EXCEPTION_CHAIN_END ((struct _EXCEPTION_REGISTRATION_RECORD*)-1)
+#else
+#error only x86 is supported for now.
+#endif
+
+
+// VEH handler flags settings.
+// These are grabbed from winnt.h for PocketPC.
+// Only EXCEPTION_NONCONTINUABLE in defined in "regular" winnt.h
+// #define EXCEPTION_NONCONTINUABLE 0x1 // Noncontinuable exception
+#define EXCEPTION_UNWINDING 0x2 // Unwind is in progress
+#define EXCEPTION_EXIT_UNWIND 0x4 // Exit unwind is in progress
+#define EXCEPTION_STACK_INVALID 0x8 // Stack out of limits or unaligned
+#define EXCEPTION_NESTED_CALL 0x10 // Nested exception handler call
+#define EXCEPTION_TARGET_UNWIND 0x20 // Target unwind in progress
+#define EXCEPTION_COLLIDED_UNWIND 0x40 // Collided exception handler call
+
+#define EXCEPTION_UNWIND (EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND | \
+ EXCEPTION_TARGET_UNWIND | EXCEPTION_COLLIDED_UNWIND)
+
+#define IS_UNWINDING(Flag) (((Flag) & EXCEPTION_UNWIND) != 0)
+#define IS_DISPATCHING(Flag) (((Flag) & EXCEPTION_UNWIND) == 0)
+#define IS_TARGET_UNWIND(Flag) ((Flag) & EXCEPTION_TARGET_UNWIND)
+// End of grabbed section
+
+template <class E>
+LONG WINAPI VectoredHandlerT<E>::VectoredHandler(
+ EXCEPTION_POINTERS* exceptionInfo) {
+ // TODO(stoyan): Consider reentrancy
+ const DWORD exceptionCode = exceptionInfo->ExceptionRecord->ExceptionCode;
+
+ // Not interested in non-error exceptions. In this category falls exceptions
+ // like:
+ // 0x40010006 - OutputDebugStringA. Seen when no debugger is attached
+ // (otherwise debugger swallows the exception and prints
+ // the string).
+ // 0x406D1388 - DebuggerProbe. Used by debug CRT - for example see source
+ // code of isatty(). Used to name a thread as well.
+ // RPC_E_DISCONNECTED and Co. - COM IPC non-fatal warnings
+ // STATUS_BREAKPOINT and Co. - Debugger related breakpoints
+
+ if ((exceptionCode & ERROR_SEVERITY_ERROR) != ERROR_SEVERITY_ERROR) {
+ return ExceptionContinueSearch;
+ }
+
+ ++VectoredHandlerT<E>::g_exceptions_seen;
+
+ // TODO(stoyan): Check whether exception address is inbetween
+ // [IsBadReadPtr, IsBadReadPtr + 0xXX]
+
+ const DWORD exceptionFlags = exceptionInfo->ExceptionRecord->ExceptionFlags;
+ // I don't think VEH is called on unwind. Just to be safe.
+ if (IS_DISPATCHING(exceptionFlags)) {
+ if (ModuleHasInstalledSEHFilter())
+ return ExceptionContinueSearch;
+
+ if (E::IsOurModule(exceptionInfo->ExceptionRecord->ExceptionAddress)) {
+ E::WriteDump(exceptionInfo);
+ return ExceptionContinueSearch;
+ }
+
+ // See whether our module is somewhere in the call stack.
+ void* back_trace[max_back_trace] = {0};
+ // Skip RtlCaptureStackBackTrace and VectoredHandler itself.
+ DWORD captured = E::RtlCaptureStackBackTrace(2, max_back_trace - 2,
+ &back_trace[0], NULL);
+ for (DWORD i = 0; i < captured; ++i) {
+ if (E::IsOurModule(back_trace[i])) {
+ E::WriteDump(exceptionInfo);
+ return ExceptionContinueSearch;
+ }
+ }
+ }
+
+ return ExceptionContinueSearch;
+}
+
+template <class E>
+BOOL VectoredHandlerT<E>::ModuleHasInstalledSEHFilter() {
+ EXCEPTION_REGISTRATION_RECORD* RegistrationFrame = E::RtlpGetExceptionList();
+ // TODO(stoyan): Add the stack limits check and some sanity checks like
+ // decreasing addresses of registration records
+ while (RegistrationFrame != EXCEPTION_CHAIN_END) {
+ if (E::IsOurModule(RegistrationFrame->Handler)) {
+ return TRUE;
+ }
+
+ RegistrationFrame = RegistrationFrame->Next;
+ }
+
+ return FALSE;
+}
+#endif // CHROME_FRAME_VECTORED_HANDLER_IMPL_H_
diff --git a/chrome_frame/vectored_handler.h b/chrome_frame/vectored_handler.h new file mode 100644 index 0000000..7351b6c --- /dev/null +++ b/chrome_frame/vectored_handler.h @@ -0,0 +1,87 @@ +// Copyright (c) 2009 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.
+
+#ifndef CHROME_FRAME_VECTORED_HANDLER_H_
+#define CHROME_FRAME_VECTORED_HANDLER_H_
+
+// Base class for VectoredHandlerT just to hold some members (independent of
+// template parameter)
+class VectoredHandlerBase {
+ public:
+ // For RtlCaptureStackBackTrace MSDN says:
+ // Windows Server 2003 and Windows XP: The sum of the FramesToSkip and
+ // FramesToCapture parameters must be less than 64.
+ // In practice (on XPSP2) it has to be less than 63, hence leaving us with
+ // max back trace of 62.
+ static const DWORD max_back_trace = 62;
+ static unsigned long g_exceptions_seen;
+ protected:
+ static void* g_handler;
+};
+
+DECLSPEC_SELECTANY void* VectoredHandlerBase::g_handler;
+DECLSPEC_SELECTANY unsigned long VectoredHandlerBase::g_exceptions_seen;
+
+// The E class is supposed to provide external/API functions. Using template
+// make testability easier. It shall confirm the following concept/archetype:
+// void* Register(PVECTORED_EXCEPTION_HANDLER,
+// const void* module_start, const void* module_end)
+// Registers Vectored Exception Handler, non-unittest implementation shall call
+// ::AddVectoredExceptionHandler Win32 API
+// ULONG Unregister(void*) - ::RemoveVectoredExceptionHandler Win32 API
+// int IsOurModule(const void* address) -
+// void WriteDump(EXCEPTION_POINTERS*) -
+// WORD RtlCaptureStackBackTrace(..) - same as Win32 API
+// EXCEPTION_REGISTRATION_RECORD* RtlpGetExceptionList() - same as Win32 API
+// You may want to derive own External class by deriving from
+// VEHExternalBase helper (see below).
+// Create dump policy:
+// 1. Scan SEH chain, if there is a handler/filter that belongs to our
+// module - assume we expect this one and hence do nothing here.
+// 2. If the address of the exception is in our module - create dump.
+// 3. If our module is in somewhere in callstack - create dump.
+template <class E>
+class VectoredHandlerT : public VectoredHandlerBase {
+ public:
+ static void* Register(const void* module_start, const void* module_end) {
+ g_exceptions_seen = 0;
+ g_handler = E::Register(&VectoredHandler, module_start, module_end);
+ return g_handler;
+ }
+
+ static ULONG Unregister() {
+ if (g_handler)
+ return E::Unregister(g_handler);
+ return 0;
+ }
+
+ static LONG WINAPI VectoredHandler(EXCEPTION_POINTERS* exceptionInfo);
+ private:
+ static BOOL ModuleHasInstalledSEHFilter();
+};
+
+// Handy class supposed to act as a base class for classes used as template
+// parameter of VectoredHandlerT<E>
+class VEHTraitsBase {
+ public:
+ static const void* g_module_start;
+ static const void* g_module_end;
+
+ static inline int IsOurModule(const void* address) {
+ return (g_module_start <= address && address < g_module_end);
+ }
+
+ static inline void SetModule(const void* module_start,
+ const void* module_end) {
+ g_module_start = module_start;
+ g_module_end = module_end;
+ }
+};
+
+DECLSPEC_SELECTANY const void* VEHTraitsBase::g_module_start;
+DECLSPEC_SELECTANY const void* VEHTraitsBase::g_module_end;
+
+class Win32VEHTraits;
+typedef class VectoredHandlerT<Win32VEHTraits> VectoredHandler;
+#endif // CHROME_FRAME_VECTORED_HANDLER_H_
diff --git a/chrome_frame/vtable_patch_manager.cc b/chrome_frame/vtable_patch_manager.cc new file mode 100644 index 0000000..5f15158 --- /dev/null +++ b/chrome_frame/vtable_patch_manager.cc @@ -0,0 +1,82 @@ +// Copyright (c) 2009 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. + +#include "chrome_frame/vtable_patch_manager.h" + +#include "base/logging.h" + +#include "chrome_frame/function_stub.h" + +namespace vtable_patch { + +// Convenient definition of a VTABLE +typedef PROC* Vtable; + +// Returns a pointer to the VTable of a COM interface. +// @param unknown [in] The pointer of the COM interface. +inline Vtable GetIFVTable(void* unknown) { + return reinterpret_cast<Vtable>(*reinterpret_cast<void**>(unknown)); +} + +HRESULT PatchInterfaceMethods(void* unknown, MethodPatchInfo* patches) { + // Do some sanity checking of the input arguments. + if (NULL == unknown || NULL == patches) { + NOTREACHED(); + return E_INVALIDARG; + } + + Vtable vtable = GetIFVTable(unknown); + DCHECK(vtable); + + for (MethodPatchInfo* it = patches; it->index_ != -1; ++it) { + PROC original_fn = vtable[it->index_]; + FunctionStub* stub = FunctionStub::FromCode(original_fn); + if (stub != NULL) { + DLOG(ERROR) << "attempt to patch a function that's already patched"; + DCHECK(stub->absolute_target() == + reinterpret_cast<uintptr_t>(it->method_)) << + "patching the same method multiple times with different hooks?"; + continue; + } + + stub = FunctionStub::Create(reinterpret_cast<uintptr_t>(original_fn), + it->method_); + if (!stub) { + NOTREACHED(); + return E_OUTOFMEMORY; + } else { + DWORD protect = 0; + if (::VirtualProtect(&vtable[it->index_], sizeof(PROC), + PAGE_EXECUTE_READWRITE, &protect)) { + it->stub_ = stub; // save the stub + vtable[it->index_] = stub->code(); + ::VirtualProtect(&vtable[it->index_], sizeof(PROC), protect, + &protect); + } else { + NOTREACHED(); + } + } + } + + return S_OK; +} + +HRESULT UnpatchInterfaceMethods(MethodPatchInfo* patches) { + for (MethodPatchInfo* it = patches; it->index_ != -1; ++it) { + if (it->stub_) { + DCHECK(it->stub_->absolute_target() == + reinterpret_cast<uintptr_t>(it->method_)); + // Modify the stub to just jump directly to the original function. + it->stub_->BypassStub(reinterpret_cast<void*>(it->stub_->argument())); + it->stub_ = NULL; + // Leave the stub in memory so that we won't break any possible chains. + } else { + DLOG(WARNING) << "attempt to unpatch a function that wasn't patched"; + } + } + + return S_OK; +} + +} // namespace vtable_patch diff --git a/chrome_frame/vtable_patch_manager.h b/chrome_frame/vtable_patch_manager.h new file mode 100644 index 0000000..944b9ef --- /dev/null +++ b/chrome_frame/vtable_patch_manager.h @@ -0,0 +1,64 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_FRAME_COMMON_VTABLE_PATCH_MANAGER_H_ +#define CHROME_FRAME_COMMON_VTABLE_PATCH_MANAGER_H_ + +#include <windows.h> + +struct FunctionStub; +// This namespace provides methods to patch VTable methods of COM interfaces. +namespace vtable_patch { + +// This structure represents information about one VTable method. +// We allocate an array of these structures per VTable that we patch to +// remember the original method. We also use this structure to actually +// describe the VTable patch functions +struct MethodPatchInfo { + int index_; + PROC method_; + FunctionStub* stub_; +}; + +// Patches methods in the passed in COM interface. The indexes of the +// methods to patch and the actual patch functions are described in the +// array pointed to by patches. +// @param[in] unknown The pointer of the COM interface to patch +// @param[in] patches An array of MethodPatchInfo structures describing +// the methods to patch and the patch functions. +// The last entry of patches must have index_ set to -1. +HRESULT PatchInterfaceMethods(void* unknown, MethodPatchInfo* patches); + +// Using the patch info provided in |patches| the function goes through the +// list of patched methods and modifies thunks so that they no longer point +// to a hook method but rather go straight through to the original target. +// The thunk itself is not destroyed to support chaining. +// @param[in] patches An array of MethodPatchInfo structures describing +// the methods to patch and the patch functions. +// The last entry of patches must have index_ set to -1. +HRESULT UnpatchInterfaceMethods(MethodPatchInfo* patches); + +} // namespace vtable_patch + +// Begins the declaration of a VTable patch +// @param IFName The name of the interface to patch +#define BEGIN_VTABLE_PATCHES(IFName) \ + vtable_patch::MethodPatchInfo IFName##_PatchInfo[] = { + +// Defines a single method patch in a VTable +// @param index The index of the method to patch +// @param PatchFunction The patch function +#define VTABLE_PATCH_ENTRY(index, PatchFunction) {\ + index, \ + reinterpret_cast<PROC>(PatchFunction), \ + NULL, \ + }, + +// Ends the declaration of a VTable patch by adding an entry with +// index set to -1. +#define END_VTABLE_PATCHES() \ + -1, NULL, NULL \ + }; + +#endif // CHROME_FRAME_COMMON_VTABLE_PATCH_MANAGER_H_ |