diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:30:52 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:30:52 -0800 |
commit | 8e35f3cfc7fba1d1c829dc557ebad6409cbe16a2 (patch) | |
tree | 11425ea0b299d6fb89c6d3618a22d97d5bf68d0f /WebCore/inspector | |
parent | 648161bb0edfc3d43db63caed5cc5213bc6cb78f (diff) | |
download | external_webkit-8e35f3cfc7fba1d1c829dc557ebad6409cbe16a2.zip external_webkit-8e35f3cfc7fba1d1c829dc557ebad6409cbe16a2.tar.gz external_webkit-8e35f3cfc7fba1d1c829dc557ebad6409cbe16a2.tar.bz2 |
auto import from //depot/cupcake/@135843
Diffstat (limited to 'WebCore/inspector')
168 files changed, 24648 insertions, 0 deletions
diff --git a/WebCore/inspector/InspectorClient.h b/WebCore/inspector/InspectorClient.h new file mode 100644 index 0000000..fcbf79d --- /dev/null +++ b/WebCore/inspector/InspectorClient.h @@ -0,0 +1,67 @@ +/* + * 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. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, 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 COMPUTER, 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. + */ + +#ifndef InspectorClient_h +#define InspectorClient_h + +#include "InspectorController.h" + +namespace WebCore { + +class Node; +class Page; +class String; + +class InspectorClient { +public: + virtual ~InspectorClient() { } + + virtual void inspectorDestroyed() = 0; + + virtual Page* createPage() = 0; + + virtual String localizedStringsURL() = 0; + + virtual void showWindow() = 0; + virtual void closeWindow() = 0; + + virtual void attachWindow() = 0; + virtual void detachWindow() = 0; + + virtual void setAttachedWindowHeight(unsigned height) = 0; + + virtual void highlight(Node*) = 0; + virtual void hideHighlight() = 0; + + virtual void inspectedURLChanged(const String& newURL) = 0; + + virtual void populateSetting(const String& key, InspectorController::Setting&) = 0; + virtual void storeSetting(const String& key, const InspectorController::Setting&) = 0; + virtual void removeSetting(const String& key) = 0; +}; + +} // namespace WebCore + +#endif // !defined(InspectorClient_h) diff --git a/WebCore/inspector/InspectorController.cpp b/WebCore/inspector/InspectorController.cpp new file mode 100644 index 0000000..8476b98 --- /dev/null +++ b/WebCore/inspector/InspectorController.cpp @@ -0,0 +1,2841 @@ +/* + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2008 Matt Lilek <webkit@mattlilek.com> + * + * 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. + */ + +#include "config.h" +#include "InspectorController.h" + +#include "CString.h" +#include "CachedResource.h" +#include "Console.h" +#include "DOMWindow.h" +#include "DocLoader.h" +#include "Document.h" +#include "DocumentLoader.h" +#include "Element.h" +#include "FloatConversion.h" +#include "FloatRect.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "FrameTree.h" +#include "FrameView.h" +#include "GraphicsContext.h" +#include "HitTestResult.h" +#include "HTMLFrameOwnerElement.h" +#include "InspectorClient.h" +#include "JSDOMWindow.h" +#include "JSInspectedObjectWrapper.h" +#include "JSInspectorCallbackWrapper.h" +#include "JSNode.h" +#include "JSRange.h" +#include "JavaScriptProfile.h" +#include "Page.h" +#include "Range.h" +#include "ResourceRequest.h" +#include "ResourceResponse.h" +#include "Settings.h" +#include "SharedBuffer.h" +#include "SystemTime.h" +#include "TextEncoding.h" +#include "TextIterator.h" +#include "ScriptController.h" +#include <JavaScriptCore/APICast.h> +#include <JavaScriptCore/JSRetainPtr.h> +#include <JavaScriptCore/JSStringRef.h> +#include <JavaScriptCore/OpaqueJSString.h> +#include <runtime/JSLock.h> +#include <kjs/ustring.h> +#include <runtime/CollectorHeapIterator.h> +#include <profiler/Profile.h> +#include <profiler/Profiler.h> +#include <wtf/RefCounted.h> + +#if ENABLE(DATABASE) +#include "Database.h" +#include "JSDatabase.h" +#endif + +#if ENABLE(JAVASCRIPT_DEBUGGER) +#include "JavaScriptCallFrame.h" +#include "JavaScriptDebugServer.h" +#include "JSJavaScriptCallFrame.h" +#endif + +using namespace JSC; +using namespace std; + +namespace WebCore { + +static const char* const UserInitiatedProfileName = "org.webkit.profiles.user-initiated"; + +static JSRetainPtr<JSStringRef> jsStringRef(const char* str) +{ + return JSRetainPtr<JSStringRef>(Adopt, JSStringCreateWithUTF8CString(str)); +} + +static JSRetainPtr<JSStringRef> jsStringRef(const SourceCode& str) +{ + return JSRetainPtr<JSStringRef>(Adopt, JSStringCreateWithCharacters(str.data(), str.length())); +} + +static JSRetainPtr<JSStringRef> jsStringRef(const String& str) +{ + return JSRetainPtr<JSStringRef>(Adopt, JSStringCreateWithCharacters(str.characters(), str.length())); +} + +static JSRetainPtr<JSStringRef> jsStringRef(const UString& str) +{ + return JSRetainPtr<JSStringRef>(Adopt, OpaqueJSString::create(str).releaseRef()); +} + +static String toString(JSContextRef context, JSValueRef value, JSValueRef* exception) +{ + ASSERT_ARG(value, value); + if (!value) + return String(); + JSRetainPtr<JSStringRef> scriptString(Adopt, JSValueToStringCopy(context, value, exception)); + if (exception && *exception) + return String(); + return String(JSStringGetCharactersPtr(scriptString.get()), JSStringGetLength(scriptString.get())); +} + +#define HANDLE_EXCEPTION(context, exception) handleException((context), (exception), __LINE__) + +JSValueRef InspectorController::callSimpleFunction(JSContextRef context, JSObjectRef thisObject, const char* functionName) const +{ + JSValueRef exception = 0; + return callFunction(context, thisObject, functionName, 0, 0, exception); +} + +JSValueRef InspectorController::callFunction(JSContextRef context, JSObjectRef thisObject, const char* functionName, size_t argumentCount, const JSValueRef arguments[], JSValueRef& exception) const +{ + ASSERT_ARG(context, context); + ASSERT_ARG(thisObject, thisObject); + + if (exception) + return JSValueMakeUndefined(context); + + JSValueRef functionProperty = JSObjectGetProperty(context, thisObject, jsStringRef(functionName).get(), &exception); + if (HANDLE_EXCEPTION(context, exception)) + return JSValueMakeUndefined(context); + + JSObjectRef function = JSValueToObject(context, functionProperty, &exception); + if (HANDLE_EXCEPTION(context, exception)) + return JSValueMakeUndefined(context); + + JSValueRef result = JSObjectCallAsFunction(context, function, thisObject, argumentCount, arguments, &exception); + if (HANDLE_EXCEPTION(context, exception)) + return JSValueMakeUndefined(context); + + return result; +} + +// ConsoleMessage Struct + +struct ConsoleMessage { + ConsoleMessage(MessageSource s, MessageLevel l, const String& m, unsigned li, const String& u, unsigned g) + : source(s) + , level(l) + , message(m) + , line(li) + , url(u) + , groupLevel(g) + , repeatCount(1) + { + } + + ConsoleMessage(MessageSource s, MessageLevel l, ExecState* exec, const ArgList& args, unsigned li, const String& u, unsigned g) + : source(s) + , level(l) + , wrappedArguments(args.size()) + , line(li) + , url(u) + , groupLevel(g) + , repeatCount(1) + { + JSLock lock(false); + for (unsigned i = 0; i < args.size(); ++i) + wrappedArguments[i] = JSInspectedObjectWrapper::wrap(exec, args.at(exec, i)); + } + + bool isEqual(ExecState* exec, ConsoleMessage* msg) const + { + if (msg->wrappedArguments.size() != this->wrappedArguments.size() || + (!exec && msg->wrappedArguments.size())) + return false; + + for (size_t i = 0; i < msg->wrappedArguments.size(); ++i) { + ASSERT_ARG(exec, exec); + if (!JSValueIsEqual(toRef(exec), toRef(msg->wrappedArguments[i].get()), toRef(this->wrappedArguments[i].get()), 0)) + return false; + } + + return msg->source == this->source + && msg->level == this->level + && msg->message == this->message + && msg->line == this->line + && msg->url == this->url + && msg->groupLevel == this->groupLevel; + } + + MessageSource source; + MessageLevel level; + String message; + Vector<ProtectedPtr<JSValue> > wrappedArguments; + unsigned line; + String url; + unsigned groupLevel; + unsigned repeatCount; +}; + +// XMLHttpRequestResource Class + +struct XMLHttpRequestResource { + XMLHttpRequestResource(JSC::UString& sourceString) + { + JSC::JSLock lock(false); + this->sourceString = sourceString.rep(); + } + + ~XMLHttpRequestResource() + { + JSC::JSLock lock(false); + sourceString.clear(); + } + + RefPtr<JSC::UString::Rep> sourceString; +}; + +// InspectorResource Struct + +struct InspectorResource : public RefCounted<InspectorResource> { + // Keep these in sync with WebInspector.Resource.Type + enum Type { + Doc, + Stylesheet, + Image, + Font, + Script, + XHR, + Media, + Other + }; + + static PassRefPtr<InspectorResource> create(long long identifier, DocumentLoader* documentLoader, Frame* frame) + { + return adoptRef(new InspectorResource(identifier, documentLoader, frame)); + } + + ~InspectorResource() + { + setScriptObject(0, 0); + } + + Type type() const + { + if (xmlHttpRequestResource) + return XHR; + + if (requestURL == loader->requestURL()) + return Doc; + + if (loader->frameLoader() && requestURL == loader->frameLoader()->iconURL()) + return Image; + + CachedResource* cachedResource = frame->document()->docLoader()->cachedResource(requestURL.string()); + if (!cachedResource) + return Other; + + switch (cachedResource->type()) { + case CachedResource::ImageResource: + return Image; + case CachedResource::FontResource: + return Font; + case CachedResource::CSSStyleSheet: +#if ENABLE(XSLT) + case CachedResource::XSLStyleSheet: +#endif + return Stylesheet; + case CachedResource::Script: + return Script; + default: + return Other; + } + } + + void setScriptObject(JSContextRef context, JSObjectRef newScriptObject) + { + if (scriptContext && scriptObject) + JSValueUnprotect(scriptContext, scriptObject); + + scriptObject = newScriptObject; + scriptContext = context; + + ASSERT((context && newScriptObject) || (!context && !newScriptObject)); + if (context && newScriptObject) + JSValueProtect(context, newScriptObject); + } + + void setXMLHttpRequestProperties(JSC::UString& data) + { + xmlHttpRequestResource.set(new XMLHttpRequestResource(data)); + } + + String sourceString() const + { + if (xmlHttpRequestResource) + return JSC::UString(xmlHttpRequestResource->sourceString); + + RefPtr<SharedBuffer> buffer; + String textEncodingName; + + if (requestURL == loader->requestURL()) { + buffer = loader->mainResourceData(); + textEncodingName = frame->document()->inputEncoding(); + } else { + CachedResource* cachedResource = frame->document()->docLoader()->cachedResource(requestURL.string()); + if (!cachedResource) + return String(); + + buffer = cachedResource->data(); + textEncodingName = cachedResource->encoding(); + } + + if (!buffer) + return String(); + + TextEncoding encoding(textEncodingName); + if (!encoding.isValid()) + encoding = WindowsLatin1Encoding(); + return encoding.decode(buffer->data(), buffer->size()); + } + + long long identifier; + RefPtr<DocumentLoader> loader; + RefPtr<Frame> frame; + OwnPtr<XMLHttpRequestResource> xmlHttpRequestResource; + KURL requestURL; + HTTPHeaderMap requestHeaderFields; + HTTPHeaderMap responseHeaderFields; + String mimeType; + String suggestedFilename; + JSContextRef scriptContext; + JSObjectRef scriptObject; + long long expectedContentLength; + bool cached; + bool finished; + bool failed; + int length; + int responseStatusCode; + double startTime; + double responseReceivedTime; + double endTime; + +protected: + InspectorResource(long long identifier, DocumentLoader* documentLoader, Frame* frame) + : identifier(identifier) + , loader(documentLoader) + , frame(frame) + , xmlHttpRequestResource(0) + , scriptContext(0) + , scriptObject(0) + , expectedContentLength(0) + , cached(false) + , finished(false) + , failed(false) + , length(0) + , responseStatusCode(0) + , startTime(-1.0) + , responseReceivedTime(-1.0) + , endTime(-1.0) + { + } +}; + +// InspectorDatabaseResource Struct + +#if ENABLE(DATABASE) +struct InspectorDatabaseResource : public RefCounted<InspectorDatabaseResource> { + static PassRefPtr<InspectorDatabaseResource> create(Database* database, const String& domain, const String& name, const String& version) + { + return adoptRef(new InspectorDatabaseResource(database, domain, name, version)); + } + + void setScriptObject(JSContextRef context, JSObjectRef newScriptObject) + { + if (scriptContext && scriptObject) + JSValueUnprotect(scriptContext, scriptObject); + + scriptObject = newScriptObject; + scriptContext = context; + + ASSERT((context && newScriptObject) || (!context && !newScriptObject)); + if (context && newScriptObject) + JSValueProtect(context, newScriptObject); + } + + RefPtr<Database> database; + String domain; + String name; + String version; + JSContextRef scriptContext; + JSObjectRef scriptObject; + +private: + InspectorDatabaseResource(Database* database, const String& domain, const String& name, const String& version) + : database(database) + , domain(domain) + , name(name) + , version(version) + , scriptContext(0) + , scriptObject(0) + { + } +}; +#endif + +// JavaScript Callbacks + +#define SIMPLE_INSPECTOR_CALLBACK(jsFunction, inspectorControllerMethod) \ +static JSValueRef jsFunction(JSContextRef ctx, JSObjectRef, JSObjectRef thisObject, size_t, const JSValueRef[], JSValueRef*) \ +{ \ + if (InspectorController* controller = reinterpret_cast<InspectorController*>(JSObjectGetPrivate(thisObject))) \ + controller->inspectorControllerMethod(); \ + return JSValueMakeUndefined(ctx); \ +} + +SIMPLE_INSPECTOR_CALLBACK(hideDOMNodeHighlight, hideHighlight); +SIMPLE_INSPECTOR_CALLBACK(loaded, scriptObjectReady); +SIMPLE_INSPECTOR_CALLBACK(unloading, close); +SIMPLE_INSPECTOR_CALLBACK(attach, attachWindow); +SIMPLE_INSPECTOR_CALLBACK(detach, detachWindow); +#if ENABLE(JAVASCRIPT_DEBUGGER) +SIMPLE_INSPECTOR_CALLBACK(enableDebugger, enableDebugger); +SIMPLE_INSPECTOR_CALLBACK(disableDebugger, disableDebugger); +SIMPLE_INSPECTOR_CALLBACK(pauseInDebugger, pauseInDebugger); +SIMPLE_INSPECTOR_CALLBACK(resumeDebugger, resumeDebugger); +SIMPLE_INSPECTOR_CALLBACK(stepOverStatementInDebugger, stepOverStatementInDebugger); +SIMPLE_INSPECTOR_CALLBACK(stepIntoStatementInDebugger, stepIntoStatementInDebugger); +SIMPLE_INSPECTOR_CALLBACK(stepOutOfFunctionInDebugger, stepOutOfFunctionInDebugger); +#endif +SIMPLE_INSPECTOR_CALLBACK(closeWindow, closeWindow); +SIMPLE_INSPECTOR_CALLBACK(clearMessages, clearConsoleMessages); +SIMPLE_INSPECTOR_CALLBACK(startProfiling, startUserInitiatedProfilingSoon); +SIMPLE_INSPECTOR_CALLBACK(stopProfiling, stopUserInitiatedProfiling); +SIMPLE_INSPECTOR_CALLBACK(enableProfiler, enableProfiler); +SIMPLE_INSPECTOR_CALLBACK(disableProfiler, disableProfiler); +SIMPLE_INSPECTOR_CALLBACK(toggleNodeSearch, toggleSearchForNodeInPage); + +#define BOOL_INSPECTOR_CALLBACK(jsFunction, inspectorControllerMethod) \ +static JSValueRef jsFunction(JSContextRef ctx, JSObjectRef, JSObjectRef thisObject, size_t, const JSValueRef[], JSValueRef*) \ +{ \ + if (InspectorController* controller = reinterpret_cast<InspectorController*>(JSObjectGetPrivate(thisObject))) \ + return JSValueMakeBoolean(ctx, controller->inspectorControllerMethod()); \ + return JSValueMakeUndefined(ctx); \ +} + +#if ENABLE(JAVASCRIPT_DEBUGGER) +BOOL_INSPECTOR_CALLBACK(debuggerEnabled, debuggerEnabled); +BOOL_INSPECTOR_CALLBACK(pauseOnExceptions, pauseOnExceptions); +#endif +BOOL_INSPECTOR_CALLBACK(profilerEnabled, profilerEnabled); +BOOL_INSPECTOR_CALLBACK(isWindowVisible, windowVisible); +BOOL_INSPECTOR_CALLBACK(searchingForNode, searchingForNodeInPage); + +static bool addSourceToFrame(const String& mimeType, const String& source, Node* frameNode) +{ + ASSERT_ARG(frameNode, frameNode); + + if (!frameNode) + return false; + + if (!frameNode->attached()) { + ASSERT_NOT_REACHED(); + return false; + } + + ASSERT(frameNode->isElementNode()); + if (!frameNode->isElementNode()) + return false; + + Element* element = static_cast<Element*>(frameNode); + ASSERT(element->isFrameOwnerElement()); + if (!element->isFrameOwnerElement()) + return false; + + HTMLFrameOwnerElement* frameOwner = static_cast<HTMLFrameOwnerElement*>(element); + ASSERT(frameOwner->contentFrame()); + if (!frameOwner->contentFrame()) + return false; + + FrameLoader* loader = frameOwner->contentFrame()->loader(); + + loader->setResponseMIMEType(mimeType); + loader->begin(); + loader->write(source); + loader->end(); + + return true; +} + +static JSValueRef addResourceSourceToFrame(JSContextRef ctx, JSObjectRef /*function*/, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) +{ + JSValueRef undefined = JSValueMakeUndefined(ctx); + + InspectorController* controller = reinterpret_cast<InspectorController*>(JSObjectGetPrivate(thisObject)); + if (argumentCount < 2 || !controller) + return undefined; + + JSValueRef identifierValue = arguments[0]; + if (!JSValueIsNumber(ctx, identifierValue)) + return undefined; + + long long identifier = static_cast<long long>(JSValueToNumber(ctx, identifierValue, exception)); + if (exception && *exception) + return undefined; + + RefPtr<InspectorResource> resource = controller->resources().get(identifier); + ASSERT(resource); + if (!resource) + return undefined; + + String sourceString = resource->sourceString(); + if (sourceString.isEmpty()) + return undefined; + + bool successfullyAddedSource = addSourceToFrame(resource->mimeType, sourceString, toNode(toJS(arguments[1]))); + return JSValueMakeBoolean(ctx, successfullyAddedSource); +} + +static JSValueRef addSourceToFrame(JSContextRef ctx, JSObjectRef /*function*/, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) +{ + JSValueRef undefined = JSValueMakeUndefined(ctx); + + InspectorController* controller = reinterpret_cast<InspectorController*>(JSObjectGetPrivate(thisObject)); + if (argumentCount < 3 || !controller) + return undefined; + + JSValueRef mimeTypeValue = arguments[0]; + if (!JSValueIsString(ctx, mimeTypeValue)) + return undefined; + + JSValueRef sourceValue = arguments[1]; + if (!JSValueIsString(ctx, sourceValue)) + return undefined; + + String mimeType = toString(ctx, mimeTypeValue, exception); + if (mimeType.isEmpty()) + return undefined; + + String source = toString(ctx, sourceValue, exception); + if (source.isEmpty()) + return undefined; + + bool successfullyAddedSource = addSourceToFrame(mimeType, source, toNode(toJS(arguments[2]))); + return JSValueMakeBoolean(ctx, successfullyAddedSource); +} + +static JSValueRef getResourceDocumentNode(JSContextRef ctx, JSObjectRef /*function*/, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) +{ + JSValueRef undefined = JSValueMakeUndefined(ctx); + + InspectorController* controller = reinterpret_cast<InspectorController*>(JSObjectGetPrivate(thisObject)); + if (!argumentCount || argumentCount > 1 || !controller) + return undefined; + + JSValueRef identifierValue = arguments[0]; + if (!JSValueIsNumber(ctx, identifierValue)) + return undefined; + + long long identifier = static_cast<long long>(JSValueToNumber(ctx, identifierValue, exception)); + if (exception && *exception) + return undefined; + + RefPtr<InspectorResource> resource = controller->resources().get(identifier); + ASSERT(resource); + if (!resource) + return undefined; + + Frame* frame = resource->frame.get(); + + Document* document = frame->document(); + if (!document) + return undefined; + + if (document->isPluginDocument() || document->isImageDocument() || document->isMediaDocument()) + return undefined; + + ExecState* exec = toJSDOMWindowShell(resource->frame.get())->window()->globalExec(); + + JSC::JSLock lock(false); + JSValueRef documentValue = toRef(JSInspectedObjectWrapper::wrap(exec, toJS(exec, document))); + return documentValue; +} + +static JSValueRef highlightDOMNode(JSContextRef context, JSObjectRef /*function*/, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* /*exception*/) +{ + JSValueRef undefined = JSValueMakeUndefined(context); + + InspectorController* controller = reinterpret_cast<InspectorController*>(JSObjectGetPrivate(thisObject)); + if (argumentCount < 1 || !controller) + return undefined; + + JSQuarantinedObjectWrapper* wrapper = JSQuarantinedObjectWrapper::asWrapper(toJS(arguments[0])); + if (!wrapper) + return undefined; + Node* node = toNode(wrapper->unwrappedObject()); + if (!node) + return undefined; + + controller->highlight(node); + + return undefined; +} + +static JSValueRef search(JSContextRef ctx, JSObjectRef /*function*/, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) +{ + InspectorController* controller = reinterpret_cast<InspectorController*>(JSObjectGetPrivate(thisObject)); + if (!controller) + return JSValueMakeUndefined(ctx); + + if (argumentCount < 2 || !JSValueIsString(ctx, arguments[1])) + return JSValueMakeUndefined(ctx); + + Node* node = toNode(toJS(arguments[0])); + if (!node) + return JSValueMakeUndefined(ctx); + + String target = toString(ctx, arguments[1], exception); + + JSObjectRef global = JSContextGetGlobalObject(ctx); + + JSValueRef arrayProperty = JSObjectGetProperty(ctx, global, jsStringRef("Array").get(), exception); + if (exception && *exception) + return JSValueMakeUndefined(ctx); + + JSObjectRef arrayConstructor = JSValueToObject(ctx, arrayProperty, exception); + if (exception && *exception) + return JSValueMakeUndefined(ctx); + + JSObjectRef result = JSObjectCallAsConstructor(ctx, arrayConstructor, 0, 0, exception); + if (exception && *exception) + return JSValueMakeUndefined(ctx); + + JSValueRef pushProperty = JSObjectGetProperty(ctx, result, jsStringRef("push").get(), exception); + if (exception && *exception) + return JSValueMakeUndefined(ctx); + + JSObjectRef pushFunction = JSValueToObject(ctx, pushProperty, exception); + if (exception && *exception) + return JSValueMakeUndefined(ctx); + + RefPtr<Range> searchRange(rangeOfContents(node)); + + ExceptionCode ec = 0; + do { + RefPtr<Range> resultRange(findPlainText(searchRange.get(), target, true, false)); + if (resultRange->collapsed(ec)) + break; + + // A non-collapsed result range can in some funky whitespace cases still not + // advance the range's start position (4509328). Break to avoid infinite loop. + VisiblePosition newStart = endVisiblePosition(resultRange.get(), DOWNSTREAM); + if (newStart == startVisiblePosition(searchRange.get(), DOWNSTREAM)) + break; + + JSC::JSLock lock(false); + JSValueRef arg0 = toRef(toJS(toJS(ctx), resultRange.get())); + JSObjectCallAsFunction(ctx, pushFunction, result, 1, &arg0, exception); + if (exception && *exception) + return JSValueMakeUndefined(ctx); + + setStart(searchRange.get(), newStart); + } while (true); + + return result; +} + +#if ENABLE(DATABASE) +static JSValueRef databaseTableNames(JSContextRef ctx, JSObjectRef /*function*/, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) +{ + InspectorController* controller = reinterpret_cast<InspectorController*>(JSObjectGetPrivate(thisObject)); + if (!controller) + return JSValueMakeUndefined(ctx); + + if (argumentCount < 1) + return JSValueMakeUndefined(ctx); + + JSQuarantinedObjectWrapper* wrapper = JSQuarantinedObjectWrapper::asWrapper(toJS(arguments[0])); + if (!wrapper) + return JSValueMakeUndefined(ctx); + + Database* database = toDatabase(wrapper->unwrappedObject()); + if (!database) + return JSValueMakeUndefined(ctx); + + JSObjectRef global = JSContextGetGlobalObject(ctx); + + JSValueRef arrayProperty = JSObjectGetProperty(ctx, global, jsStringRef("Array").get(), exception); + if (exception && *exception) + return JSValueMakeUndefined(ctx); + + JSObjectRef arrayConstructor = JSValueToObject(ctx, arrayProperty, exception); + if (exception && *exception) + return JSValueMakeUndefined(ctx); + + JSObjectRef result = JSObjectCallAsConstructor(ctx, arrayConstructor, 0, 0, exception); + if (exception && *exception) + return JSValueMakeUndefined(ctx); + + JSValueRef pushProperty = JSObjectGetProperty(ctx, result, jsStringRef("push").get(), exception); + if (exception && *exception) + return JSValueMakeUndefined(ctx); + + JSObjectRef pushFunction = JSValueToObject(ctx, pushProperty, exception); + if (exception && *exception) + return JSValueMakeUndefined(ctx); + + Vector<String> tableNames = database->tableNames(); + unsigned length = tableNames.size(); + for (unsigned i = 0; i < length; ++i) { + String tableName = tableNames[i]; + JSValueRef tableNameValue = JSValueMakeString(ctx, jsStringRef(tableName).get()); + + JSValueRef pushArguments[] = { tableNameValue }; + JSObjectCallAsFunction(ctx, pushFunction, result, 1, pushArguments, exception); + if (exception && *exception) + return JSValueMakeUndefined(ctx); + } + + return result; +} +#endif + +static JSValueRef inspectedWindow(JSContextRef ctx, JSObjectRef /*function*/, JSObjectRef thisObject, size_t /*argumentCount*/, const JSValueRef[] /*arguments[]*/, JSValueRef* /*exception*/) +{ + InspectorController* controller = reinterpret_cast<InspectorController*>(JSObjectGetPrivate(thisObject)); + if (!controller) + return JSValueMakeUndefined(ctx); + + JSDOMWindow* inspectedWindow = toJSDOMWindow(controller->inspectedPage()->mainFrame()); + JSLock lock(false); + return toRef(JSInspectedObjectWrapper::wrap(inspectedWindow->globalExec(), inspectedWindow)); +} + +static JSValueRef setting(JSContextRef ctx, JSObjectRef /*function*/, JSObjectRef thisObject, size_t /*argumentCount*/, const JSValueRef arguments[], JSValueRef* exception) +{ + InspectorController* controller = reinterpret_cast<InspectorController*>(JSObjectGetPrivate(thisObject)); + if (!controller) + return JSValueMakeUndefined(ctx); + + JSValueRef keyValue = arguments[0]; + if (!JSValueIsString(ctx, keyValue)) + return JSValueMakeUndefined(ctx); + + const InspectorController::Setting& setting = controller->setting(toString(ctx, keyValue, exception)); + + switch (setting.type()) { + default: + case InspectorController::Setting::NoType: + return JSValueMakeUndefined(ctx); + case InspectorController::Setting::StringType: + return JSValueMakeString(ctx, jsStringRef(setting.string()).get()); + case InspectorController::Setting::DoubleType: + return JSValueMakeNumber(ctx, setting.doubleValue()); + case InspectorController::Setting::IntegerType: + return JSValueMakeNumber(ctx, setting.integerValue()); + case InspectorController::Setting::BooleanType: + return JSValueMakeBoolean(ctx, setting.booleanValue()); + case InspectorController::Setting::StringVectorType: { + Vector<JSValueRef> stringValues; + const Vector<String>& strings = setting.stringVector(); + const unsigned length = strings.size(); + for (unsigned i = 0; i < length; ++i) + stringValues.append(JSValueMakeString(ctx, jsStringRef(strings[i]).get())); + + JSObjectRef stringsArray = JSObjectMakeArray(ctx, stringValues.size(), stringValues.data(), exception); + if (exception && *exception) + return JSValueMakeUndefined(ctx); + return stringsArray; + } + } +} + +static JSValueRef setSetting(JSContextRef ctx, JSObjectRef /*function*/, JSObjectRef thisObject, size_t /*argumentCount*/, const JSValueRef arguments[], JSValueRef* exception) +{ + InspectorController* controller = reinterpret_cast<InspectorController*>(JSObjectGetPrivate(thisObject)); + if (!controller) + return JSValueMakeUndefined(ctx); + + JSValueRef keyValue = arguments[0]; + if (!JSValueIsString(ctx, keyValue)) + return JSValueMakeUndefined(ctx); + + InspectorController::Setting setting; + + JSValueRef value = arguments[1]; + switch (JSValueGetType(ctx, value)) { + default: + case kJSTypeUndefined: + case kJSTypeNull: + // Do nothing. The setting is already NoType. + ASSERT(setting.type() == InspectorController::Setting::NoType); + break; + case kJSTypeString: + setting.set(toString(ctx, value, exception)); + break; + case kJSTypeNumber: + setting.set(JSValueToNumber(ctx, value, exception)); + break; + case kJSTypeBoolean: + setting.set(JSValueToBoolean(ctx, value)); + break; + case kJSTypeObject: { + JSObjectRef object = JSValueToObject(ctx, value, 0); + JSValueRef lengthValue = JSObjectGetProperty(ctx, object, jsStringRef("length").get(), exception); + if (exception && *exception) + return JSValueMakeUndefined(ctx); + + Vector<String> strings; + const unsigned length = static_cast<unsigned>(JSValueToNumber(ctx, lengthValue, 0)); + for (unsigned i = 0; i < length; ++i) { + JSValueRef itemValue = JSObjectGetPropertyAtIndex(ctx, object, i, exception); + if (exception && *exception) + return JSValueMakeUndefined(ctx); + strings.append(toString(ctx, itemValue, exception)); + if (exception && *exception) + return JSValueMakeUndefined(ctx); + } + + setting.set(strings); + break; + } + } + + if (exception && *exception) + return JSValueMakeUndefined(ctx); + + controller->setSetting(toString(ctx, keyValue, exception), setting); + + return JSValueMakeUndefined(ctx); +} + +static JSValueRef localizedStrings(JSContextRef ctx, JSObjectRef /*function*/, JSObjectRef thisObject, size_t /*argumentCount*/, const JSValueRef[] /*arguments[]*/, JSValueRef* /*exception*/) +{ + InspectorController* controller = reinterpret_cast<InspectorController*>(JSObjectGetPrivate(thisObject)); + if (!controller) + return JSValueMakeUndefined(ctx); + + String url = controller->localizedStringsURL(); + if (url.isNull()) + return JSValueMakeNull(ctx); + + return JSValueMakeString(ctx, jsStringRef(url).get()); +} + +static JSValueRef platform(JSContextRef ctx, JSObjectRef /*function*/, JSObjectRef thisObject, size_t /*argumentCount*/, const JSValueRef[] /*arguments[]*/, JSValueRef* /*exception*/) +{ +#if PLATFORM(MAC) +#ifdef BUILDING_ON_TIGER + static const String platform = "mac-tiger"; +#else + static const String platform = "mac-leopard"; +#endif +#elif PLATFORM(WIN_OS) + static const String platform = "windows"; +#elif PLATFORM(QT) + static const String platform = "qt"; +#elif PLATFORM(GTK) + static const String platform = "gtk"; +#elif PLATFORM(WX) + static const String platform = "wx"; +#else + static const String platform = "unknown"; +#endif + + JSValueRef platformValue = JSValueMakeString(ctx, jsStringRef(platform).get()); + + return platformValue; +} + +static JSValueRef moveByUnrestricted(JSContextRef ctx, JSObjectRef /*function*/, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) +{ + InspectorController* controller = reinterpret_cast<InspectorController*>(JSObjectGetPrivate(thisObject)); + if (!controller) + return JSValueMakeUndefined(ctx); + + if (argumentCount < 2) + return JSValueMakeUndefined(ctx); + + double x = JSValueToNumber(ctx, arguments[0], exception); + if (exception && *exception) + return JSValueMakeUndefined(ctx); + + double y = JSValueToNumber(ctx, arguments[1], exception); + if (exception && *exception) + return JSValueMakeUndefined(ctx); + + controller->moveWindowBy(narrowPrecisionToFloat(x), narrowPrecisionToFloat(y)); + + return JSValueMakeUndefined(ctx); +} + +static JSValueRef setAttachedWindowHeight(JSContextRef ctx, JSObjectRef /*function*/, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) +{ + InspectorController* controller = reinterpret_cast<InspectorController*>(JSObjectGetPrivate(thisObject)); + if (!controller) + return JSValueMakeUndefined(ctx); + + if (argumentCount < 1) + return JSValueMakeUndefined(ctx); + + unsigned height = static_cast<unsigned>(JSValueToNumber(ctx, arguments[0], exception)); + if (exception && *exception) + return JSValueMakeUndefined(ctx); + + controller->setAttachedWindowHeight(height); + + return JSValueMakeUndefined(ctx); +} + +static JSValueRef wrapCallback(JSContextRef ctx, JSObjectRef /*function*/, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) +{ + InspectorController* controller = reinterpret_cast<InspectorController*>(JSObjectGetPrivate(thisObject)); + if (!controller) + return JSValueMakeUndefined(ctx); + + if (argumentCount < 1) + return JSValueMakeUndefined(ctx); + + JSLock lock(false); + return toRef(JSInspectorCallbackWrapper::wrap(toJS(ctx), toJS(arguments[0]))); +} + +#if ENABLE(JAVASCRIPT_DEBUGGER) +static JSValueRef currentCallFrame(JSContextRef ctx, JSObjectRef /*function*/, JSObjectRef thisObject, size_t /*argumentCount*/, const JSValueRef[] /*arguments*/, JSValueRef* /*exception*/) +{ + InspectorController* controller = reinterpret_cast<InspectorController*>(JSObjectGetPrivate(thisObject)); + if (!controller) + return JSValueMakeUndefined(ctx); + + JavaScriptCallFrame* callFrame = controller->currentCallFrame(); + if (!callFrame || !callFrame->isValid()) + return JSValueMakeNull(ctx); + + ExecState* globalExec = callFrame->scopeChain()->globalObject()->globalExec(); + + JSLock lock(false); + return toRef(JSInspectedObjectWrapper::wrap(globalExec, toJS(toJS(ctx), callFrame))); +} + +static JSValueRef setPauseOnExceptions(JSContextRef ctx, JSObjectRef /*function*/, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* /*exception*/) +{ + InspectorController* controller = reinterpret_cast<InspectorController*>(JSObjectGetPrivate(thisObject)); + if (!controller) + return JSValueMakeUndefined(ctx); + + if (argumentCount < 1) + return JSValueMakeUndefined(ctx); + + controller->setPauseOnExceptions(JSValueToBoolean(ctx, arguments[0])); + + return JSValueMakeUndefined(ctx); +} + +static JSValueRef addBreakpoint(JSContextRef ctx, JSObjectRef /*function*/, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) +{ + InspectorController* controller = reinterpret_cast<InspectorController*>(JSObjectGetPrivate(thisObject)); + if (!controller) + return JSValueMakeUndefined(ctx); + + if (argumentCount < 2) + return JSValueMakeUndefined(ctx); + + double sourceID = JSValueToNumber(ctx, arguments[0], exception); + if (exception && *exception) + return JSValueMakeUndefined(ctx); + + double lineNumber = JSValueToNumber(ctx, arguments[1], exception); + if (exception && *exception) + return JSValueMakeUndefined(ctx); + + controller->addBreakpoint(static_cast<int>(sourceID), static_cast<unsigned>(lineNumber)); + + return JSValueMakeUndefined(ctx); +} + +static JSValueRef removeBreakpoint(JSContextRef ctx, JSObjectRef /*function*/, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) +{ + InspectorController* controller = reinterpret_cast<InspectorController*>(JSObjectGetPrivate(thisObject)); + if (!controller) + return JSValueMakeUndefined(ctx); + + if (argumentCount < 2) + return JSValueMakeUndefined(ctx); + + double sourceID = JSValueToNumber(ctx, arguments[0], exception); + if (exception && *exception) + return JSValueMakeUndefined(ctx); + + double lineNumber = JSValueToNumber(ctx, arguments[1], exception); + if (exception && *exception) + return JSValueMakeUndefined(ctx); + + controller->removeBreakpoint(static_cast<int>(sourceID), static_cast<unsigned>(lineNumber)); + + return JSValueMakeUndefined(ctx); +} +#endif + +static JSValueRef profiles(JSContextRef ctx, JSObjectRef /*function*/, JSObjectRef thisObject, size_t /*argumentCount*/, const JSValueRef[] /*arguments*/, JSValueRef* exception) +{ + InspectorController* controller = reinterpret_cast<InspectorController*>(JSObjectGetPrivate(thisObject)); + if (!controller) + return JSValueMakeUndefined(ctx); + + JSLock lock(false); + + const Vector<RefPtr<Profile> >& profiles = controller->profiles(); + + JSObjectRef global = JSContextGetGlobalObject(ctx); + + JSValueRef arrayProperty = JSObjectGetProperty(ctx, global, jsStringRef("Array").get(), exception); + if (exception && *exception) + return JSValueMakeUndefined(ctx); + + JSObjectRef arrayConstructor = JSValueToObject(ctx, arrayProperty, exception); + if (exception && *exception) + return JSValueMakeUndefined(ctx); + + JSObjectRef result = JSObjectCallAsConstructor(ctx, arrayConstructor, 0, 0, exception); + if (exception && *exception) + return JSValueMakeUndefined(ctx); + + JSValueRef pushProperty = JSObjectGetProperty(ctx, result, jsStringRef("push").get(), exception); + if (exception && *exception) + return JSValueMakeUndefined(ctx); + + JSObjectRef pushFunction = JSValueToObject(ctx, pushProperty, exception); + if (exception && *exception) + return JSValueMakeUndefined(ctx); + + for (size_t i = 0; i < profiles.size(); ++i) { + JSValueRef arg0 = toRef(toJS(toJS(ctx), profiles[i].get())); + JSObjectCallAsFunction(ctx, pushFunction, result, 1, &arg0, exception); + if (exception && *exception) + return JSValueMakeUndefined(ctx); + } + + return result; +} + +// InspectorController Class + +static unsigned s_inspectorControllerCount; +static HashMap<String, InspectorController::Setting*>* s_settingCache; + +InspectorController::InspectorController(Page* page, InspectorClient* client) + : m_inspectedPage(page) + , m_client(client) + , m_page(0) + , m_scriptObject(0) + , m_controllerScriptObject(0) + , m_scriptContext(0) + , m_windowVisible(false) +#if ENABLE(JAVASCRIPT_DEBUGGER) + , m_debuggerEnabled(false) + , m_attachDebuggerWhenShown(false) +#endif + , m_profilerEnabled(false) + , m_recordingUserInitiatedProfile(false) + , m_showAfterVisible(ElementsPanel) + , m_nextIdentifier(-2) + , m_groupLevel(0) + , m_searchingForNode(false) + , m_currentUserInitiatedProfileNumber(-1) + , m_nextUserInitiatedProfileNumber(1) + , m_previousMessage(0) + , m_startProfiling(this, &InspectorController::startUserInitiatedProfiling) +{ + ASSERT_ARG(page, page); + ASSERT_ARG(client, client); + ++s_inspectorControllerCount; +} + +InspectorController::~InspectorController() +{ + m_client->inspectorDestroyed(); + + if (m_scriptContext) { + JSValueRef exception = 0; + + JSObjectRef global = JSContextGetGlobalObject(m_scriptContext); + JSValueRef controllerProperty = JSObjectGetProperty(m_scriptContext, global, jsStringRef("InspectorController").get(), &exception); + if (!HANDLE_EXCEPTION(m_scriptContext, exception)) { + if (JSObjectRef controller = JSValueToObject(m_scriptContext, controllerProperty, &exception)) { + if (!HANDLE_EXCEPTION(m_scriptContext, exception)) + JSObjectSetPrivate(controller, 0); + } + } + } + + if (m_page) + m_page->setParentInspectorController(0); + + // m_inspectedPage should have been cleared in inspectedPageDestroyed(). + ASSERT(!m_inspectedPage); + + deleteAllValues(m_frameResources); + deleteAllValues(m_consoleMessages); + + ASSERT(s_inspectorControllerCount); + --s_inspectorControllerCount; + + if (!s_inspectorControllerCount && s_settingCache) { + deleteAllValues(*s_settingCache); + delete s_settingCache; + s_settingCache = 0; + } +} + +void InspectorController::inspectedPageDestroyed() +{ + close(); + + ASSERT(m_inspectedPage); + m_inspectedPage = 0; +} + +bool InspectorController::enabled() const +{ + if (!m_inspectedPage) + return false; + return m_inspectedPage->settings()->developerExtrasEnabled(); +} + +const InspectorController::Setting& InspectorController::setting(const String& key) const +{ + if (!s_settingCache) + s_settingCache = new HashMap<String, Setting*>; + + if (Setting* cachedSetting = s_settingCache->get(key)) + return *cachedSetting; + + Setting* newSetting = new Setting; + s_settingCache->set(key, newSetting); + + m_client->populateSetting(key, *newSetting); + + return *newSetting; +} + +void InspectorController::setSetting(const String& key, const Setting& setting) +{ + if (setting.type() == Setting::NoType) { + if (s_settingCache) { + Setting* cachedSetting = s_settingCache->get(key); + if (cachedSetting) { + s_settingCache->remove(key); + delete cachedSetting; + } + } + + m_client->removeSetting(key); + return; + } + + if (!s_settingCache) + s_settingCache = new HashMap<String, Setting*>; + + if (Setting* cachedSetting = s_settingCache->get(key)) + *cachedSetting = setting; + else + s_settingCache->set(key, new Setting(setting)); + + m_client->storeSetting(key, setting); +} + +String InspectorController::localizedStringsURL() +{ + if (!enabled()) + return String(); + return m_client->localizedStringsURL(); +} + +// Trying to inspect something in a frame with JavaScript disabled would later lead to +// crashes trying to create JavaScript wrappers. Some day we could fix this issue, but +// for now prevent crashes here by never targeting a node in such a frame. +static bool canPassNodeToJavaScript(Node* node) +{ + if (!node) + return false; + Frame* frame = node->document()->frame(); + return frame && frame->script()->isEnabled(); +} + +void InspectorController::inspect(Node* node) +{ + if (!canPassNodeToJavaScript(node) || !enabled()) + return; + + show(); + + if (node->nodeType() != Node::ELEMENT_NODE && node->nodeType() != Node::DOCUMENT_NODE) + node = node->parentNode(); + m_nodeToFocus = node; + + if (!m_scriptObject) { + m_showAfterVisible = ElementsPanel; + return; + } + + if (windowVisible()) + focusNode(); +} + +void InspectorController::focusNode() +{ + if (!enabled()) + return; + + ASSERT(m_scriptContext); + ASSERT(m_scriptObject); + ASSERT(m_nodeToFocus); + + Frame* frame = m_nodeToFocus->document()->frame(); + if (!frame) + return; + + ExecState* exec = toJSDOMWindow(frame)->globalExec(); + + JSValueRef arg0; + + { + JSC::JSLock lock(false); + arg0 = toRef(JSInspectedObjectWrapper::wrap(exec, toJS(exec, m_nodeToFocus.get()))); + } + + m_nodeToFocus = 0; + + JSValueRef exception = 0; + callFunction(m_scriptContext, m_scriptObject, "updateFocusedNode", 1, &arg0, exception); +} + +void InspectorController::highlight(Node* node) +{ + if (!enabled()) + return; + ASSERT_ARG(node, node); + m_highlightedNode = node; + m_client->highlight(node); +} + +void InspectorController::hideHighlight() +{ + if (!enabled()) + return; + m_client->hideHighlight(); +} + +bool InspectorController::windowVisible() +{ + return m_windowVisible; +} + +void InspectorController::setWindowVisible(bool visible, bool attached) +{ + if (visible == m_windowVisible) + return; + + m_windowVisible = visible; + + if (!m_scriptContext || !m_scriptObject) + return; + + if (m_windowVisible) { + setAttachedWindow(attached); + populateScriptObjects(); + if (m_nodeToFocus) + focusNode(); +#if ENABLE(JAVASCRIPT_DEBUGGER) + if (m_attachDebuggerWhenShown) + enableDebugger(); +#endif + if (m_showAfterVisible != CurrentPanel) + showPanel(m_showAfterVisible); + } else { +#if ENABLE(JAVASCRIPT_DEBUGGER) + disableDebugger(); +#endif + resetScriptObjects(); + } + + m_showAfterVisible = CurrentPanel; +} + +void InspectorController::addMessageToConsole(MessageSource source, MessageLevel level, ExecState* exec, const ArgList& arguments, unsigned lineNumber, const String& sourceURL) +{ + if (!enabled()) + return; + + addConsoleMessage(exec, new ConsoleMessage(source, level, exec, arguments, lineNumber, sourceURL, m_groupLevel)); +} + +void InspectorController::addMessageToConsole(MessageSource source, MessageLevel level, const String& message, unsigned lineNumber, const String& sourceID) +{ + if (!enabled()) + return; + + addConsoleMessage(0, new ConsoleMessage(source, level, message, lineNumber, sourceID, m_groupLevel)); +} + +void InspectorController::addConsoleMessage(ExecState* exec, ConsoleMessage* consoleMessage) +{ + ASSERT(enabled()); + ASSERT_ARG(consoleMessage, consoleMessage); + + if (m_previousMessage && m_previousMessage->isEqual(exec, consoleMessage)) { + ++m_previousMessage->repeatCount; + delete consoleMessage; + } else { + m_previousMessage = consoleMessage; + m_consoleMessages.append(consoleMessage); + } + + if (windowVisible()) + addScriptConsoleMessage(m_previousMessage); +} + +void InspectorController::clearConsoleMessages() +{ + deleteAllValues(m_consoleMessages); + m_consoleMessages.clear(); + m_previousMessage = 0; + m_groupLevel = 0; +} + +void InspectorController::toggleRecordButton(bool isProfiling) +{ + if (!m_scriptContext) + return; + + JSValueRef exception = 0; + JSValueRef isProvingValue = JSValueMakeBoolean(m_scriptContext, isProfiling); + callFunction(m_scriptContext, m_scriptObject, "setRecordingProfile", 1, &isProvingValue, exception); +} + +void InspectorController::startGroup(MessageSource source, ExecState* exec, const ArgList& arguments, unsigned lineNumber, const String& sourceURL) +{ + ++m_groupLevel; + + addConsoleMessage(exec, new ConsoleMessage(source, StartGroupMessageLevel, exec, arguments, lineNumber, sourceURL, m_groupLevel)); +} + +void InspectorController::endGroup(MessageSource source, unsigned lineNumber, const String& sourceURL) +{ + if (m_groupLevel == 0) + return; + + --m_groupLevel; + + addConsoleMessage(0, new ConsoleMessage(source, EndGroupMessageLevel, String(), lineNumber, sourceURL, m_groupLevel)); +} + +void InspectorController::addProfile(PassRefPtr<Profile> prpProfile, unsigned lineNumber, const UString& sourceURL) +{ + if (!enabled()) + return; + + RefPtr<Profile> profile = prpProfile; + m_profiles.append(profile); + + if (windowVisible()) + addScriptProfile(profile.get()); + + addProfileMessageToConsole(profile, lineNumber, sourceURL); +} + +void InspectorController::addProfileMessageToConsole(PassRefPtr<Profile> prpProfile, unsigned lineNumber, const UString& sourceURL) +{ + RefPtr<Profile> profile = prpProfile; + + UString message = "Profile \"webkit-profile://"; + message += encodeWithURLEscapeSequences(profile->title()); + message += "/"; + message += UString::from(profile->uid()); + message += "\" finished."; + addMessageToConsole(JSMessageSource, LogMessageLevel, message, lineNumber, sourceURL); +} + +void InspectorController::attachWindow() +{ + if (!enabled()) + return; + m_client->attachWindow(); +} + +void InspectorController::detachWindow() +{ + if (!enabled()) + return; + m_client->detachWindow(); +} + +void InspectorController::setAttachedWindow(bool attached) +{ + if (!enabled() || !m_scriptContext || !m_scriptObject) + return; + + JSValueRef attachedValue = JSValueMakeBoolean(m_scriptContext, attached); + + JSValueRef exception = 0; + callFunction(m_scriptContext, m_scriptObject, "setAttachedWindow", 1, &attachedValue, exception); +} + +void InspectorController::setAttachedWindowHeight(unsigned height) +{ + if (!enabled()) + return; + m_client->setAttachedWindowHeight(height); +} + +void InspectorController::toggleSearchForNodeInPage() +{ + if (!enabled()) + return; + + m_searchingForNode = !m_searchingForNode; + if (!m_searchingForNode) + hideHighlight(); +} + +void InspectorController::mouseDidMoveOverElement(const HitTestResult& result, unsigned modifierFlags) +{ + if (!enabled() || !m_searchingForNode) + return; + + Node* node = result.innerNode(); + if (node) + highlight(node); +} + +void InspectorController::handleMousePressOnNode(Node* node) +{ + if (!enabled()) + return; + + ASSERT(m_searchingForNode); + ASSERT(node); + if (!node) + return; + + // inspect() will implicitly call ElementsPanel's focusedNodeChanged() and the hover feedback will be stopped there. + inspect(node); +} + +void InspectorController::inspectedWindowScriptObjectCleared(Frame* frame) +{ + if (!enabled() || !m_scriptContext || !m_scriptObject) + return; + + JSDOMWindow* win = toJSDOMWindow(frame); + ExecState* exec = win->globalExec(); + + JSValueRef arg0; + + { + JSC::JSLock lock(false); + arg0 = toRef(JSInspectedObjectWrapper::wrap(exec, win)); + } + + JSValueRef exception = 0; + callFunction(m_scriptContext, m_scriptObject, "inspectedWindowCleared", 1, &arg0, exception); +} + +void InspectorController::windowScriptObjectAvailable() +{ + if (!m_page || !enabled()) + return; + + m_scriptContext = toRef(m_page->mainFrame()->script()->globalObject()->globalExec()); + + JSObjectRef global = JSContextGetGlobalObject(m_scriptContext); + ASSERT(global); + + static JSStaticFunction staticFunctions[] = { + // SIMPLE_INSPECTOR_CALLBACK + { "hideDOMNodeHighlight", WebCore::hideDOMNodeHighlight, kJSPropertyAttributeNone }, + { "loaded", WebCore::loaded, kJSPropertyAttributeNone }, + { "windowUnloading", WebCore::unloading, kJSPropertyAttributeNone }, + { "attach", WebCore::attach, kJSPropertyAttributeNone }, + { "detach", WebCore::detach, kJSPropertyAttributeNone }, +#if ENABLE(JAVASCRIPT_DEBUGGER) + { "enableDebugger", WebCore::enableDebugger, kJSPropertyAttributeNone }, + { "disableDebugger", WebCore::disableDebugger, kJSPropertyAttributeNone }, + { "pauseInDebugger", WebCore::pauseInDebugger, kJSPropertyAttributeNone }, + { "resumeDebugger", WebCore::resumeDebugger, kJSPropertyAttributeNone }, + { "stepOverStatementInDebugger", WebCore::stepOverStatementInDebugger, kJSPropertyAttributeNone }, + { "stepIntoStatementInDebugger", WebCore::stepIntoStatementInDebugger, kJSPropertyAttributeNone }, + { "stepOutOfFunctionInDebugger", WebCore::stepOutOfFunctionInDebugger, kJSPropertyAttributeNone }, +#endif + { "closeWindow", WebCore::closeWindow, kJSPropertyAttributeNone }, + { "clearMessages", WebCore::clearMessages, kJSPropertyAttributeNone }, + { "startProfiling", WebCore::startProfiling, kJSPropertyAttributeNone }, + { "stopProfiling", WebCore::stopProfiling, kJSPropertyAttributeNone }, + { "enableProfiler", WebCore::enableProfiler, kJSPropertyAttributeNone }, + { "disableProfiler", WebCore::disableProfiler, kJSPropertyAttributeNone }, + { "toggleNodeSearch", WebCore::toggleNodeSearch, kJSPropertyAttributeNone }, + + // BOOL_INSPECTOR_CALLBACK +#if ENABLE(JAVASCRIPT_DEBUGGER) + { "debuggerEnabled", WebCore::debuggerEnabled, kJSPropertyAttributeNone }, + { "pauseOnExceptions", WebCore::pauseOnExceptions, kJSPropertyAttributeNone }, +#endif + { "profilerEnabled", WebCore::profilerEnabled, kJSPropertyAttributeNone }, + { "isWindowVisible", WebCore::isWindowVisible, kJSPropertyAttributeNone }, + { "searchingForNode", WebCore::searchingForNode, kJSPropertyAttributeNone }, + + // Custom callbacks + { "addResourceSourceToFrame", WebCore::addResourceSourceToFrame, kJSPropertyAttributeNone }, + { "addSourceToFrame", WebCore::addSourceToFrame, kJSPropertyAttributeNone }, + { "getResourceDocumentNode", WebCore::getResourceDocumentNode, kJSPropertyAttributeNone }, + { "highlightDOMNode", WebCore::highlightDOMNode, kJSPropertyAttributeNone }, + { "search", WebCore::search, kJSPropertyAttributeNone }, +#if ENABLE(DATABASE) + { "databaseTableNames", WebCore::databaseTableNames, kJSPropertyAttributeNone }, +#endif + { "setting", WebCore::setting, kJSPropertyAttributeNone }, + { "setSetting", WebCore::setSetting, kJSPropertyAttributeNone }, + { "inspectedWindow", WebCore::inspectedWindow, kJSPropertyAttributeNone }, + { "localizedStringsURL", WebCore::localizedStrings, kJSPropertyAttributeNone }, + { "platform", WebCore::platform, kJSPropertyAttributeNone }, + { "moveByUnrestricted", WebCore::moveByUnrestricted, kJSPropertyAttributeNone }, + { "setAttachedWindowHeight", WebCore::setAttachedWindowHeight, kJSPropertyAttributeNone }, + { "wrapCallback", WebCore::wrapCallback, kJSPropertyAttributeNone }, +#if ENABLE(JAVASCRIPT_DEBUGGER) + { "currentCallFrame", WebCore::currentCallFrame, kJSPropertyAttributeNone }, + { "setPauseOnExceptions", WebCore::setPauseOnExceptions, kJSPropertyAttributeNone }, + { "addBreakpoint", WebCore::addBreakpoint, kJSPropertyAttributeNone }, + { "removeBreakpoint", WebCore::removeBreakpoint, kJSPropertyAttributeNone }, +#endif + { "profiles", WebCore::profiles, kJSPropertyAttributeNone }, + { 0, 0, 0 } + }; + + JSClassDefinition inspectorControllerDefinition = { + 0, kJSClassAttributeNone, "InspectorController", 0, 0, staticFunctions, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + + JSClassRef controllerClass = JSClassCreate(&inspectorControllerDefinition); + ASSERT(controllerClass); + + m_controllerScriptObject = JSObjectMake(m_scriptContext, controllerClass, reinterpret_cast<void*>(this)); + ASSERT(m_controllerScriptObject); + + JSObjectSetProperty(m_scriptContext, global, jsStringRef("InspectorController").get(), m_controllerScriptObject, kJSPropertyAttributeNone, 0); +} + +void InspectorController::scriptObjectReady() +{ + ASSERT(m_scriptContext); + if (!m_scriptContext) + return; + + JSObjectRef global = JSContextGetGlobalObject(m_scriptContext); + ASSERT(global); + + JSValueRef exception = 0; + + JSValueRef inspectorValue = JSObjectGetProperty(m_scriptContext, global, jsStringRef("WebInspector").get(), &exception); + if (HANDLE_EXCEPTION(m_scriptContext, exception)) + return; + + ASSERT(inspectorValue); + if (!inspectorValue) + return; + + m_scriptObject = JSValueToObject(m_scriptContext, inspectorValue, &exception); + if (HANDLE_EXCEPTION(m_scriptContext, exception)) + return; + + ASSERT(m_scriptObject); + + JSValueProtect(m_scriptContext, m_scriptObject); + + // Make sure our window is visible now that the page loaded + showWindow(); +} + +void InspectorController::show() +{ + if (!enabled()) + return; + + if (!m_page) { + m_page = m_client->createPage(); + if (!m_page) + return; + m_page->setParentInspectorController(this); + + // showWindow() will be called after the page loads in scriptObjectReady() + return; + } + + showWindow(); +} + +void InspectorController::showPanel(SpecialPanels panel) +{ + if (!enabled()) + return; + + show(); + + if (!m_scriptObject) { + m_showAfterVisible = panel; + return; + } + + if (panel == CurrentPanel) + return; + + const char* showFunctionName; + switch (panel) { + case ConsolePanel: + showFunctionName = "showConsole"; + break; + case DatabasesPanel: + showFunctionName = "showDatabasesPanel"; + break; + case ElementsPanel: + showFunctionName = "showElementsPanel"; + break; + case ProfilesPanel: + showFunctionName = "showProfilesPanel"; + break; + case ResourcesPanel: + showFunctionName = "showResourcesPanel"; + break; + case ScriptsPanel: + showFunctionName = "showScriptsPanel"; + break; + default: + ASSERT_NOT_REACHED(); + showFunctionName = 0; + } + + if (showFunctionName) + callSimpleFunction(m_scriptContext, m_scriptObject, showFunctionName); +} + +void InspectorController::close() +{ + if (!enabled()) + return; + + stopUserInitiatedProfiling(); +#if ENABLE(JAVASCRIPT_DEBUGGER) + disableDebugger(); +#endif + closeWindow(); + + if (m_scriptContext && m_scriptObject) + JSValueUnprotect(m_scriptContext, m_scriptObject); + + m_scriptObject = 0; + m_scriptContext = 0; +} + +void InspectorController::showWindow() +{ + ASSERT(enabled()); + m_client->showWindow(); +} + +void InspectorController::closeWindow() +{ + m_client->closeWindow(); +} + +void InspectorController::startUserInitiatedProfilingSoon() +{ + m_startProfiling.startOneShot(0); +} + +void InspectorController::startUserInitiatedProfiling(Timer<InspectorController>*) +{ + if (!enabled()) + return; + + if (!profilerEnabled()) { + enableProfiler(true); + JavaScriptDebugServer::shared().recompileAllJSFunctions(); + } + + m_recordingUserInitiatedProfile = true; + m_currentUserInitiatedProfileNumber = m_nextUserInitiatedProfileNumber++; + + UString title = UserInitiatedProfileName; + title += "."; + title += UString::from(m_currentUserInitiatedProfileNumber); + + ExecState* exec = toJSDOMWindow(m_inspectedPage->mainFrame())->globalExec(); + Profiler::profiler()->startProfiling(exec, title); + + toggleRecordButton(true); +} + +void InspectorController::stopUserInitiatedProfiling() +{ + if (!enabled()) + return; + + m_recordingUserInitiatedProfile = false; + + UString title = UserInitiatedProfileName; + title += "."; + title += UString::from(m_currentUserInitiatedProfileNumber); + + ExecState* exec = toJSDOMWindow(m_inspectedPage->mainFrame())->globalExec(); + RefPtr<Profile> profile = Profiler::profiler()->stopProfiling(exec, title); + if (profile) + addProfile(profile, 0, UString()); + + toggleRecordButton(false); +} + +void InspectorController::enableProfiler(bool skipRecompile) +{ + if (m_profilerEnabled) + return; + + m_profilerEnabled = true; + + if (!skipRecompile) + JavaScriptDebugServer::shared().recompileAllJSFunctionsSoon(); + + if (m_scriptContext && m_scriptObject) + callSimpleFunction(m_scriptContext, m_scriptObject, "profilerWasEnabled"); +} + +void InspectorController::disableProfiler() +{ + if (!m_profilerEnabled) + return; + + m_profilerEnabled = false; + + JavaScriptDebugServer::shared().recompileAllJSFunctionsSoon(); + + if (m_scriptContext && m_scriptObject) + callSimpleFunction(m_scriptContext, m_scriptObject, "profilerWasDisabled"); +} + +static void addHeaders(JSContextRef context, JSObjectRef object, const HTTPHeaderMap& headers, JSValueRef* exception) +{ + ASSERT_ARG(context, context); + ASSERT_ARG(object, object); + + HTTPHeaderMap::const_iterator end = headers.end(); + for (HTTPHeaderMap::const_iterator it = headers.begin(); it != end; ++it) { + JSValueRef value = JSValueMakeString(context, jsStringRef(it->second).get()); + JSObjectSetProperty(context, object, jsStringRef(it->first).get(), value, kJSPropertyAttributeNone, exception); + if (exception && *exception) + return; + } +} + +static JSObjectRef scriptObjectForRequest(JSContextRef context, const InspectorResource* resource, JSValueRef* exception) +{ + ASSERT_ARG(context, context); + + JSObjectRef object = JSObjectMake(context, 0, 0); + addHeaders(context, object, resource->requestHeaderFields, exception); + + return object; +} + +static JSObjectRef scriptObjectForResponse(JSContextRef context, const InspectorResource* resource, JSValueRef* exception) +{ + ASSERT_ARG(context, context); + + JSObjectRef object = JSObjectMake(context, 0, 0); + addHeaders(context, object, resource->responseHeaderFields, exception); + + return object; +} + +JSObjectRef InspectorController::addScriptResource(InspectorResource* resource) +{ + ASSERT_ARG(resource, resource); + + ASSERT(m_scriptContext); + ASSERT(m_scriptObject); + if (!m_scriptContext || !m_scriptObject) + return 0; + + if (!resource->scriptObject) { + JSValueRef exception = 0; + + JSValueRef resourceProperty = JSObjectGetProperty(m_scriptContext, m_scriptObject, jsStringRef("Resource").get(), &exception); + if (HANDLE_EXCEPTION(m_scriptContext, exception)) + return 0; + + JSObjectRef resourceConstructor = JSValueToObject(m_scriptContext, resourceProperty, &exception); + if (HANDLE_EXCEPTION(m_scriptContext, exception)) + return 0; + + JSValueRef urlValue = JSValueMakeString(m_scriptContext, jsStringRef(resource->requestURL.string()).get()); + JSValueRef domainValue = JSValueMakeString(m_scriptContext, jsStringRef(resource->requestURL.host()).get()); + JSValueRef pathValue = JSValueMakeString(m_scriptContext, jsStringRef(resource->requestURL.path()).get()); + JSValueRef lastPathComponentValue = JSValueMakeString(m_scriptContext, jsStringRef(resource->requestURL.lastPathComponent()).get()); + + JSValueRef identifier = JSValueMakeNumber(m_scriptContext, resource->identifier); + JSValueRef mainResource = JSValueMakeBoolean(m_scriptContext, m_mainResource == resource); + JSValueRef cached = JSValueMakeBoolean(m_scriptContext, resource->cached); + + JSObjectRef scriptObject = scriptObjectForRequest(m_scriptContext, resource, &exception); + if (HANDLE_EXCEPTION(m_scriptContext, exception)) + return 0; + + JSValueRef arguments[] = { scriptObject, urlValue, domainValue, pathValue, lastPathComponentValue, identifier, mainResource, cached }; + JSObjectRef result = JSObjectCallAsConstructor(m_scriptContext, resourceConstructor, 8, arguments, &exception); + if (HANDLE_EXCEPTION(m_scriptContext, exception)) + return 0; + + ASSERT(result); + + resource->setScriptObject(m_scriptContext, result); + } + + JSValueRef exception = 0; + callFunction(m_scriptContext, m_scriptObject, "addResource", 1, &resource->scriptObject, exception); + + if (exception) + return 0; + + return resource->scriptObject; +} + +JSObjectRef InspectorController::addAndUpdateScriptResource(InspectorResource* resource) +{ + ASSERT_ARG(resource, resource); + + JSObjectRef scriptResource = addScriptResource(resource); + if (!scriptResource) + return 0; + + updateScriptResourceResponse(resource); + updateScriptResource(resource, resource->length); + updateScriptResource(resource, resource->startTime, resource->responseReceivedTime, resource->endTime); + updateScriptResource(resource, resource->finished, resource->failed); + return scriptResource; +} + +void InspectorController::removeScriptResource(InspectorResource* resource) +{ + ASSERT(m_scriptContext); + ASSERT(m_scriptObject); + if (!m_scriptContext || !m_scriptObject) + return; + + ASSERT(resource); + ASSERT(resource->scriptObject); + if (!resource || !resource->scriptObject) + return; + + JSObjectRef scriptObject = resource->scriptObject; + resource->setScriptObject(0, 0); + + JSValueRef exception = 0; + callFunction(m_scriptContext, m_scriptObject, "removeResource", 1, &scriptObject, exception); +} + +static void updateResourceRequest(InspectorResource* resource, const ResourceRequest& request) +{ + resource->requestHeaderFields = request.httpHeaderFields(); + resource->requestURL = request.url(); +} + +static void updateResourceResponse(InspectorResource* resource, const ResourceResponse& response) +{ + resource->expectedContentLength = response.expectedContentLength(); + resource->mimeType = response.mimeType(); + resource->responseHeaderFields = response.httpHeaderFields(); + resource->responseStatusCode = response.httpStatusCode(); + resource->suggestedFilename = response.suggestedFilename(); +} + +void InspectorController::updateScriptResourceRequest(InspectorResource* resource) +{ + ASSERT(resource->scriptObject); + ASSERT(m_scriptContext); + if (!resource->scriptObject || !m_scriptContext) + return; + + JSValueRef urlValue = JSValueMakeString(m_scriptContext, jsStringRef(resource->requestURL.string()).get()); + JSValueRef domainValue = JSValueMakeString(m_scriptContext, jsStringRef(resource->requestURL.host()).get()); + JSValueRef pathValue = JSValueMakeString(m_scriptContext, jsStringRef(resource->requestURL.path()).get()); + JSValueRef lastPathComponentValue = JSValueMakeString(m_scriptContext, jsStringRef(resource->requestURL.lastPathComponent()).get()); + + JSValueRef mainResourceValue = JSValueMakeBoolean(m_scriptContext, m_mainResource == resource); + + JSValueRef exception = 0; + + JSObjectSetProperty(m_scriptContext, resource->scriptObject, jsStringRef("url").get(), urlValue, kJSPropertyAttributeNone, &exception); + if (HANDLE_EXCEPTION(m_scriptContext, exception)) + return; + + JSObjectSetProperty(m_scriptContext, resource->scriptObject, jsStringRef("domain").get(), domainValue, kJSPropertyAttributeNone, &exception); + if (HANDLE_EXCEPTION(m_scriptContext, exception)) + return; + + JSObjectSetProperty(m_scriptContext, resource->scriptObject, jsStringRef("path").get(), pathValue, kJSPropertyAttributeNone, &exception); + if (HANDLE_EXCEPTION(m_scriptContext, exception)) + return; + + JSObjectSetProperty(m_scriptContext, resource->scriptObject, jsStringRef("lastPathComponent").get(), lastPathComponentValue, kJSPropertyAttributeNone, &exception); + if (HANDLE_EXCEPTION(m_scriptContext, exception)) + return; + + JSObjectRef scriptObject = scriptObjectForRequest(m_scriptContext, resource, &exception); + if (HANDLE_EXCEPTION(m_scriptContext, exception)) + return; + + JSObjectSetProperty(m_scriptContext, resource->scriptObject, jsStringRef("requestHeaders").get(), scriptObject, kJSPropertyAttributeNone, &exception); + if (HANDLE_EXCEPTION(m_scriptContext, exception)) + return; + + JSObjectSetProperty(m_scriptContext, resource->scriptObject, jsStringRef("mainResource").get(), mainResourceValue, kJSPropertyAttributeNone, &exception); + HANDLE_EXCEPTION(m_scriptContext, exception); +} + +void InspectorController::updateScriptResourceResponse(InspectorResource* resource) +{ + ASSERT(resource->scriptObject); + ASSERT(m_scriptContext); + if (!resource->scriptObject || !m_scriptContext) + return; + + JSValueRef mimeTypeValue = JSValueMakeString(m_scriptContext, jsStringRef(resource->mimeType).get()); + + JSValueRef suggestedFilenameValue = JSValueMakeString(m_scriptContext, jsStringRef(resource->suggestedFilename).get()); + + JSValueRef expectedContentLengthValue = JSValueMakeNumber(m_scriptContext, static_cast<double>(resource->expectedContentLength)); + JSValueRef statusCodeValue = JSValueMakeNumber(m_scriptContext, resource->responseStatusCode); + + JSValueRef exception = 0; + + JSObjectSetProperty(m_scriptContext, resource->scriptObject, jsStringRef("mimeType").get(), mimeTypeValue, kJSPropertyAttributeNone, &exception); + if (HANDLE_EXCEPTION(m_scriptContext, exception)) + return; + + JSObjectSetProperty(m_scriptContext, resource->scriptObject, jsStringRef("suggestedFilename").get(), suggestedFilenameValue, kJSPropertyAttributeNone, &exception); + if (HANDLE_EXCEPTION(m_scriptContext, exception)) + return; + + JSObjectSetProperty(m_scriptContext, resource->scriptObject, jsStringRef("expectedContentLength").get(), expectedContentLengthValue, kJSPropertyAttributeNone, &exception); + if (HANDLE_EXCEPTION(m_scriptContext, exception)) + return; + + JSObjectSetProperty(m_scriptContext, resource->scriptObject, jsStringRef("statusCode").get(), statusCodeValue, kJSPropertyAttributeNone, &exception); + if (HANDLE_EXCEPTION(m_scriptContext, exception)) + return; + + JSObjectRef scriptObject = scriptObjectForResponse(m_scriptContext, resource, &exception); + if (HANDLE_EXCEPTION(m_scriptContext, exception)) + return; + + JSObjectSetProperty(m_scriptContext, resource->scriptObject, jsStringRef("responseHeaders").get(), scriptObject, kJSPropertyAttributeNone, &exception); + if (HANDLE_EXCEPTION(m_scriptContext, exception)) + return; + + updateScriptResourceType(resource); +} + +void InspectorController::updateScriptResourceType(InspectorResource* resource) +{ + ASSERT(resource->scriptObject); + ASSERT(m_scriptContext); + if (!resource->scriptObject || !m_scriptContext) + return; + + JSValueRef exception = 0; + + JSValueRef typeValue = JSValueMakeNumber(m_scriptContext, resource->type()); + JSObjectSetProperty(m_scriptContext, resource->scriptObject, jsStringRef("type").get(), typeValue, kJSPropertyAttributeNone, &exception); + HANDLE_EXCEPTION(m_scriptContext, exception); +} + +void InspectorController::updateScriptResource(InspectorResource* resource, int length) +{ + ASSERT(resource->scriptObject); + ASSERT(m_scriptContext); + if (!resource->scriptObject || !m_scriptContext) + return; + + JSValueRef lengthValue = JSValueMakeNumber(m_scriptContext, length); + + JSValueRef exception = 0; + + JSObjectSetProperty(m_scriptContext, resource->scriptObject, jsStringRef("contentLength").get(), lengthValue, kJSPropertyAttributeNone, &exception); + HANDLE_EXCEPTION(m_scriptContext, exception); +} + +void InspectorController::updateScriptResource(InspectorResource* resource, bool finished, bool failed) +{ + ASSERT(resource->scriptObject); + ASSERT(m_scriptContext); + if (!resource->scriptObject || !m_scriptContext) + return; + + JSValueRef failedValue = JSValueMakeBoolean(m_scriptContext, failed); + JSValueRef finishedValue = JSValueMakeBoolean(m_scriptContext, finished); + + JSValueRef exception = 0; + + JSObjectSetProperty(m_scriptContext, resource->scriptObject, jsStringRef("failed").get(), failedValue, kJSPropertyAttributeNone, &exception); + if (HANDLE_EXCEPTION(m_scriptContext, exception)) + return; + + JSObjectSetProperty(m_scriptContext, resource->scriptObject, jsStringRef("finished").get(), finishedValue, kJSPropertyAttributeNone, &exception); + HANDLE_EXCEPTION(m_scriptContext, exception); +} + +void InspectorController::updateScriptResource(InspectorResource* resource, double startTime, double responseReceivedTime, double endTime) +{ + ASSERT(resource->scriptObject); + ASSERT(m_scriptContext); + if (!resource->scriptObject || !m_scriptContext) + return; + + JSValueRef startTimeValue = JSValueMakeNumber(m_scriptContext, startTime); + JSValueRef responseReceivedTimeValue = JSValueMakeNumber(m_scriptContext, responseReceivedTime); + JSValueRef endTimeValue = JSValueMakeNumber(m_scriptContext, endTime); + + JSValueRef exception = 0; + + JSObjectSetProperty(m_scriptContext, resource->scriptObject, jsStringRef("startTime").get(), startTimeValue, kJSPropertyAttributeNone, &exception); + if (HANDLE_EXCEPTION(m_scriptContext, exception)) + return; + + JSObjectSetProperty(m_scriptContext, resource->scriptObject, jsStringRef("responseReceivedTime").get(), responseReceivedTimeValue, kJSPropertyAttributeNone, &exception); + if (HANDLE_EXCEPTION(m_scriptContext, exception)) + return; + + JSObjectSetProperty(m_scriptContext, resource->scriptObject, jsStringRef("endTime").get(), endTimeValue, kJSPropertyAttributeNone, &exception); + HANDLE_EXCEPTION(m_scriptContext, exception); +} + +void InspectorController::populateScriptObjects() +{ + ASSERT(m_scriptContext); + if (!m_scriptContext) + return; + + ResourcesMap::iterator resourcesEnd = m_resources.end(); + for (ResourcesMap::iterator it = m_resources.begin(); it != resourcesEnd; ++it) + addAndUpdateScriptResource(it->second.get()); + + unsigned messageCount = m_consoleMessages.size(); + for (unsigned i = 0; i < messageCount; ++i) + addScriptConsoleMessage(m_consoleMessages[i]); + +#if ENABLE(DATABASE) + DatabaseResourcesSet::iterator databasesEnd = m_databaseResources.end(); + for (DatabaseResourcesSet::iterator it = m_databaseResources.begin(); it != databasesEnd; ++it) + addDatabaseScriptResource((*it).get()); +#endif + + callSimpleFunction(m_scriptContext, m_scriptObject, "populateInterface"); +} + +#if ENABLE(DATABASE) +JSObjectRef InspectorController::addDatabaseScriptResource(InspectorDatabaseResource* resource) +{ + ASSERT_ARG(resource, resource); + + if (resource->scriptObject) + return resource->scriptObject; + + ASSERT(m_scriptContext); + ASSERT(m_scriptObject); + if (!m_scriptContext || !m_scriptObject) + return 0; + + Frame* frame = resource->database->document()->frame(); + if (!frame) + return 0; + + JSValueRef exception = 0; + + JSValueRef databaseProperty = JSObjectGetProperty(m_scriptContext, m_scriptObject, jsStringRef("Database").get(), &exception); + if (HANDLE_EXCEPTION(m_scriptContext, exception)) + return 0; + + JSObjectRef databaseConstructor = JSValueToObject(m_scriptContext, databaseProperty, &exception); + if (HANDLE_EXCEPTION(m_scriptContext, exception)) + return 0; + + ExecState* exec = toJSDOMWindow(frame)->globalExec(); + + JSValueRef database; + + { + JSC::JSLock lock(false); + database = toRef(JSInspectedObjectWrapper::wrap(exec, toJS(exec, resource->database.get()))); + } + + JSValueRef domainValue = JSValueMakeString(m_scriptContext, jsStringRef(resource->domain).get()); + JSValueRef nameValue = JSValueMakeString(m_scriptContext, jsStringRef(resource->name).get()); + JSValueRef versionValue = JSValueMakeString(m_scriptContext, jsStringRef(resource->version).get()); + + JSValueRef arguments[] = { database, domainValue, nameValue, versionValue }; + JSObjectRef result = JSObjectCallAsConstructor(m_scriptContext, databaseConstructor, 4, arguments, &exception); + if (HANDLE_EXCEPTION(m_scriptContext, exception)) + return 0; + + ASSERT(result); + + callFunction(m_scriptContext, m_scriptObject, "addDatabase", 1, &result, exception); + + if (exception) + return 0; + + resource->setScriptObject(m_scriptContext, result); + + return result; +} + +void InspectorController::removeDatabaseScriptResource(InspectorDatabaseResource* resource) +{ + ASSERT(m_scriptContext); + ASSERT(m_scriptObject); + if (!m_scriptContext || !m_scriptObject) + return; + + ASSERT(resource); + ASSERT(resource->scriptObject); + if (!resource || !resource->scriptObject) + return; + + JSObjectRef scriptObject = resource->scriptObject; + resource->setScriptObject(0, 0); + + JSValueRef exception = 0; + callFunction(m_scriptContext, m_scriptObject, "removeDatabase", 1, &scriptObject, exception); +} +#endif + +void InspectorController::addScriptConsoleMessage(const ConsoleMessage* message) +{ + ASSERT_ARG(message, message); + + JSValueRef exception = 0; + + JSValueRef messageConstructorProperty = JSObjectGetProperty(m_scriptContext, m_scriptObject, jsStringRef("ConsoleMessage").get(), &exception); + if (HANDLE_EXCEPTION(m_scriptContext, exception)) + return; + + JSObjectRef messageConstructor = JSValueToObject(m_scriptContext, messageConstructorProperty, &exception); + if (HANDLE_EXCEPTION(m_scriptContext, exception)) + return; + + JSValueRef sourceValue = JSValueMakeNumber(m_scriptContext, message->source); + JSValueRef levelValue = JSValueMakeNumber(m_scriptContext, message->level); + JSValueRef lineValue = JSValueMakeNumber(m_scriptContext, message->line); + JSValueRef urlValue = JSValueMakeString(m_scriptContext, jsStringRef(message->url).get()); + JSValueRef groupLevelValue = JSValueMakeNumber(m_scriptContext, message->groupLevel); + JSValueRef repeatCountValue = JSValueMakeNumber(m_scriptContext, message->repeatCount); + + static const unsigned maximumMessageArguments = 256; + JSValueRef arguments[maximumMessageArguments]; + unsigned argumentCount = 0; + arguments[argumentCount++] = sourceValue; + arguments[argumentCount++] = levelValue; + arguments[argumentCount++] = lineValue; + arguments[argumentCount++] = urlValue; + arguments[argumentCount++] = groupLevelValue; + arguments[argumentCount++] = repeatCountValue; + + if (!message->wrappedArguments.isEmpty()) { + unsigned remainingSpaceInArguments = maximumMessageArguments - argumentCount; + unsigned argumentsToAdd = min(remainingSpaceInArguments, static_cast<unsigned>(message->wrappedArguments.size())); + for (unsigned i = 0; i < argumentsToAdd; ++i) + arguments[argumentCount++] = toRef(message->wrappedArguments[i]); + } else { + JSValueRef messageValue = JSValueMakeString(m_scriptContext, jsStringRef(message->message).get()); + arguments[argumentCount++] = messageValue; + } + + JSObjectRef messageObject = JSObjectCallAsConstructor(m_scriptContext, messageConstructor, argumentCount, arguments, &exception); + if (HANDLE_EXCEPTION(m_scriptContext, exception)) + return; + + callFunction(m_scriptContext, m_scriptObject, "addMessageToConsole", 1, &messageObject, exception); +} + +void InspectorController::addScriptProfile(Profile* profile) +{ + JSLock lock(false); + JSValueRef exception = 0; + JSValueRef profileObject = toRef(toJS(toJS(m_scriptContext), profile)); + callFunction(m_scriptContext, m_scriptObject, "addProfile", 1, &profileObject, exception); +} + +void InspectorController::resetScriptObjects() +{ + if (!m_scriptContext || !m_scriptObject) + return; + + ResourcesMap::iterator resourcesEnd = m_resources.end(); + for (ResourcesMap::iterator it = m_resources.begin(); it != resourcesEnd; ++it) { + InspectorResource* resource = it->second.get(); + resource->setScriptObject(0, 0); + } + +#if ENABLE(DATABASE) + DatabaseResourcesSet::iterator databasesEnd = m_databaseResources.end(); + for (DatabaseResourcesSet::iterator it = m_databaseResources.begin(); it != databasesEnd; ++it) { + InspectorDatabaseResource* resource = (*it).get(); + resource->setScriptObject(0, 0); + } +#endif + + callSimpleFunction(m_scriptContext, m_scriptObject, "reset"); +} + +void InspectorController::pruneResources(ResourcesMap* resourceMap, DocumentLoader* loaderToKeep) +{ + ASSERT_ARG(resourceMap, resourceMap); + + ResourcesMap mapCopy(*resourceMap); + ResourcesMap::iterator end = mapCopy.end(); + for (ResourcesMap::iterator it = mapCopy.begin(); it != end; ++it) { + InspectorResource* resource = (*it).second.get(); + if (resource == m_mainResource) + continue; + + if (!loaderToKeep || resource->loader != loaderToKeep) { + removeResource(resource); + if (windowVisible() && resource->scriptObject) + removeScriptResource(resource); + } + } +} + +void InspectorController::didCommitLoad(DocumentLoader* loader) +{ + if (!enabled()) + return; + + ASSERT(m_inspectedPage); + + if (loader->frame() == m_inspectedPage->mainFrame()) { + m_client->inspectedURLChanged(loader->url().string()); + + clearConsoleMessages(); + + m_times.clear(); + m_counts.clear(); + m_profiles.clear(); + +#if ENABLE(DATABASE) + m_databaseResources.clear(); +#endif + + if (windowVisible()) { + resetScriptObjects(); + + if (!loader->isLoadingFromCachedPage()) { + ASSERT(m_mainResource && m_mainResource->loader == loader); + // We don't add the main resource until its load is committed. This is + // needed to keep the load for a user-entered URL from showing up in the + // list of resources for the page they are navigating away from. + addAndUpdateScriptResource(m_mainResource.get()); + } else { + // Pages loaded from the page cache are committed before + // m_mainResource is the right resource for this load, so we + // clear it here. It will be re-assigned in + // identifierForInitialRequest. + m_mainResource = 0; + } + } + } + + for (Frame* frame = loader->frame(); frame; frame = frame->tree()->traverseNext(loader->frame())) + if (ResourcesMap* resourceMap = m_frameResources.get(frame)) + pruneResources(resourceMap, loader); +} + +void InspectorController::frameDetachedFromParent(Frame* frame) +{ + if (!enabled()) + return; + if (ResourcesMap* resourceMap = m_frameResources.get(frame)) + removeAllResources(resourceMap); +} + +void InspectorController::addResource(InspectorResource* resource) +{ + m_resources.set(resource->identifier, resource); + m_knownResources.add(resource->requestURL.string()); + + Frame* frame = resource->frame.get(); + ResourcesMap* resourceMap = m_frameResources.get(frame); + if (resourceMap) + resourceMap->set(resource->identifier, resource); + else { + resourceMap = new ResourcesMap; + resourceMap->set(resource->identifier, resource); + m_frameResources.set(frame, resourceMap); + } +} + +void InspectorController::removeResource(InspectorResource* resource) +{ + m_resources.remove(resource->identifier); + m_knownResources.remove(resource->requestURL.string()); + + Frame* frame = resource->frame.get(); + ResourcesMap* resourceMap = m_frameResources.get(frame); + if (!resourceMap) { + ASSERT_NOT_REACHED(); + return; + } + + resourceMap->remove(resource->identifier); + if (resourceMap->isEmpty()) { + m_frameResources.remove(frame); + delete resourceMap; + } +} + +void InspectorController::didLoadResourceFromMemoryCache(DocumentLoader* loader, const ResourceRequest& request, const ResourceResponse& response, int length) +{ + if (!enabled()) + return; + + // If the resource URL is already known, we don't need to add it again since this is just a cached load. + if (m_knownResources.contains(request.url().string())) + return; + + RefPtr<InspectorResource> resource = InspectorResource::create(m_nextIdentifier--, loader, loader->frame()); + resource->finished = true; + + updateResourceRequest(resource.get(), request); + updateResourceResponse(resource.get(), response); + + resource->length = length; + resource->cached = true; + resource->startTime = currentTime(); + resource->responseReceivedTime = resource->startTime; + resource->endTime = resource->startTime; + + ASSERT(m_inspectedPage); + + if (loader->frame() == m_inspectedPage->mainFrame() && request.url() == loader->requestURL()) + m_mainResource = resource; + + addResource(resource.get()); + + if (windowVisible()) + addAndUpdateScriptResource(resource.get()); +} + +void InspectorController::identifierForInitialRequest(unsigned long identifier, DocumentLoader* loader, const ResourceRequest& request) +{ + if (!enabled()) + return; + + RefPtr<InspectorResource> resource = InspectorResource::create(identifier, loader, loader->frame()); + + updateResourceRequest(resource.get(), request); + + ASSERT(m_inspectedPage); + + if (loader->frame() == m_inspectedPage->mainFrame() && request.url() == loader->requestURL()) + m_mainResource = resource; + + addResource(resource.get()); + + if (windowVisible() && loader->isLoadingFromCachedPage() && resource == m_mainResource) + addAndUpdateScriptResource(resource.get()); +} + +void InspectorController::willSendRequest(DocumentLoader* loader, unsigned long identifier, ResourceRequest& request, const ResourceResponse& redirectResponse) +{ + if (!enabled()) + return; + + InspectorResource* resource = m_resources.get(identifier).get(); + if (!resource) + return; + + resource->startTime = currentTime(); + + if (!redirectResponse.isNull()) { + updateResourceRequest(resource, request); + updateResourceResponse(resource, redirectResponse); + } + + if (resource != m_mainResource && windowVisible()) { + if (!resource->scriptObject) + addScriptResource(resource); + else + updateScriptResourceRequest(resource); + + updateScriptResource(resource, resource->startTime, resource->responseReceivedTime, resource->endTime); + + if (!redirectResponse.isNull()) + updateScriptResourceResponse(resource); + } +} + +void InspectorController::didReceiveResponse(DocumentLoader*, unsigned long identifier, const ResourceResponse& response) +{ + if (!enabled()) + return; + + InspectorResource* resource = m_resources.get(identifier).get(); + if (!resource) + return; + + updateResourceResponse(resource, response); + + resource->responseReceivedTime = currentTime(); + + if (windowVisible() && resource->scriptObject) { + updateScriptResourceResponse(resource); + updateScriptResource(resource, resource->startTime, resource->responseReceivedTime, resource->endTime); + } +} + +void InspectorController::didReceiveContentLength(DocumentLoader*, unsigned long identifier, int lengthReceived) +{ + if (!enabled()) + return; + + InspectorResource* resource = m_resources.get(identifier).get(); + if (!resource) + return; + + resource->length += lengthReceived; + + if (windowVisible() && resource->scriptObject) + updateScriptResource(resource, resource->length); +} + +void InspectorController::didFinishLoading(DocumentLoader* loader, unsigned long identifier) +{ + if (!enabled()) + return; + + RefPtr<InspectorResource> resource = m_resources.get(identifier); + if (!resource) + return; + + removeResource(resource.get()); + + resource->finished = true; + resource->endTime = currentTime(); + + addResource(resource.get()); + + if (windowVisible() && resource->scriptObject) { + updateScriptResource(resource.get(), resource->startTime, resource->responseReceivedTime, resource->endTime); + updateScriptResource(resource.get(), resource->finished); + } +} + +void InspectorController::didFailLoading(DocumentLoader* loader, unsigned long identifier, const ResourceError& /*error*/) +{ + if (!enabled()) + return; + + RefPtr<InspectorResource> resource = m_resources.get(identifier); + if (!resource) + return; + + removeResource(resource.get()); + + resource->finished = true; + resource->failed = true; + resource->endTime = currentTime(); + + addResource(resource.get()); + + if (windowVisible() && resource->scriptObject) { + updateScriptResource(resource.get(), resource->startTime, resource->responseReceivedTime, resource->endTime); + updateScriptResource(resource.get(), resource->finished, resource->failed); + } +} + +void InspectorController::resourceRetrievedByXMLHttpRequest(unsigned long identifier, JSC::UString& sourceString) +{ + if (!enabled()) + return; + + InspectorResource* resource = m_resources.get(identifier).get(); + if (!resource) + return; + + resource->setXMLHttpRequestProperties(sourceString); + + if (windowVisible() && resource->scriptObject) + updateScriptResourceType(resource); +} + + +#if ENABLE(DATABASE) +void InspectorController::didOpenDatabase(Database* database, const String& domain, const String& name, const String& version) +{ + if (!enabled()) + return; + + RefPtr<InspectorDatabaseResource> resource = InspectorDatabaseResource::create(database, domain, name, version); + + m_databaseResources.add(resource); + + if (windowVisible()) + addDatabaseScriptResource(resource.get()); +} +#endif + +void InspectorController::moveWindowBy(float x, float y) const +{ + if (!m_page || !enabled()) + return; + + FloatRect frameRect = m_page->chrome()->windowRect(); + frameRect.move(x, y); + m_page->chrome()->setWindowRect(frameRect); +} + +#if ENABLE(JAVASCRIPT_DEBUGGER) +void InspectorController::enableDebugger() +{ + if (!enabled()) + return; + + if (!m_scriptContext || !m_scriptObject) { + m_attachDebuggerWhenShown = true; + return; + } + + ASSERT(m_inspectedPage); + + JavaScriptDebugServer::shared().addListener(this, m_inspectedPage); + JavaScriptDebugServer::shared().clearBreakpoints(); + + m_debuggerEnabled = true; + m_attachDebuggerWhenShown = false; + + callSimpleFunction(m_scriptContext, m_scriptObject, "debuggerWasEnabled"); +} + +void InspectorController::disableDebugger() +{ + if (!enabled()) + return; + + ASSERT(m_inspectedPage); + + JavaScriptDebugServer::shared().removeListener(this, m_inspectedPage); + + m_debuggerEnabled = false; + m_attachDebuggerWhenShown = false; + + if (m_scriptContext && m_scriptObject) + callSimpleFunction(m_scriptContext, m_scriptObject, "debuggerWasDisabled"); +} + +JavaScriptCallFrame* InspectorController::currentCallFrame() const +{ + return JavaScriptDebugServer::shared().currentCallFrame(); +} + +bool InspectorController::pauseOnExceptions() +{ + return JavaScriptDebugServer::shared().pauseOnExceptions(); +} + +void InspectorController::setPauseOnExceptions(bool pause) +{ + JavaScriptDebugServer::shared().setPauseOnExceptions(pause); +} + +void InspectorController::pauseInDebugger() +{ + if (!m_debuggerEnabled) + return; + JavaScriptDebugServer::shared().pauseProgram(); +} + +void InspectorController::resumeDebugger() +{ + if (!m_debuggerEnabled) + return; + JavaScriptDebugServer::shared().continueProgram(); +} + +void InspectorController::stepOverStatementInDebugger() +{ + if (!m_debuggerEnabled) + return; + JavaScriptDebugServer::shared().stepOverStatement(); +} + +void InspectorController::stepIntoStatementInDebugger() +{ + if (!m_debuggerEnabled) + return; + JavaScriptDebugServer::shared().stepIntoStatement(); +} + +void InspectorController::stepOutOfFunctionInDebugger() +{ + if (!m_debuggerEnabled) + return; + JavaScriptDebugServer::shared().stepOutOfFunction(); +} + +void InspectorController::addBreakpoint(intptr_t sourceID, unsigned lineNumber) +{ + JavaScriptDebugServer::shared().addBreakpoint(sourceID, lineNumber); +} + +void InspectorController::removeBreakpoint(intptr_t sourceID, unsigned lineNumber) +{ + JavaScriptDebugServer::shared().removeBreakpoint(sourceID, lineNumber); +} +#endif + +static void drawOutlinedRect(GraphicsContext& context, const IntRect& rect, const Color& fillColor) +{ + static const int outlineThickness = 1; + static const Color outlineColor(62, 86, 180, 228); + + IntRect outline = rect; + outline.inflate(outlineThickness); + + context.clearRect(outline); + + context.save(); + context.clipOut(rect); + context.fillRect(outline, outlineColor); + context.restore(); + + context.fillRect(rect, fillColor); +} + +static void drawHighlightForBoxes(GraphicsContext& context, const Vector<IntRect>& lineBoxRects, const IntRect& contentBox, const IntRect& paddingBox, const IntRect& borderBox, const IntRect& marginBox) +{ + static const Color contentBoxColor(125, 173, 217, 128); + static const Color paddingBoxColor(125, 173, 217, 160); + static const Color borderBoxColor(125, 173, 217, 192); + static const Color marginBoxColor(125, 173, 217, 228); + + if (!lineBoxRects.isEmpty()) { + for (size_t i = 0; i < lineBoxRects.size(); ++i) + drawOutlinedRect(context, lineBoxRects[i], contentBoxColor); + return; + } + + if (marginBox != borderBox) + drawOutlinedRect(context, marginBox, marginBoxColor); + if (borderBox != paddingBox) + drawOutlinedRect(context, borderBox, borderBoxColor); + if (paddingBox != contentBox) + drawOutlinedRect(context, paddingBox, paddingBoxColor); + drawOutlinedRect(context, contentBox, contentBoxColor); +} + +static inline void convertFromFrameToMainFrame(Frame* frame, IntRect& rect) +{ + rect = frame->page()->mainFrame()->view()->windowToContents(frame->view()->contentsToWindow(rect)); +} + +void InspectorController::drawNodeHighlight(GraphicsContext& context) const +{ + if (!m_highlightedNode) + return; + + RenderObject* renderer = m_highlightedNode->renderer(); + Frame* containingFrame = m_highlightedNode->document()->frame(); + if (!renderer || !containingFrame) + return; + + IntRect contentBox = renderer->absoluteContentBox(); + IntRect boundingBox = renderer->absoluteBoundingBoxRect(); + + // FIXME: Should we add methods to RenderObject to obtain these rects? + IntRect paddingBox(contentBox.x() - renderer->paddingLeft(), contentBox.y() - renderer->paddingTop(), contentBox.width() + renderer->paddingLeft() + renderer->paddingRight(), contentBox.height() + renderer->paddingTop() + renderer->paddingBottom()); + IntRect borderBox(paddingBox.x() - renderer->borderLeft(), paddingBox.y() - renderer->borderTop(), paddingBox.width() + renderer->borderLeft() + renderer->borderRight(), paddingBox.height() + renderer->borderTop() + renderer->borderBottom()); + IntRect marginBox(borderBox.x() - renderer->marginLeft(), borderBox.y() - renderer->marginTop(), borderBox.width() + renderer->marginLeft() + renderer->marginRight(), borderBox.height() + renderer->marginTop() + renderer->marginBottom()); + + convertFromFrameToMainFrame(containingFrame, contentBox); + convertFromFrameToMainFrame(containingFrame, paddingBox); + convertFromFrameToMainFrame(containingFrame, borderBox); + convertFromFrameToMainFrame(containingFrame, marginBox); + convertFromFrameToMainFrame(containingFrame, boundingBox); + + Vector<IntRect> lineBoxRects; + if (renderer->isInline() || (renderer->isText() && !m_highlightedNode->isSVGElement())) { + // FIXME: We should show margins/padding/border for inlines. + renderer->addLineBoxRects(lineBoxRects); + } + + for (unsigned i = 0; i < lineBoxRects.size(); ++i) + convertFromFrameToMainFrame(containingFrame, lineBoxRects[i]); + + if (lineBoxRects.isEmpty() && contentBox.isEmpty()) { + // If we have no line boxes and our content box is empty, we'll just draw our bounding box. + // This can happen, e.g., with an <a> enclosing an <img style="float:right">. + // FIXME: Can we make this better/more accurate? The <a> in the above case has no + // width/height but the highlight makes it appear to be the size of the <img>. + lineBoxRects.append(boundingBox); + } + + ASSERT(m_inspectedPage); + + FrameView* view = m_inspectedPage->mainFrame()->view(); + FloatRect overlayRect = view->visibleContentRect(); + + if (!overlayRect.contains(boundingBox) && !boundingBox.contains(enclosingIntRect(overlayRect))) { + Element* element; + if (m_highlightedNode->isElementNode()) + element = static_cast<Element*>(m_highlightedNode.get()); + else + element = static_cast<Element*>(m_highlightedNode->parent()); + overlayRect = view->visibleContentRect(); + } + + context.translate(-overlayRect.x(), -overlayRect.y()); + + drawHighlightForBoxes(context, lineBoxRects, contentBox, paddingBox, borderBox, marginBox); +} + +void InspectorController::count(const UString& title, unsigned lineNumber, const String& sourceID) +{ + String identifier = String(title) + String::format("@%s:%d", sourceID.utf8().data(), lineNumber); + HashMap<String, unsigned>::iterator it = m_counts.find(identifier); + int count; + if (it == m_counts.end()) + count = 1; + else { + count = it->second + 1; + m_counts.remove(it); + } + + m_counts.add(identifier, count); + + String message = String::format("%s: %d", title.UTF8String().c_str(), count); + addMessageToConsole(JSMessageSource, LogMessageLevel, message, lineNumber, sourceID); +} + +void InspectorController::startTiming(const UString& title) +{ + m_times.add(title, currentTime() * 1000); +} + +bool InspectorController::stopTiming(const UString& title, double& elapsed) +{ + HashMap<String, double>::iterator it = m_times.find(title); + if (it == m_times.end()) + return false; + + double startTime = it->second; + m_times.remove(it); + + elapsed = currentTime() * 1000 - startTime; + return true; +} + +bool InspectorController::handleException(JSContextRef context, JSValueRef exception, unsigned lineNumber) const +{ + if (!exception) + return false; + + if (!m_page) + return true; + + String message = toString(context, exception, 0); + String file(__FILE__); + + if (JSObjectRef exceptionObject = JSValueToObject(context, exception, 0)) { + JSValueRef lineValue = JSObjectGetProperty(context, exceptionObject, jsStringRef("line").get(), NULL); + if (lineValue) + lineNumber = static_cast<unsigned>(JSValueToNumber(context, lineValue, 0)); + + JSValueRef fileValue = JSObjectGetProperty(context, exceptionObject, jsStringRef("sourceURL").get(), NULL); + if (fileValue) + file = toString(context, fileValue, 0); + } + + m_page->mainFrame()->domWindow()->console()->addMessage(JSMessageSource, ErrorMessageLevel, message, lineNumber, file); + return true; +} + +#if ENABLE(JAVASCRIPT_DEBUGGER) +// JavaScriptDebugListener functions + +void InspectorController::didParseSource(ExecState* exec, const SourceCode& source) +{ + JSValueRef sourceIDValue = JSValueMakeNumber(m_scriptContext, source.provider()->asID()); + JSValueRef sourceURLValue = JSValueMakeString(m_scriptContext, jsStringRef(source.provider()->url()).get()); + JSValueRef sourceValue = JSValueMakeString(m_scriptContext, jsStringRef(source).get()); + JSValueRef firstLineValue = JSValueMakeNumber(m_scriptContext, source.firstLine()); + + JSValueRef exception = 0; + JSValueRef arguments[] = { sourceIDValue, sourceURLValue, sourceValue, firstLineValue }; + callFunction(m_scriptContext, m_scriptObject, "parsedScriptSource", 4, arguments, exception); +} + +void InspectorController::failedToParseSource(ExecState* exec, const SourceCode& source, int errorLine, const UString& errorMessage) +{ + JSValueRef sourceURLValue = JSValueMakeString(m_scriptContext, jsStringRef(source.provider()->url()).get()); + JSValueRef sourceValue = JSValueMakeString(m_scriptContext, jsStringRef(source.data()).get()); + JSValueRef firstLineValue = JSValueMakeNumber(m_scriptContext, source.firstLine()); + JSValueRef errorLineValue = JSValueMakeNumber(m_scriptContext, errorLine); + JSValueRef errorMessageValue = JSValueMakeString(m_scriptContext, jsStringRef(errorMessage).get()); + + JSValueRef exception = 0; + JSValueRef arguments[] = { sourceURLValue, sourceValue, firstLineValue, errorLineValue, errorMessageValue }; + callFunction(m_scriptContext, m_scriptObject, "failedToParseScriptSource", 5, arguments, exception); +} + +void InspectorController::didPause() +{ + JSValueRef exception = 0; + callFunction(m_scriptContext, m_scriptObject, "pausedScript", 0, 0, exception); +} +#endif + +} // namespace WebCore diff --git a/WebCore/inspector/InspectorController.h b/WebCore/inspector/InspectorController.h new file mode 100644 index 0000000..62f3bdb --- /dev/null +++ b/WebCore/inspector/InspectorController.h @@ -0,0 +1,327 @@ +/* + * 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. + */ + +#ifndef InspectorController_h +#define InspectorController_h + +#if ENABLE(JAVASCRIPT_DEBUGGER) +#include "JavaScriptDebugListener.h" +#endif + +#include "Console.h" +#include "PlatformString.h" +#include "StringHash.h" +#include "Timer.h" + +#include <JavaScriptCore/JSContextRef.h> + +#include <wtf/HashMap.h> +#include <wtf/HashSet.h> +#include <wtf/Vector.h> + +namespace JSC { + class Profile; + class UString; +} + +namespace WebCore { + +class Database; +class DocumentLoader; +class GraphicsContext; +class HitTestResult; +class InspectorClient; +class JavaScriptCallFrame; +class Node; +class Page; +class ResourceResponse; +class ResourceError; +class SharedBuffer; + +struct ConsoleMessage; +struct InspectorDatabaseResource; +struct InspectorResource; +class ResourceRequest; + +class InspectorController +#if ENABLE(JAVASCRIPT_DEBUGGER) + : JavaScriptDebugListener +#endif + { +public: + typedef HashMap<long long, RefPtr<InspectorResource> > ResourcesMap; + typedef HashMap<RefPtr<Frame>, ResourcesMap*> FrameResourcesMap; + typedef HashSet<RefPtr<InspectorDatabaseResource> > DatabaseResourcesSet; + + typedef enum { + CurrentPanel, + ConsolePanel, + DatabasesPanel, + ElementsPanel, + ProfilesPanel, + ResourcesPanel, + ScriptsPanel + } SpecialPanels; + + struct Setting { + enum Type { + NoType, StringType, StringVectorType, DoubleType, IntegerType, BooleanType + }; + + Setting() + : m_type(NoType) + { + } + + Type type() const { return m_type; } + + String string() const { ASSERT(m_type == StringType); return m_string; } + const Vector<String>& stringVector() const { ASSERT(m_type == StringVectorType); return m_stringVector; } + double doubleValue() const { ASSERT(m_type == DoubleType); return m_simpleContent.m_double; } + long integerValue() const { ASSERT(m_type == IntegerType); return m_simpleContent.m_integer; } + bool booleanValue() const { ASSERT(m_type == BooleanType); return m_simpleContent.m_boolean; } + + void set(const String& value) { m_type = StringType; m_string = value; } + void set(const Vector<String>& value) { m_type = StringVectorType; m_stringVector = value; } + void set(double value) { m_type = DoubleType; m_simpleContent.m_double = value; } + void set(long value) { m_type = IntegerType; m_simpleContent.m_integer = value; } + void set(bool value) { m_type = BooleanType; m_simpleContent.m_boolean = value; } + + private: + Type m_type; + + String m_string; + Vector<String> m_stringVector; + + union { + double m_double; + long m_integer; + bool m_boolean; + } m_simpleContent; + }; + + InspectorController(Page*, InspectorClient*); + ~InspectorController(); + + void inspectedPageDestroyed(); + void pageDestroyed() { m_page = 0; } + + bool enabled() const; + + Page* inspectedPage() const { return m_inspectedPage; } + + const Setting& setting(const String& key) const; + void setSetting(const String& key, const Setting&); + + String localizedStringsURL(); + + void inspect(Node*); + void highlight(Node*); + void hideHighlight(); + + void show(); + void showPanel(SpecialPanels); + void close(); + + bool isRecordingUserInitiatedProfile() const { return m_recordingUserInitiatedProfile; } + void startUserInitiatedProfilingSoon(); + void startUserInitiatedProfiling(Timer<InspectorController>* = 0); + void stopUserInitiatedProfiling(); + + void enableProfiler(bool skipRecompile = false); + void disableProfiler(); + bool profilerEnabled() const { return enabled() && m_profilerEnabled; } + + bool windowVisible(); + void setWindowVisible(bool visible = true, bool attached = false); + + void addMessageToConsole(MessageSource, MessageLevel, JSC::ExecState*, const JSC::ArgList& arguments, unsigned lineNumber, const String& sourceID); + void addMessageToConsole(MessageSource, MessageLevel, const String& message, unsigned lineNumber, const String& sourceID); + void clearConsoleMessages(); + void toggleRecordButton(bool); + + void addProfile(PassRefPtr<JSC::Profile>, unsigned lineNumber, const JSC::UString& sourceURL); + void addProfileMessageToConsole(PassRefPtr<JSC::Profile> prpProfile, unsigned lineNumber, const JSC::UString& sourceURL); + void addScriptProfile(JSC::Profile* profile); + const ProfilesArray& profiles() const { return m_profiles; } + + void attachWindow(); + void detachWindow(); + + void setAttachedWindow(bool); + void setAttachedWindowHeight(unsigned height); + + void toggleSearchForNodeInPage(); + bool searchingForNodeInPage() { return m_searchingForNode; }; + void mouseDidMoveOverElement(const HitTestResult&, unsigned modifierFlags); + void handleMousePressOnNode(Node*); + + JSContextRef scriptContext() const { return m_scriptContext; }; + void setScriptContext(JSContextRef context) { m_scriptContext = context; }; + + void inspectedWindowScriptObjectCleared(Frame*); + void windowScriptObjectAvailable(); + + void scriptObjectReady(); + + void populateScriptObjects(); + void resetScriptObjects(); + + void didCommitLoad(DocumentLoader*); + void frameDetachedFromParent(Frame*); + + void didLoadResourceFromMemoryCache(DocumentLoader*, const ResourceRequest&, const ResourceResponse&, int length); + + void identifierForInitialRequest(unsigned long identifier, DocumentLoader*, const ResourceRequest&); + void willSendRequest(DocumentLoader*, unsigned long identifier, ResourceRequest&, const ResourceResponse& redirectResponse); + void didReceiveResponse(DocumentLoader*, unsigned long identifier, const ResourceResponse&); + void didReceiveContentLength(DocumentLoader*, unsigned long identifier, int lengthReceived); + void didFinishLoading(DocumentLoader*, unsigned long identifier); + void didFailLoading(DocumentLoader*, unsigned long identifier, const ResourceError&); + void resourceRetrievedByXMLHttpRequest(unsigned long identifier, JSC::UString& sourceString); + +#if ENABLE(DATABASE) + void didOpenDatabase(Database*, const String& domain, const String& name, const String& version); +#endif + + const ResourcesMap& resources() const { return m_resources; } + + void moveWindowBy(float x, float y) const; + void closeWindow(); + +#if ENABLE(JAVASCRIPT_DEBUGGER) + void enableDebugger(); + void disableDebugger(); + bool debuggerEnabled() const { return m_debuggerEnabled; } + + JavaScriptCallFrame* currentCallFrame() const; + + void addBreakpoint(intptr_t sourceID, unsigned lineNumber); + void removeBreakpoint(intptr_t sourceID, unsigned lineNumber); + + bool pauseOnExceptions(); + void setPauseOnExceptions(bool pause); + + void pauseInDebugger(); + void resumeDebugger(); + + void stepOverStatementInDebugger(); + void stepIntoStatementInDebugger(); + void stepOutOfFunctionInDebugger(); +#endif + + void drawNodeHighlight(GraphicsContext&) const; + + void count(const JSC::UString& title, unsigned lineNumber, const String& sourceID); + + void startTiming(const JSC::UString& title); + bool stopTiming(const JSC::UString& title, double& elapsed); + + void startGroup(MessageSource source, JSC::ExecState* exec, const JSC::ArgList& arguments, unsigned lineNumber, const String& sourceURL); + void endGroup(MessageSource source, unsigned lineNumber, const String& sourceURL); + +private: + void focusNode(); + + void addConsoleMessage(JSC::ExecState*, ConsoleMessage*); + void addScriptConsoleMessage(const ConsoleMessage*); + + void addResource(InspectorResource*); + void removeResource(InspectorResource*); + + JSObjectRef addScriptResource(InspectorResource*); + void removeScriptResource(InspectorResource*); + + JSObjectRef addAndUpdateScriptResource(InspectorResource*); + void updateScriptResourceRequest(InspectorResource*); + void updateScriptResourceResponse(InspectorResource*); + void updateScriptResourceType(InspectorResource*); + void updateScriptResource(InspectorResource*, int length); + void updateScriptResource(InspectorResource*, bool finished, bool failed = false); + void updateScriptResource(InspectorResource*, double startTime, double responseReceivedTime, double endTime); + + void pruneResources(ResourcesMap*, DocumentLoader* loaderToKeep = 0); + void removeAllResources(ResourcesMap* map) { pruneResources(map); } + +#if ENABLE(DATABASE) + JSObjectRef addDatabaseScriptResource(InspectorDatabaseResource*); + void removeDatabaseScriptResource(InspectorDatabaseResource*); +#endif + + JSValueRef callSimpleFunction(JSContextRef, JSObjectRef thisObject, const char* functionName) const; + JSValueRef callFunction(JSContextRef, JSObjectRef thisObject, const char* functionName, size_t argumentCount, const JSValueRef arguments[], JSValueRef& exception) const; + + bool handleException(JSContextRef, JSValueRef exception, unsigned lineNumber) const; + + void showWindow(); + +#if ENABLE(JAVASCRIPT_DEBUGGER) + virtual void didParseSource(JSC::ExecState*, const JSC::SourceCode&); + virtual void failedToParseSource(JSC::ExecState*, const JSC::SourceCode&, int errorLine, const JSC::UString& errorMessage); + virtual void didPause(); +#endif + + Page* m_inspectedPage; + InspectorClient* m_client; + Page* m_page; + RefPtr<Node> m_nodeToFocus; + RefPtr<InspectorResource> m_mainResource; + ResourcesMap m_resources; + HashSet<String> m_knownResources; + FrameResourcesMap m_frameResources; + Vector<ConsoleMessage*> m_consoleMessages; + ProfilesArray m_profiles; + HashMap<String, double> m_times; + HashMap<String, unsigned> m_counts; +#if ENABLE(DATABASE) + DatabaseResourcesSet m_databaseResources; +#endif + JSObjectRef m_scriptObject; + JSObjectRef m_controllerScriptObject; + JSContextRef m_scriptContext; + bool m_windowVisible; +#if ENABLE(JAVASCRIPT_DEBUGGER) + bool m_debuggerEnabled; + bool m_attachDebuggerWhenShown; +#endif + bool m_profilerEnabled; + bool m_recordingUserInitiatedProfile; + SpecialPanels m_showAfterVisible; + long long m_nextIdentifier; + RefPtr<Node> m_highlightedNode; + unsigned m_groupLevel; + bool m_searchingForNode; + int m_currentUserInitiatedProfileNumber; + unsigned m_nextUserInitiatedProfileNumber; + ConsoleMessage* m_previousMessage; + Timer<InspectorController> m_startProfiling; +}; + +} // namespace WebCore + +#endif // !defined(InspectorController_h) diff --git a/WebCore/inspector/JavaScriptCallFrame.cpp b/WebCore/inspector/JavaScriptCallFrame.cpp new file mode 100644 index 0000000..e87880b --- /dev/null +++ b/WebCore/inspector/JavaScriptCallFrame.cpp @@ -0,0 +1,101 @@ +/* + * 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. + */ + +#include "config.h" +#include "JavaScriptCallFrame.h" + +#include "PlatformString.h" +#include <debugger/DebuggerCallFrame.h> +#include <runtime/JSGlobalObject.h> +#include <kjs/interpreter.h> +#include <runtime/JSLock.h> +#include <runtime/JSObject.h> +#include <runtime/JSValue.h> + +using namespace JSC; + +namespace WebCore { + +JavaScriptCallFrame::JavaScriptCallFrame(const DebuggerCallFrame& debuggerCallFrame, PassRefPtr<JavaScriptCallFrame> caller, intptr_t sourceID, int line) + : m_debuggerCallFrame(debuggerCallFrame) + , m_caller(caller) + , m_sourceID(sourceID) + , m_line(line) + , m_isValid(true) +{ +} + +JavaScriptCallFrame* JavaScriptCallFrame::caller() +{ + return m_caller.get(); +} + +const JSC::ScopeChainNode* JavaScriptCallFrame::scopeChain() const +{ + ASSERT(m_isValid); + if (!m_isValid) + return 0; + return m_debuggerCallFrame.scopeChain(); +} + +String JavaScriptCallFrame::functionName() const +{ + ASSERT(m_isValid); + if (!m_isValid) + return String(); + const UString* functionName = m_debuggerCallFrame.functionName(); + if (!functionName) + return String(); + return *functionName; +} + +DebuggerCallFrame::Type JavaScriptCallFrame::type() const +{ + ASSERT(m_isValid); + if (!m_isValid) + return DebuggerCallFrame::ProgramType; + return m_debuggerCallFrame.type(); +} + +JSObject* JavaScriptCallFrame::thisObject() const +{ + ASSERT(m_isValid); + if (!m_isValid) + return 0; + return m_debuggerCallFrame.thisObject(); +} + +// Evaluate some JavaScript code in the scope of this frame. +JSValue* JavaScriptCallFrame::evaluate(const UString& script, JSValue*& exception) const +{ + ASSERT(m_isValid); + if (!m_isValid) + return jsNull(); + + JSLock lock(false); + return m_debuggerCallFrame.evaluate(script, exception); +} + +} // namespace WebCore diff --git a/WebCore/inspector/JavaScriptCallFrame.h b/WebCore/inspector/JavaScriptCallFrame.h new file mode 100644 index 0000000..7b94fa8 --- /dev/null +++ b/WebCore/inspector/JavaScriptCallFrame.h @@ -0,0 +1,78 @@ +/* + * 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. + */ + +#ifndef JavaScriptCallFrame_h +#define JavaScriptCallFrame_h + +#include <runtime/ExecState.h> +#include <wtf/PassRefPtr.h> +#include <wtf/RefCounted.h> +#include <debugger/DebuggerCallFrame.h> + +namespace WebCore { + + class String; + + class JavaScriptCallFrame : public RefCounted<JavaScriptCallFrame> { + public: + static PassRefPtr<JavaScriptCallFrame> create(const JSC::DebuggerCallFrame& debuggerCallFrame, PassRefPtr<JavaScriptCallFrame> caller, intptr_t sourceID, int line) + { + return adoptRef(new JavaScriptCallFrame(debuggerCallFrame, caller, sourceID, line)); + } + + void invalidate() { m_isValid = false; } + bool isValid() const { return m_isValid; } + + JavaScriptCallFrame* caller(); + + intptr_t sourceID() const { return m_sourceID; } + int line() const { return m_line; } + void update(const JSC::DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int line) + { + m_debuggerCallFrame = debuggerCallFrame; + m_line = line; + m_sourceID = sourceID; + } + + String functionName() const; + JSC::DebuggerCallFrame::Type type() const; + const JSC::ScopeChainNode* scopeChain() const; + + JSC::JSObject* thisObject() const; + JSC::JSValue* evaluate(const JSC::UString& script, JSC::JSValue*& exception) const; + + private: + JavaScriptCallFrame(const JSC::DebuggerCallFrame&, PassRefPtr<JavaScriptCallFrame> caller, intptr_t sourceID, int line); + + JSC::DebuggerCallFrame m_debuggerCallFrame; + RefPtr<JavaScriptCallFrame> m_caller; + int m_sourceID; + int m_line; + bool m_isValid; + }; + +} // namespace WebCore + +#endif // JavaScriptCallFrame_h diff --git a/WebCore/inspector/JavaScriptCallFrame.idl b/WebCore/inspector/JavaScriptCallFrame.idl new file mode 100644 index 0000000..96e4c6e --- /dev/null +++ b/WebCore/inspector/JavaScriptCallFrame.idl @@ -0,0 +1,40 @@ +/* + * 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. + */ + +module inspector { + + interface JavaScriptCallFrame { + [Custom] void evaluate(in DOMString script); + + readonly attribute JavaScriptCallFrame caller; + readonly attribute long sourceID; + readonly attribute long line; + readonly attribute [CustomGetter] Array scopeChain; + readonly attribute [CustomGetter] Object thisObject; + readonly attribute DOMString functionName; + readonly attribute [CustomGetter] DOMString type; + }; + +} diff --git a/WebCore/inspector/JavaScriptDebugListener.h b/WebCore/inspector/JavaScriptDebugListener.h new file mode 100644 index 0000000..f1cd77f --- /dev/null +++ b/WebCore/inspector/JavaScriptDebugListener.h @@ -0,0 +1,54 @@ +/* + * 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. + */ + +#ifndef JavaScriptDebugListener_h +#define JavaScriptDebugListener_h + +namespace JSC { + class ExecState; + class SourceCode; + class UString; +} + +namespace WebCore { + + class Frame; + class Page; + + class JavaScriptDebugListener { + public: + virtual ~JavaScriptDebugListener() { } + + virtual void didParseSource(JSC::ExecState*, const JSC::SourceCode& source) = 0; + virtual void failedToParseSource(JSC::ExecState*, const JSC::SourceCode& source, int errorLine, const JSC::UString& errorMessage) = 0; + virtual void didPause() = 0; + }; + +} // namespace WebCore + +#endif // JavaScriptDebugListener_h diff --git a/WebCore/inspector/JavaScriptDebugServer.cpp b/WebCore/inspector/JavaScriptDebugServer.cpp new file mode 100644 index 0000000..ed9ae85 --- /dev/null +++ b/WebCore/inspector/JavaScriptDebugServer.cpp @@ -0,0 +1,617 @@ +/* + * 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. + */ + +#include "config.h" +#include "JavaScriptDebugServer.h" + +#include "DOMWindow.h" +#include "EventLoop.h" +#include "Frame.h" +#include "FrameTree.h" +#include "FrameView.h" +#include "JSDOMWindowCustom.h" +#include "JavaScriptCallFrame.h" +#include "JavaScriptDebugListener.h" +#include "Page.h" +#include "PageGroup.h" +#include "PausedTimeouts.h" +#include "PluginView.h" +#include "ScrollView.h" +#include "Widget.h" +#include "ScriptController.h" +#include <runtime/CollectorHeapIterator.h> +#include <debugger/DebuggerCallFrame.h> +#include <runtime/JSLock.h> +#include <kjs/Parser.h> +#include <wtf/MainThread.h> +#include <wtf/UnusedParam.h> + +using namespace JSC; + +namespace WebCore { + +typedef JavaScriptDebugServer::ListenerSet ListenerSet; + +JavaScriptDebugServer& JavaScriptDebugServer::shared() +{ + static JavaScriptDebugServer server; + return server; +} + +JavaScriptDebugServer::JavaScriptDebugServer() + : m_callingListeners(false) + , m_pauseOnExceptions(false) + , m_pauseOnNextStatement(false) + , m_paused(false) + , m_doneProcessingDebuggerEvents(true) + , m_pauseOnCallFrame(0) + , m_recompileTimer(this, &JavaScriptDebugServer::recompileAllJSFunctions) +{ +} + +JavaScriptDebugServer::~JavaScriptDebugServer() +{ + deleteAllValues(m_pageListenersMap); + deleteAllValues(m_breakpoints); + deleteAllValues(m_pausedTimeouts); +} + +void JavaScriptDebugServer::addListener(JavaScriptDebugListener* listener) +{ + ASSERT_ARG(listener, listener); + + m_listeners.add(listener); + + didAddListener(0); +} + +void JavaScriptDebugServer::removeListener(JavaScriptDebugListener* listener) +{ + ASSERT_ARG(listener, listener); + + m_listeners.remove(listener); + + didRemoveListener(0); + if (!hasListeners()) + didRemoveLastListener(); +} + +void JavaScriptDebugServer::addListener(JavaScriptDebugListener* listener, Page* page) +{ + ASSERT_ARG(listener, listener); + ASSERT_ARG(page, page); + + pair<PageListenersMap::iterator, bool> result = m_pageListenersMap.add(page, 0); + if (result.second) + result.first->second = new ListenerSet; + + ListenerSet* listeners = result.first->second; + listeners->add(listener); + + didAddListener(page); +} + +void JavaScriptDebugServer::removeListener(JavaScriptDebugListener* listener, Page* page) +{ + ASSERT_ARG(listener, listener); + ASSERT_ARG(page, page); + + PageListenersMap::iterator it = m_pageListenersMap.find(page); + if (it == m_pageListenersMap.end()) + return; + + ListenerSet* listeners = it->second; + listeners->remove(listener); + if (listeners->isEmpty()) { + m_pageListenersMap.remove(it); + delete listeners; + } + + didRemoveListener(page); + if (!hasListeners()) + didRemoveLastListener(); +} + +void JavaScriptDebugServer::pageCreated(Page* page) +{ + ASSERT_ARG(page, page); + + if (!hasListenersInterestedInPage(page)) + return; + page->setDebugger(this); +} + +bool JavaScriptDebugServer::hasListenersInterestedInPage(Page* page) +{ + ASSERT_ARG(page, page); + + if (hasGlobalListeners()) + return true; + + return m_pageListenersMap.contains(page); +} + +void JavaScriptDebugServer::addBreakpoint(intptr_t sourceID, unsigned lineNumber) +{ + HashSet<unsigned>* lines = m_breakpoints.get(sourceID); + if (!lines) { + lines = new HashSet<unsigned>; + m_breakpoints.set(sourceID, lines); + } + + lines->add(lineNumber); +} + +void JavaScriptDebugServer::removeBreakpoint(intptr_t sourceID, unsigned lineNumber) +{ + HashSet<unsigned>* lines = m_breakpoints.get(sourceID); + if (!lines) + return; + + lines->remove(lineNumber); + + if (!lines->isEmpty()) + return; + + m_breakpoints.remove(sourceID); + delete lines; +} + +bool JavaScriptDebugServer::hasBreakpoint(intptr_t sourceID, unsigned lineNumber) const +{ + HashSet<unsigned>* lines = m_breakpoints.get(sourceID); + if (!lines) + return false; + return lines->contains(lineNumber); +} + +void JavaScriptDebugServer::clearBreakpoints() +{ + deleteAllValues(m_breakpoints); + m_breakpoints.clear(); +} + +void JavaScriptDebugServer::setPauseOnExceptions(bool pause) +{ + m_pauseOnExceptions = pause; +} + +void JavaScriptDebugServer::pauseProgram() +{ + m_pauseOnNextStatement = true; +} + +void JavaScriptDebugServer::continueProgram() +{ + if (!m_paused) + return; + + m_pauseOnNextStatement = false; + m_doneProcessingDebuggerEvents = true; +} + +void JavaScriptDebugServer::stepIntoStatement() +{ + if (!m_paused) + return; + + m_pauseOnNextStatement = true; + m_doneProcessingDebuggerEvents = true; +} + +void JavaScriptDebugServer::stepOverStatement() +{ + if (!m_paused) + return; + + m_pauseOnCallFrame = m_currentCallFrame.get(); + m_doneProcessingDebuggerEvents = true; +} + +void JavaScriptDebugServer::stepOutOfFunction() +{ + if (!m_paused) + return; + + m_pauseOnCallFrame = m_currentCallFrame ? m_currentCallFrame->caller() : 0; + m_doneProcessingDebuggerEvents = true; +} + +JavaScriptCallFrame* JavaScriptDebugServer::currentCallFrame() +{ + if (!m_paused) + return 0; + return m_currentCallFrame.get(); +} + +static void dispatchDidParseSource(const ListenerSet& listeners, ExecState* exec, const JSC::SourceCode& source) +{ + Vector<JavaScriptDebugListener*> copy; + copyToVector(listeners, copy); + for (size_t i = 0; i < copy.size(); ++i) + copy[i]->didParseSource(exec, source); +} + +static void dispatchFailedToParseSource(const ListenerSet& listeners, ExecState* exec, const SourceCode& source, int errorLine, const String& errorMessage) +{ + Vector<JavaScriptDebugListener*> copy; + copyToVector(listeners, copy); + for (size_t i = 0; i < copy.size(); ++i) + copy[i]->failedToParseSource(exec, source, errorLine, errorMessage); +} + +static Page* toPage(JSGlobalObject* globalObject) +{ + ASSERT_ARG(globalObject, globalObject); + + JSDOMWindow* window = asJSDOMWindow(globalObject); + Frame* frame = window->impl()->frame(); + return frame ? frame->page() : 0; +} + +void JavaScriptDebugServer::sourceParsed(ExecState* exec, const SourceCode& source, int errorLine, const UString& errorMessage) +{ + if (m_callingListeners) + return; + + Page* page = toPage(exec->dynamicGlobalObject()); + if (!page) + return; + + m_callingListeners = true; + + ASSERT(hasListeners()); + + bool isError = errorLine != -1; + + if (hasGlobalListeners()) { + if (isError) + dispatchFailedToParseSource(m_listeners, exec, source, errorLine, errorMessage); + else + dispatchDidParseSource(m_listeners, exec, source); + } + + if (ListenerSet* pageListeners = m_pageListenersMap.get(page)) { + ASSERT(!pageListeners->isEmpty()); + if (isError) + dispatchFailedToParseSource(*pageListeners, exec, source, errorLine, errorMessage); + else + dispatchDidParseSource(*pageListeners, exec, source); + } + + m_callingListeners = false; +} + +static void dispatchFunctionToListeners(const ListenerSet& listeners, JavaScriptDebugServer::JavaScriptExecutionCallback callback) +{ + Vector<JavaScriptDebugListener*> copy; + copyToVector(listeners, copy); + for (size_t i = 0; i < copy.size(); ++i) + (copy[i]->*callback)(); +} + +void JavaScriptDebugServer::dispatchFunctionToListeners(JavaScriptExecutionCallback callback, Page* page) +{ + if (m_callingListeners) + return; + + m_callingListeners = true; + + ASSERT(hasListeners()); + + WebCore::dispatchFunctionToListeners(m_listeners, callback); + + if (ListenerSet* pageListeners = m_pageListenersMap.get(page)) { + ASSERT(!pageListeners->isEmpty()); + WebCore::dispatchFunctionToListeners(*pageListeners, callback); + } + + m_callingListeners = false; +} + +void JavaScriptDebugServer::setJavaScriptPaused(const PageGroup& pageGroup, bool paused) +{ + setMainThreadCallbacksPaused(paused); + + const HashSet<Page*>& pages = pageGroup.pages(); + + HashSet<Page*>::const_iterator end = pages.end(); + for (HashSet<Page*>::const_iterator it = pages.begin(); it != end; ++it) + setJavaScriptPaused(*it, paused); +} + +void JavaScriptDebugServer::setJavaScriptPaused(Page* page, bool paused) +{ + ASSERT_ARG(page, page); + + page->setDefersLoading(paused); + + for (Frame* frame = page->mainFrame(); frame; frame = frame->tree()->traverseNext()) + setJavaScriptPaused(frame, paused); +} + +void JavaScriptDebugServer::setJavaScriptPaused(Frame* frame, bool paused) +{ + ASSERT_ARG(frame, frame); + + if (!frame->script()->isEnabled()) + return; + + frame->script()->setPaused(paused); + + if (JSDOMWindow* window = toJSDOMWindow(frame)) { + if (paused) { + OwnPtr<PausedTimeouts> timeouts; + window->pauseTimeouts(timeouts); + m_pausedTimeouts.set(frame, timeouts.release()); + } else { + OwnPtr<PausedTimeouts> timeouts(m_pausedTimeouts.take(frame)); + window->resumeTimeouts(timeouts); + } + } + + setJavaScriptPaused(frame->view(), paused); +} + +void JavaScriptDebugServer::setJavaScriptPaused(FrameView* view, bool paused) +{ +#if !PLATFORM(MAC) + if (!view) + return; + + const HashSet<Widget*>* children = view->children(); + ASSERT(children); + + HashSet<Widget*>::const_iterator end = children->end(); + for (HashSet<Widget*>::const_iterator it = children->begin(); it != end; ++it) { + Widget* widget = *it; + if (!widget->isPluginView()) + continue; + static_cast<PluginView*>(widget)->setJavaScriptPaused(paused); + } +#endif +} + +void JavaScriptDebugServer::pauseIfNeeded(Page* page) +{ + if (m_paused) + return; + + if (!page || !hasListenersInterestedInPage(page)) + return; + + bool pauseNow = m_pauseOnNextStatement; + pauseNow |= (m_pauseOnCallFrame == m_currentCallFrame); + pauseNow |= (m_currentCallFrame->line() > 0 && hasBreakpoint(m_currentCallFrame->sourceID(), m_currentCallFrame->line())); + if (!pauseNow) + return; + + m_pauseOnCallFrame = 0; + m_pauseOnNextStatement = false; + m_paused = true; + + dispatchFunctionToListeners(&JavaScriptDebugListener::didPause, page); + + setJavaScriptPaused(page->group(), true); + + TimerBase::fireTimersInNestedEventLoop(); + + EventLoop loop; + m_doneProcessingDebuggerEvents = false; + while (!m_doneProcessingDebuggerEvents && !loop.ended()) + loop.cycle(); + + setJavaScriptPaused(page->group(), false); + + m_paused = false; +} + +void JavaScriptDebugServer::callEvent(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber) +{ + if (m_paused) + return; + + m_currentCallFrame = JavaScriptCallFrame::create(debuggerCallFrame, m_currentCallFrame, sourceID, lineNumber); + pauseIfNeeded(toPage(debuggerCallFrame.dynamicGlobalObject())); +} + +void JavaScriptDebugServer::atStatement(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber) +{ + if (m_paused) + return; + + ASSERT(m_currentCallFrame); + if (!m_currentCallFrame) + return; + + m_currentCallFrame->update(debuggerCallFrame, sourceID, lineNumber); + pauseIfNeeded(toPage(debuggerCallFrame.dynamicGlobalObject())); +} + +void JavaScriptDebugServer::returnEvent(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber) +{ + if (m_paused) + return; + + ASSERT(m_currentCallFrame); + if (!m_currentCallFrame) + return; + + m_currentCallFrame->update(debuggerCallFrame, sourceID, lineNumber); + pauseIfNeeded(toPage(debuggerCallFrame.dynamicGlobalObject())); + + // Treat stepping over a return statement like stepping out. + if (m_currentCallFrame == m_pauseOnCallFrame) + m_pauseOnCallFrame = m_currentCallFrame->caller(); + m_currentCallFrame = m_currentCallFrame->caller(); +} + +void JavaScriptDebugServer::exception(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber) +{ + if (m_paused) + return; + + ASSERT(m_currentCallFrame); + if (!m_currentCallFrame) + return; + + if (m_pauseOnExceptions) + m_pauseOnNextStatement = true; + + m_currentCallFrame->update(debuggerCallFrame, sourceID, lineNumber); + pauseIfNeeded(toPage(debuggerCallFrame.dynamicGlobalObject())); +} + +void JavaScriptDebugServer::willExecuteProgram(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber) +{ + if (m_paused) + return; + + m_currentCallFrame = JavaScriptCallFrame::create(debuggerCallFrame, m_currentCallFrame, sourceID, lineNumber); + pauseIfNeeded(toPage(debuggerCallFrame.dynamicGlobalObject())); +} + +void JavaScriptDebugServer::didExecuteProgram(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber) +{ + if (m_paused) + return; + + ASSERT(m_currentCallFrame); + if (!m_currentCallFrame) + return; + + m_currentCallFrame->update(debuggerCallFrame, sourceID, lineNumber); + pauseIfNeeded(toPage(debuggerCallFrame.dynamicGlobalObject())); + + // Treat stepping over the end of a program like stepping out. + if (m_currentCallFrame == m_pauseOnCallFrame) + m_pauseOnCallFrame = m_currentCallFrame->caller(); + m_currentCallFrame = m_currentCallFrame->caller(); +} + +void JavaScriptDebugServer::didReachBreakpoint(const DebuggerCallFrame& debuggerCallFrame, intptr_t sourceID, int lineNumber) +{ + if (m_paused) + return; + + ASSERT(m_currentCallFrame); + if (!m_currentCallFrame) + return; + + m_pauseOnNextStatement = true; + m_currentCallFrame->update(debuggerCallFrame, sourceID, lineNumber); + pauseIfNeeded(toPage(debuggerCallFrame.dynamicGlobalObject())); +} + +void JavaScriptDebugServer::recompileAllJSFunctionsSoon() +{ + m_recompileTimer.startOneShot(0); +} + +void JavaScriptDebugServer::recompileAllJSFunctions(Timer<JavaScriptDebugServer>*) +{ + JSLock lock(false); + JSGlobalData* globalData = JSDOMWindow::commonJSGlobalData(); + + // If JavaScript is running, it's not safe to recompile, since we'll end + // up throwing away code that is live on the stack. + ASSERT(!globalData->dynamicGlobalObject); + if (globalData->dynamicGlobalObject) + return; + + Vector<ProtectedPtr<JSFunction> > functions; + Heap::iterator heapEnd = globalData->heap.primaryHeapEnd(); + for (Heap::iterator it = globalData->heap.primaryHeapBegin(); it != heapEnd; ++it) { + if ((*it)->isObject(&JSFunction::info)) + functions.append(static_cast<JSFunction*>(*it)); + } + + typedef HashMap<RefPtr<FunctionBodyNode>, RefPtr<FunctionBodyNode> > FunctionBodyMap; + typedef HashSet<SourceProvider*> SourceProviderSet; + + FunctionBodyMap functionBodies; + SourceProviderSet sourceProviders; + + size_t size = functions.size(); + for (size_t i = 0; i < size; ++i) { + JSFunction* function = functions[i]; + + FunctionBodyNode* oldBody = function->m_body.get(); + pair<FunctionBodyMap::iterator, bool> result = functionBodies.add(oldBody, 0); + if (!result.second) { + function->m_body = result.first->second; + continue; + } + + ExecState* exec = function->scope().globalObject()->JSGlobalObject::globalExec(); + const SourceCode& sourceCode = oldBody->source(); + + RefPtr<FunctionBodyNode> newBody = globalData->parser->parse<FunctionBodyNode>(exec, 0, sourceCode); + ASSERT(newBody); + newBody->finishParsing(oldBody->copyParameters(), oldBody->parameterCount()); + + result.first->second = newBody; + function->m_body = newBody.release(); + + if (hasListeners()) { + SourceProvider* provider = sourceCode.provider(); + if (sourceProviders.add(provider).second) + sourceParsed(exec, SourceCode(provider), -1, 0); + } + } +} + +void JavaScriptDebugServer::didAddListener(Page* page) +{ + recompileAllJSFunctionsSoon(); + + if (page) + page->setDebugger(this); + else + Page::setDebuggerForAllPages(this); +} + +void JavaScriptDebugServer::didRemoveListener(Page* page) +{ + if (hasGlobalListeners() || (page && hasListenersInterestedInPage(page))) + return; + + recompileAllJSFunctionsSoon(); + + if (page) + page->setDebugger(0); + else + Page::setDebuggerForAllPages(0); +} + +void JavaScriptDebugServer::didRemoveLastListener() +{ + m_doneProcessingDebuggerEvents = true; +} + +} // namespace WebCore diff --git a/WebCore/inspector/JavaScriptDebugServer.h b/WebCore/inspector/JavaScriptDebugServer.h new file mode 100644 index 0000000..f3ee5a4 --- /dev/null +++ b/WebCore/inspector/JavaScriptDebugServer.h @@ -0,0 +1,132 @@ +/* + * 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. + */ + +#ifndef JavaScriptDebugServer_h +#define JavaScriptDebugServer_h + +#include "Timer.h" +#include <debugger/Debugger.h> +#include <wtf/HashMap.h> +#include <wtf/HashSet.h> +#include <wtf/RefPtr.h> + +namespace JSC { + class DebuggerCallFrame; +} + +namespace WebCore { + + class Frame; + class FrameView; + class Page; + class PageGroup; + class PausedTimeouts; + class JavaScriptCallFrame; + class JavaScriptDebugListener; + + class JavaScriptDebugServer : JSC::Debugger { + public: + static JavaScriptDebugServer& shared(); + + void addListener(JavaScriptDebugListener*); + void removeListener(JavaScriptDebugListener*); + + void addListener(JavaScriptDebugListener*, Page*); + void removeListener(JavaScriptDebugListener*, Page*); + + void addBreakpoint(intptr_t sourceID, unsigned lineNumber); + void removeBreakpoint(intptr_t sourceID, unsigned lineNumber); + bool hasBreakpoint(intptr_t sourceID, unsigned lineNumber) const; + void clearBreakpoints(); + + bool pauseOnExceptions() const { return m_pauseOnExceptions; } + void setPauseOnExceptions(bool); + + void pauseProgram(); + void continueProgram(); + void stepIntoStatement(); + void stepOverStatement(); + void stepOutOfFunction(); + + void recompileAllJSFunctionsSoon(); + void recompileAllJSFunctions(Timer<JavaScriptDebugServer>* = 0); + + JavaScriptCallFrame* currentCallFrame(); + + void pageCreated(Page*); + + typedef HashSet<JavaScriptDebugListener*> ListenerSet; + typedef void (JavaScriptDebugListener::*JavaScriptExecutionCallback)(); + + private: + JavaScriptDebugServer(); + ~JavaScriptDebugServer(); + + bool hasListeners() const { return !m_listeners.isEmpty() || !m_pageListenersMap.isEmpty(); } + bool hasGlobalListeners() const { return !m_listeners.isEmpty(); } + bool hasListenersInterestedInPage(Page*); + + void setJavaScriptPaused(const PageGroup&, bool paused); + void setJavaScriptPaused(Page*, bool paused); + void setJavaScriptPaused(Frame*, bool paused); + void setJavaScriptPaused(FrameView*, bool paused); + + void dispatchFunctionToListeners(JavaScriptExecutionCallback, Page*); + void pauseIfNeeded(Page*); + + virtual void sourceParsed(JSC::ExecState*, const JSC::SourceCode&, int errorLine, const JSC::UString& errorMsg); + virtual void callEvent(const JSC::DebuggerCallFrame&, intptr_t sourceID, int lineNumber); + virtual void atStatement(const JSC::DebuggerCallFrame&, intptr_t sourceID, int firstLine); + virtual void returnEvent(const JSC::DebuggerCallFrame&, intptr_t sourceID, int lineNumber); + virtual void exception(const JSC::DebuggerCallFrame&, intptr_t sourceID, int lineNumber); + virtual void willExecuteProgram(const JSC::DebuggerCallFrame&, intptr_t sourceID, int lineno); + virtual void didExecuteProgram(const JSC::DebuggerCallFrame&, intptr_t sourceID, int lineno); + virtual void didReachBreakpoint(const JSC::DebuggerCallFrame&, intptr_t sourceID, int lineno); + + void didAddListener(Page*); + void didRemoveListener(Page*); + void didRemoveLastListener(); + + typedef HashMap<Page*, ListenerSet*> PageListenersMap; + PageListenersMap m_pageListenersMap; + ListenerSet m_listeners; + bool m_callingListeners; + bool m_pauseOnExceptions; + bool m_pauseOnNextStatement; + bool m_paused; + bool m_doneProcessingDebuggerEvents; + JavaScriptCallFrame* m_pauseOnCallFrame; + RefPtr<JavaScriptCallFrame> m_currentCallFrame; + HashMap<RefPtr<Frame>, PausedTimeouts*> m_pausedTimeouts; + HashMap<intptr_t, HashSet<unsigned>*> m_breakpoints; + Timer<JavaScriptDebugServer> m_recompileTimer; + }; + +} // namespace WebCore + +#endif // JavaScriptDebugServer_h diff --git a/WebCore/inspector/JavaScriptProfile.cpp b/WebCore/inspector/JavaScriptProfile.cpp new file mode 100644 index 0000000..3ebdd55 --- /dev/null +++ b/WebCore/inspector/JavaScriptProfile.cpp @@ -0,0 +1,293 @@ +/* + * 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. + */ + +#include "config.h" +#include "JavaScriptProfile.h" + +#include "JavaScriptProfileNode.h" +#include <profiler/Profile.h> +#include <JavaScriptCore/APICast.h> +#include <JavaScriptCore/JSObjectRef.h> +#include <JavaScriptCore/JSStringRef.h> +#include <JavaScriptCore/OpaqueJSString.h> +#include <runtime/JSObject.h> +#include <runtime/JSValue.h> + +using namespace JSC; + +namespace WebCore { + +// Cache + +typedef HashMap<Profile*, JSObject*> ProfileMap; + +static ProfileMap& profileCache() +{ + static ProfileMap staticProfiles; + return staticProfiles; +} + +// Static Values + +static JSClassRef ProfileClass(); + +static JSValueRef getTitleCallback(JSContextRef ctx, JSObjectRef thisObject, JSStringRef propertyName, JSValueRef* exception) +{ + if (!JSValueIsObjectOfClass(ctx, thisObject, ProfileClass())) + return JSValueMakeUndefined(ctx); + + Profile* profile = static_cast<Profile*>(JSObjectGetPrivate(thisObject)); + return JSValueMakeString(ctx, OpaqueJSString::create(profile->title()).get()); +} + +static JSValueRef getHeadCallback(JSContextRef ctx, JSObjectRef thisObject, JSStringRef propertyName, JSValueRef* exception) +{ + if (!JSValueIsObjectOfClass(ctx, thisObject, ProfileClass())) + return JSValueMakeUndefined(ctx); + + Profile* profile = static_cast<Profile*>(JSObjectGetPrivate(thisObject)); + return toRef(toJS(toJS(ctx), profile->head())); +} + +static JSValueRef getHeavyProfileCallback(JSContextRef ctx, JSObjectRef thisObject, JSStringRef propertyName, JSValueRef* exception) +{ + if (!JSValueIsObjectOfClass(ctx, thisObject, ProfileClass())) + return JSValueMakeUndefined(ctx); + + Profile* profile = static_cast<Profile*>(JSObjectGetPrivate(thisObject)); + return toRef(toJS(toJS(ctx), profile->heavyProfile())); +} + +static JSValueRef getTreeProfileCallback(JSContextRef ctx, JSObjectRef thisObject, JSStringRef propertyName, JSValueRef* exception) +{ + if (!JSValueIsObjectOfClass(ctx, thisObject, ProfileClass())) + return JSValueMakeUndefined(ctx); + + Profile* profile = static_cast<Profile*>(JSObjectGetPrivate(thisObject)); + return toRef(toJS(toJS(ctx), profile->treeProfile())); +} + +static JSValueRef getUniqueIdCallback(JSContextRef ctx, JSObjectRef thisObject, JSStringRef propertyName, JSValueRef* exception) +{ + if (!JSValueIsObjectOfClass(ctx, thisObject, ProfileClass())) + return JSValueMakeUndefined(ctx); + + Profile* profile = static_cast<Profile*>(JSObjectGetPrivate(thisObject)); + return JSValueMakeNumber(ctx, profile->uid()); +} + +// Static Functions + +static JSValueRef focus(JSContextRef ctx, JSObjectRef /*function*/, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* /*exception*/) +{ + if (!JSValueIsObjectOfClass(ctx, thisObject, ProfileClass())) + return JSValueMakeUndefined(ctx); + + if (argumentCount < 1) + return JSValueMakeUndefined(ctx); + + if (!JSValueIsObjectOfClass(ctx, arguments[0], ProfileNodeClass())) + return JSValueMakeUndefined(ctx); + + Profile* profile = static_cast<Profile*>(JSObjectGetPrivate(thisObject)); + profile->focus(static_cast<ProfileNode*>(JSObjectGetPrivate(const_cast<JSObjectRef>(arguments[0])))); + + return JSValueMakeUndefined(ctx); +} + +static JSValueRef exclude(JSContextRef ctx, JSObjectRef /*function*/, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* /*exception*/) +{ + if (!JSValueIsObjectOfClass(ctx, thisObject, ProfileClass())) + return JSValueMakeUndefined(ctx); + + if (argumentCount < 1) + return JSValueMakeUndefined(ctx); + + if (!JSValueIsObjectOfClass(ctx, arguments[0], ProfileNodeClass())) + return JSValueMakeUndefined(ctx); + + Profile* profile = static_cast<Profile*>(JSObjectGetPrivate(thisObject)); + profile->exclude(static_cast<ProfileNode*>(JSObjectGetPrivate(const_cast<JSObjectRef>(arguments[0])))); + + return JSValueMakeUndefined(ctx); +} + +static JSValueRef restoreAll(JSContextRef ctx, JSObjectRef /*function*/, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* /*exception*/) +{ + if (!JSValueIsObjectOfClass(ctx, thisObject, ProfileClass())) + return JSValueMakeUndefined(ctx); + + Profile* profile = static_cast<Profile*>(JSObjectGetPrivate(thisObject)); + profile->restoreAll(); + + return JSValueMakeUndefined(ctx); +} + +static JSValueRef sortTotalTimeDescending(JSContextRef ctx, JSObjectRef /*function*/, JSObjectRef thisObject, size_t /*argumentCount*/, const JSValueRef[] /*arguments*/, JSValueRef* /*exception*/) +{ + if (!JSValueIsObjectOfClass(ctx, thisObject, ProfileClass())) + return JSValueMakeUndefined(ctx); + + Profile* profile = static_cast<Profile*>(JSObjectGetPrivate(thisObject)); + profile->sortTotalTimeDescending(); + + return JSValueMakeUndefined(ctx); +} + +static JSValueRef sortTotalTimeAscending(JSContextRef ctx, JSObjectRef /*function*/, JSObjectRef thisObject, size_t /*argumentCount*/, const JSValueRef[] /*arguments*/, JSValueRef* /*exception*/) +{ + if (!JSValueIsObjectOfClass(ctx, thisObject, ProfileClass())) + return JSValueMakeUndefined(ctx); + + Profile* profile = static_cast<Profile*>(JSObjectGetPrivate(thisObject)); + profile->sortTotalTimeAscending(); + + return JSValueMakeUndefined(ctx); +} + +static JSValueRef sortSelfTimeDescending(JSContextRef ctx, JSObjectRef /*function*/, JSObjectRef thisObject, size_t /*argumentCount*/, const JSValueRef[] /*arguments*/, JSValueRef* /*exception*/) +{ + if (!JSValueIsObjectOfClass(ctx, thisObject, ProfileClass())) + return JSValueMakeUndefined(ctx); + + Profile* profile = static_cast<Profile*>(JSObjectGetPrivate(thisObject)); + profile->sortSelfTimeDescending(); + + return JSValueMakeUndefined(ctx); +} + +static JSValueRef sortSelfTimeAscending(JSContextRef ctx, JSObjectRef /*function*/, JSObjectRef thisObject, size_t /*argumentCount*/, const JSValueRef[] /*arguments*/, JSValueRef* /*exception*/) +{ + if (!JSValueIsObjectOfClass(ctx, thisObject, ProfileClass())) + return JSValueMakeUndefined(ctx); + + Profile* profile = static_cast<Profile*>(JSObjectGetPrivate(thisObject)); + profile->sortSelfTimeAscending(); + + return JSValueMakeUndefined(ctx); +} + +static JSValueRef sortCallsDescending(JSContextRef ctx, JSObjectRef /*function*/, JSObjectRef thisObject, size_t /*argumentCount*/, const JSValueRef[] /*arguments*/, JSValueRef* /*exception*/) +{ + if (!JSValueIsObjectOfClass(ctx, thisObject, ProfileClass())) + return JSValueMakeUndefined(ctx); + + Profile* profile = static_cast<Profile*>(JSObjectGetPrivate(thisObject)); + profile->sortCallsDescending(); + + return JSValueMakeUndefined(ctx); +} + +static JSValueRef sortCallsAscending(JSContextRef ctx, JSObjectRef /*function*/, JSObjectRef thisObject, size_t /*argumentCount*/, const JSValueRef[] /*arguments*/, JSValueRef* /*exception*/) +{ + if (!JSValueIsObjectOfClass(ctx, thisObject, ProfileClass())) + return JSValueMakeUndefined(ctx); + + Profile* profile = static_cast<Profile*>(JSObjectGetPrivate(thisObject)); + profile->sortCallsAscending(); + + return JSValueMakeUndefined(ctx); +} + +static JSValueRef sortFunctionNameDescending(JSContextRef ctx, JSObjectRef /*function*/, JSObjectRef thisObject, size_t /*argumentCount*/, const JSValueRef[] /*arguments*/, JSValueRef* /*exception*/) +{ + if (!JSValueIsObjectOfClass(ctx, thisObject, ProfileClass())) + return JSValueMakeUndefined(ctx); + + Profile* profile = static_cast<Profile*>(JSObjectGetPrivate(thisObject)); + profile->sortFunctionNameDescending(); + + return JSValueMakeUndefined(ctx); +} + +static JSValueRef sortFunctionNameAscending(JSContextRef ctx, JSObjectRef /*function*/, JSObjectRef thisObject, size_t /*argumentCount*/, const JSValueRef[] /*arguments*/, JSValueRef* /*exception*/) +{ + if (!JSValueIsObjectOfClass(ctx, thisObject, ProfileClass())) + return JSValueMakeUndefined(ctx); + + Profile* profile = static_cast<Profile*>(JSObjectGetPrivate(thisObject)); + profile->sortFunctionNameAscending(); + + return JSValueMakeUndefined(ctx); +} + +static void finalize(JSObjectRef object) +{ + Profile* profile = static_cast<Profile*>(JSObjectGetPrivate(object)); + profileCache().remove(profile); + profile->deref(); +} + +JSClassRef ProfileClass() +{ + static JSStaticValue staticValues[] = { + { "title", getTitleCallback, 0, kJSPropertyAttributeNone }, + { "head", getHeadCallback, 0, kJSPropertyAttributeNone }, + { "heavyProfile", getHeavyProfileCallback, 0, kJSPropertyAttributeNone }, + { "treeProfile", getTreeProfileCallback, 0, kJSPropertyAttributeNone }, + { "uid", getUniqueIdCallback, 0, kJSPropertyAttributeNone }, + { 0, 0, 0, 0 } + }; + + static JSStaticFunction staticFunctions[] = { + { "focus", focus, kJSPropertyAttributeNone }, + { "exclude", exclude, kJSPropertyAttributeNone }, + { "restoreAll", restoreAll, kJSPropertyAttributeNone }, + { "sortTotalTimeDescending", sortTotalTimeDescending, kJSPropertyAttributeNone }, + { "sortTotalTimeAscending", sortTotalTimeAscending, kJSPropertyAttributeNone }, + { "sortSelfTimeDescending", sortSelfTimeDescending, kJSPropertyAttributeNone }, + { "sortSelfTimeAscending", sortSelfTimeAscending, kJSPropertyAttributeNone }, + { "sortCallsDescending", sortCallsDescending, kJSPropertyAttributeNone }, + { "sortCallsAscending", sortCallsAscending, kJSPropertyAttributeNone }, + { "sortFunctionNameDescending", sortFunctionNameDescending, kJSPropertyAttributeNone }, + { "sortFunctionNameAscending", sortFunctionNameAscending, kJSPropertyAttributeNone }, + { 0, 0, 0 } + }; + + static JSClassDefinition classDefinition = { + 0, kJSClassAttributeNone, "Profile", 0, staticValues, staticFunctions, + 0, finalize, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + + static JSClassRef profileClass = JSClassCreate(&classDefinition); + return profileClass; +} + +JSValue* toJS(ExecState* exec, Profile* profile) +{ + if (!profile) + return jsNull(); + + JSObject* profileWrapper = profileCache().get(profile); + if (profileWrapper) + return profileWrapper; + + profile->ref(); + profileWrapper = toJS(JSObjectMake(toRef(exec), ProfileClass(), static_cast<void*>(profile))); + profileCache().set(profile, profileWrapper); + return profileWrapper; +} + +} // namespace WebCore diff --git a/WebCore/inspector/JavaScriptProfile.h b/WebCore/inspector/JavaScriptProfile.h new file mode 100644 index 0000000..6cb4d33 --- /dev/null +++ b/WebCore/inspector/JavaScriptProfile.h @@ -0,0 +1,42 @@ +/* + * 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. + */ + +#ifndef JavaScriptProfile_h +#define JavaScriptProfile_h + +#include <runtime/JSValue.h> + +namespace JSC { + class ExecState; + class Profile; +} + +namespace WebCore { + + JSC::JSValue* toJS(JSC::ExecState*, JSC::Profile*); + +} // namespace WebCore + +#endif diff --git a/WebCore/inspector/JavaScriptProfileNode.cpp b/WebCore/inspector/JavaScriptProfileNode.cpp new file mode 100644 index 0000000..ef2b2cd --- /dev/null +++ b/WebCore/inspector/JavaScriptProfileNode.cpp @@ -0,0 +1,242 @@ +/* + * 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. + */ + +#include "config.h" +#include "JavaScriptProfileNode.h" + +#include "JSDOMBinding.h" +#include <profiler/ProfileNode.h> +#include <JavaScriptCore/APICast.h> +#include <JavaScriptCore/JSObjectRef.h> +#include <JavaScriptCore/JSContextRef.h> +#include <JavaScriptCore/JSRetainPtr.h> +#include <JavaScriptCore/JSStringRef.h> +#include <runtime/JSLock.h> +#include <runtime/JSValue.h> + +using namespace JSC; + +namespace WebCore { + +// Cache + +typedef HashMap<ProfileNode*, JSObject*> ProfileNodeMap; + +static ProfileNodeMap& profileNodeCache() +{ + static ProfileNodeMap staticProfileNodes; + return staticProfileNodes; +} + +static JSValueRef getFunctionName(JSContextRef ctx, JSObjectRef thisObject, JSStringRef propertyName, JSValueRef* exception) +{ + if (!JSValueIsObjectOfClass(ctx, thisObject, ProfileNodeClass())) + return JSValueMakeUndefined(ctx); + + ProfileNode* profileNode = static_cast<ProfileNode*>(JSObjectGetPrivate(thisObject)); + JSRetainPtr<JSStringRef> functionNameString(Adopt, JSStringCreateWithCharacters(profileNode->functionName().data(), profileNode->functionName().size())); + return JSValueMakeString(ctx, functionNameString.get()); +} + +static JSValueRef getURL(JSContextRef ctx, JSObjectRef thisObject, JSStringRef propertyName, JSValueRef* exception) +{ + if (!JSValueIsObjectOfClass(ctx, thisObject, ProfileNodeClass())) + return JSValueMakeUndefined(ctx); + + ProfileNode* profileNode = static_cast<ProfileNode*>(JSObjectGetPrivate(thisObject)); + JSRetainPtr<JSStringRef> urlString(Adopt, JSStringCreateWithCharacters(profileNode->url().data(), profileNode->url().size())); + return JSValueMakeString(ctx, urlString.get()); +} + +static JSValueRef getLineNumber(JSContextRef ctx, JSObjectRef thisObject, JSStringRef propertyName, JSValueRef* exception) +{ + if (!JSValueIsObjectOfClass(ctx, thisObject, ProfileNodeClass())) + return JSValueMakeUndefined(ctx); + + ProfileNode* profileNode = static_cast<ProfileNode*>(JSObjectGetPrivate(thisObject)); + return JSValueMakeNumber(ctx, profileNode->lineNumber()); +} + +static JSValueRef getTotalTime(JSContextRef ctx, JSObjectRef thisObject, JSStringRef propertyName, JSValueRef* exception) +{ + JSC::JSLock lock(false); + + if (!JSValueIsObjectOfClass(ctx, thisObject, ProfileNodeClass())) + return JSValueMakeUndefined(ctx); + + ProfileNode* profileNode = static_cast<ProfileNode*>(JSObjectGetPrivate(thisObject)); + return JSValueMakeNumber(ctx, profileNode->totalTime()); +} + +static JSValueRef getSelfTime(JSContextRef ctx, JSObjectRef thisObject, JSStringRef propertyName, JSValueRef* exception) +{ + JSC::JSLock lock(false); + + if (!JSValueIsObjectOfClass(ctx, thisObject, ProfileNodeClass())) + return JSValueMakeUndefined(ctx); + + ProfileNode* profileNode = static_cast<ProfileNode*>(JSObjectGetPrivate(thisObject)); + return JSValueMakeNumber(ctx, profileNode->selfTime()); +} + +static JSValueRef getTotalPercent(JSContextRef ctx, JSObjectRef thisObject, JSStringRef propertyName, JSValueRef* exception) +{ + JSC::JSLock lock(false); + + if (!JSValueIsObjectOfClass(ctx, thisObject, ProfileNodeClass())) + return JSValueMakeUndefined(ctx); + + ProfileNode* profileNode = static_cast<ProfileNode*>(JSObjectGetPrivate(thisObject)); + return JSValueMakeNumber(ctx, profileNode->totalPercent()); +} + +static JSValueRef getSelfPercent(JSContextRef ctx, JSObjectRef thisObject, JSStringRef propertyName, JSValueRef* exception) +{ + JSC::JSLock lock(false); + + if (!JSValueIsObjectOfClass(ctx, thisObject, ProfileNodeClass())) + return JSValueMakeUndefined(ctx); + + ProfileNode* profileNode = static_cast<ProfileNode*>(JSObjectGetPrivate(thisObject)); + return JSValueMakeNumber(ctx, profileNode->selfPercent()); +} + +static JSValueRef getNumberOfCalls(JSContextRef ctx, JSObjectRef thisObject, JSStringRef propertyName, JSValueRef* exception) +{ + JSC::JSLock lock(false); + + if (!JSValueIsObjectOfClass(ctx, thisObject, ProfileNodeClass())) + return JSValueMakeUndefined(ctx); + + ProfileNode* profileNode = static_cast<ProfileNode*>(JSObjectGetPrivate(thisObject)); + return JSValueMakeNumber(ctx, profileNode->numberOfCalls()); +} + +static JSValueRef getChildren(JSContextRef ctx, JSObjectRef thisObject, JSStringRef propertyName, JSValueRef* exception) +{ + JSC::JSLock lock(false); + + if (!JSValueIsObjectOfClass(ctx, thisObject, ProfileNodeClass())) + return JSValueMakeUndefined(ctx); + + ProfileNode* profileNode = static_cast<ProfileNode*>(JSObjectGetPrivate(thisObject)); + const Vector<RefPtr<ProfileNode> >& children = profileNode->children(); + + JSObjectRef global = JSContextGetGlobalObject(ctx); + + JSRetainPtr<JSStringRef> arrayString(Adopt, JSStringCreateWithUTF8CString("Array")); + + JSValueRef arrayProperty = JSObjectGetProperty(ctx, global, arrayString.get(), exception); + if (exception && *exception) + return JSValueMakeUndefined(ctx); + + JSObjectRef arrayConstructor = JSValueToObject(ctx, arrayProperty, exception); + if (exception && *exception) + return JSValueMakeUndefined(ctx); + + JSObjectRef result = JSObjectCallAsConstructor(ctx, arrayConstructor, 0, 0, exception); + if (exception && *exception) + return JSValueMakeUndefined(ctx); + + JSRetainPtr<JSStringRef> pushString(Adopt, JSStringCreateWithUTF8CString("push")); + + JSValueRef pushProperty = JSObjectGetProperty(ctx, result, pushString.get(), exception); + if (exception && *exception) + return JSValueMakeUndefined(ctx); + + JSObjectRef pushFunction = JSValueToObject(ctx, pushProperty, exception); + if (exception && *exception) + return JSValueMakeUndefined(ctx); + + for (Vector<RefPtr<ProfileNode> >::const_iterator it = children.begin(); it != children.end(); ++it) { + JSValueRef arg0 = toRef(toJS(toJS(ctx), (*it).get() )); + JSObjectCallAsFunction(ctx, pushFunction, result, 1, &arg0, exception); + if (exception && *exception) + return JSValueMakeUndefined(ctx); + } + + return result; +} + +static JSValueRef getVisible(JSContextRef ctx, JSObjectRef thisObject, JSStringRef propertyName, JSValueRef* exception) +{ + JSC::JSLock lock(false); + + if (!JSValueIsObjectOfClass(ctx, thisObject, ProfileNodeClass())) + return JSValueMakeUndefined(ctx); + + ProfileNode* profileNode = static_cast<ProfileNode*>(JSObjectGetPrivate(thisObject)); + return JSValueMakeBoolean(ctx, profileNode->visible()); +} + +static void finalize(JSObjectRef object) +{ + ProfileNode* profileNode = static_cast<ProfileNode*>(JSObjectGetPrivate(object)); + profileNodeCache().remove(profileNode); + profileNode->deref(); +} + +JSClassRef ProfileNodeClass() +{ + static JSStaticValue staticValues[] = { + { "functionName", getFunctionName, 0, kJSPropertyAttributeNone }, + { "url", getURL, 0, kJSPropertyAttributeNone }, + { "lineNumber", getLineNumber, 0, kJSPropertyAttributeNone }, + { "totalTime", getTotalTime, 0, kJSPropertyAttributeNone }, + { "selfTime", getSelfTime, 0, kJSPropertyAttributeNone }, + { "totalPercent", getTotalPercent, 0, kJSPropertyAttributeNone }, + { "selfPercent", getSelfPercent, 0, kJSPropertyAttributeNone }, + { "numberOfCalls", getNumberOfCalls, 0, kJSPropertyAttributeNone }, + { "children", getChildren, 0, kJSPropertyAttributeNone }, + { "visible", getVisible, 0, kJSPropertyAttributeNone }, + { 0, 0, 0, 0 } + }; + + static JSClassDefinition classDefinition = { + 0, kJSClassAttributeNone, "ProfileNode", 0, staticValues, 0, + 0, finalize, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + + static JSClassRef profileNodeClass = JSClassCreate(&classDefinition); + return profileNodeClass; +} + +JSValue* toJS(ExecState* exec, ProfileNode* profileNode) +{ + if (!profileNode) + return jsNull(); + + JSObject* profileNodeWrapper = profileNodeCache().get(profileNode); + if (profileNodeWrapper) + return profileNodeWrapper; + + profileNode->ref(); + + profileNodeWrapper = toJS(JSObjectMake(toRef(exec), ProfileNodeClass(), static_cast<void*>(profileNode))); + profileNodeCache().set(profileNode, profileNodeWrapper); + return profileNodeWrapper; +} + +} // namespace WebCore diff --git a/WebCore/inspector/JavaScriptProfileNode.h b/WebCore/inspector/JavaScriptProfileNode.h new file mode 100644 index 0000000..61d01eb --- /dev/null +++ b/WebCore/inspector/JavaScriptProfileNode.h @@ -0,0 +1,44 @@ +/* + * 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. + */ + +#ifndef JavaScriptProfileNode_h +#define JavaScriptProfileNode_h + +#include <runtime/JSValue.h> +#include <JavaScriptCore/JSBase.h> + +namespace JSC { + class ExecState; + class ProfileNode; +} + +namespace WebCore { + + JSClassRef ProfileNodeClass(); + JSC::JSValue* toJS(JSC::ExecState*, JSC::ProfileNode*); + +} // namespace WebCore + +#endif diff --git a/WebCore/inspector/front-end/Breakpoint.js b/WebCore/inspector/front-end/Breakpoint.js new file mode 100644 index 0000000..8611cf5 --- /dev/null +++ b/WebCore/inspector/front-end/Breakpoint.js @@ -0,0 +1,54 @@ +/* + * 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) +{ + this.url = url; + this.line = line; + this.sourceID = sourceID; + this._enabled = true; +} + +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"); + } +} + +WebInspector.Breakpoint.prototype.__proto__ = WebInspector.Object.prototype; diff --git a/WebCore/inspector/front-end/BreakpointsSidebarPane.js b/WebCore/inspector/front-end/BreakpointsSidebarPane.js new file mode 100644 index 0000000..2b8f3cd --- /dev/null +++ b/WebCore/inspector/front-end/BreakpointsSidebarPane.js @@ -0,0 +1,85 @@ +/* + * 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.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) + { + this.breakpoints.push(breakpoint); + breakpoint.addEventListener("enabled", this._breakpointEnableChanged, this); + breakpoint.addEventListener("disabled", this._breakpointEnableChanged, this); + + // FIXME: add to the breakpoints UI. + + if (!InspectorController.debuggerEnabled() || !breakpoint.sourceID) + return; + + if (breakpoint.enabled) + InspectorController.addBreakpoint(breakpoint.sourceID, breakpoint.line); + }, + + removeBreakpoint: function(breakpoint) + { + this.breakpoints.remove(breakpoint); + breakpoint.removeEventListener("enabled", null, this); + breakpoint.removeEventListener("disabled", null, this); + + // FIXME: remove from the breakpoints UI. + + if (!InspectorController.debuggerEnabled() || !breakpoint.sourceID) + return; + + InspectorController.removeBreakpoint(breakpoint.sourceID, breakpoint.line); + }, + + _breakpointEnableChanged: function(event) + { + var breakpoint = event.target; + + // FIXME: change the breakpoint checkbox state in the UI. + + if (!InspectorController.debuggerEnabled() || !breakpoint.sourceID) + return; + + if (breakpoint.enabled) + InspectorController.addBreakpoint(breakpoint.sourceID, breakpoint.line); + else + InspectorController.removeBreakpoint(breakpoint.sourceID, breakpoint.line); + } +} + +WebInspector.BreakpointsSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype; diff --git a/WebCore/inspector/front-end/CallStackSidebarPane.js b/WebCore/inspector/front-end/CallStackSidebarPane.js new file mode 100644 index 0000000..a2c8bed --- /dev/null +++ b/WebCore/inspector/front-end/CallStackSidebarPane.js @@ -0,0 +1,110 @@ +/* + * 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")); +} + +WebInspector.CallStackSidebarPane.prototype = { + update: function(callFrame, sourceIDMap) + { + this.bodyElement.removeChildren(); + + this.placards = []; + delete this._selectedCallFrame; + + if (!callFrame) { + 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; + + do { + 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); + + callFrame = callFrame.caller; + } while (callFrame); + }, + + 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"); + }, + + _placardSelected: function(event) + { + var placardElement = event.target.enclosingNodeOrSelfWithClass("placard"); + this.selectedCallFrame = placardElement.placard.callFrame; + } +} + +WebInspector.CallStackSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype; diff --git a/WebCore/inspector/front-end/Console.js b/WebCore/inspector/front-end/Console.js new file mode 100644 index 0000000..e4de88b --- /dev/null +++ b/WebCore/inspector/front-end/Console.js @@ -0,0 +1,931 @@ +/* + * 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.Console = function() +{ + this.messages = []; + + WebInspector.View.call(this, document.getElementById("console")); + + 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.toggleButton = document.getElementById("console-status-bar-item"); + this.toggleButton.title = WebInspector.UIString("Show console."); + this.toggleButton.addEventListener("click", this._toggleButtonClicked.bind(this), false); + + 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.topGroup = new WebInspector.ConsoleGroup(null, 0); + this.messagesElement.insertBefore(this.topGroup.element, this.promptElement); + this.groupLevel = 0; + this.currentGroup = this.topGroup; + + document.getElementById("main-status-bar").addEventListener("mousedown", this._startStatusBarDragging.bind(this), true); +} + +WebInspector.Console.prototype = { + show: function() + { + if (this._animating || this.visible) + return; + + WebInspector.View.prototype.show.call(this); + + this._animating = true; + + this.toggleButton.addStyleClass("toggled-on"); + this.toggleButton.title = WebInspector.UIString("Hide console."); + + document.body.addStyleClass("console-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-console-status-bar-items"), start: {opacity: 0}, end: {opacity: 1}} + ]; + + var consoleStatusBar = document.getElementById("console-status-bar"); + consoleStatusBar.insertBefore(anchoredItems, consoleStatusBar.firstChild); + + function animationFinished() + { + if ("updateStatusBarItems" in WebInspector.currentPanel) + WebInspector.currentPanel.updateStatusBarItems(); + WebInspector.currentFocusElement = this.promptElement; + delete this._animating; + } + + WebInspector.animateStyle(animations, window.event && window.event.shiftKey ? 2000 : 250, animationFinished.bind(this)); + + if (!this.prompt.isCaretInsidePrompt()) + this.prompt.moveCaretToEndOfPrompt(); + }, + + hide: function() + { + if (this._animating || !this.visible) + return; + + WebInspector.View.prototype.hide.call(this); + + this._animating = true; + + this.toggleButton.removeStyleClass("toggled-on"); + this.toggleButton.title = WebInspector.UIString("Show console."); + + 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("console-visible"); + if ("updateStatusBarItems" in WebInspector.currentPanel) + WebInspector.currentPanel.updateStatusBarItems(); + document.body.addStyleClass("console-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-console-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("console-visible"); + delete this._animating; + } + + WebInspector.animateStyle(animations, window.event && window.event.shiftKey ? 2000 : 250, animationFinished.bind(this)); + }, + + addMessage: function(msg) + { + if (msg instanceof WebInspector.ConsoleMessage) { + 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]; + 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.level === WebInspector.ConsoleMessage.MessageLevel.EndGroup) { + if (this.groupLevel < 1) + return; + + this.groupLevel--; + + this.currentGroup = this.currentGroup.parentGroup; + } else { + if (msg.level === WebInspector.ConsoleMessage.MessageLevel.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(); + 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) + { + // 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 result; + if (expressionString) { + try { + result = this._evalInInspectedWindow(expressionString); + } catch(e) { + // Do nothing, the prefix will be considered a window property. + } + } else { + // There is no expressionString, so the completion should happen against global properties. + // Or if the debugger is paused, against properties in scope of the selected call frame. + if (WebInspector.panels.scripts.paused) + result = WebInspector.panels.scripts.variablesInScopeForSelectedCallFrame(); + else + result = InspectorController.inspectedWindow(); + } + + 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; + } + + return results; + }, + + _toggleButtonClicked: function() + { + this.visible = !this.visible; + }, + + _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); + event.stopPropagation(); + event.preventDefault(); + }, + + _promptKeyDown: function(event) + { + switch (event.keyIdentifier) { + case "Enter": + this._enterKeyPressed(event); + return; + } + + this.prompt.handleKeyEvent(event); + }, + + _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(); + }, + + _evalInInspectedWindow: function(expression) + { + if (WebInspector.panels.scripts.paused) + return WebInspector.panels.scripts.evaluateInSelectedCallFrame(expression); + + var inspectedWindow = InspectorController.inspectedWindow(); + if (!inspectedWindow._inspectorCommandLineAPI) { + 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 (k in o) a.push(k); return a; }, \ + values: function(o) { var a = []; for (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) } \ + };"); + + inspectedWindow._inspectorCommandLineAPI.clear = InspectorController.wrapCallback(this.clearMessages.bind(this)); + } + + // 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 inspectedWindow.eval(expression); + }, + + _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 result; + var exception = false; + try { + result = this._evalInInspectedWindow(str); + } catch(e) { + result = e; + exception = true; + } + + this.prompt.history.push(str); + this.prompt.historyOffset = 0; + this.prompt.text = ""; + + var level = exception ? WebInspector.ConsoleMessage.MessageLevel.Error : WebInspector.ConsoleMessage.MessageLevel.Log; + this.addMessage(new WebInspector.ConsoleCommand(str, result, this._format(result), level)); + }, + + _mouseOverNode: function(event) + { + var anchorElement = event.target.enclosingNodeOrSelfWithNodeName("a"); + WebInspector.hoveredDOMNode = (anchorElement ? anchorElement.representedNode : null); + }, + + _mouseOutOfNode: function(event) + { + var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY); + var anchorElement = nodeUnderMouse.enclosingNodeOrSelfWithNodeName("a"); + if (!anchorElement || !anchorElement.representedNode) + WebInspector.hoveredDOMNode = null; + }, + + _format: function(output) + { + var type = Object.type(output, InspectorController.inspectedWindow()); + if (type === "object") { + if (output instanceof InspectorController.inspectedWindow().Node) + type = "node"; + } + + // 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, + "date": 1, + "function": 1, + }; + + var formatter; + 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)); + }, + + _formatstring: function(str, elem) + { + elem.appendChild(document.createTextNode("\"" + str + "\"")); + }, + + _formatregexp: function(re, elem) + { + var formatted = String(re).replace(/([\\\/])/g, "\\$1").replace(/\\(\/[gim]*)$/, "$1").substring(1); + elem.appendChild(document.createTextNode(formatted)); + }, + + _formatarray: function(arr, elem) + { + elem.appendChild(document.createTextNode("[")); + for (var i = 0; i < arr.length; ++i) { + elem.appendChild(this._format(arr[i])); + if (i < arr.length - 1) + elem.appendChild(document.createTextNode(", ")); + } + elem.appendChild(document.createTextNode("]")); + }, + + _formatnode: function(node, elem) + { + var anchor = document.createElement("a"); + anchor.className = "inspectible-node"; + anchor.innerHTML = nodeTitleInfo.call(node).title; + anchor.representedNode = node; + anchor.addEventListener("mouseover", this._mouseOverNode.bind(this), false); + anchor.addEventListener("mouseout", this._mouseOutOfNode.bind(this), false); + elem.appendChild(anchor); + }, + + _formatobject: function(obj, elem) + { + elem.appendChild(document.createTextNode(Object.describe(obj))); + }, + + _formaterror: function(obj, elem) + { + elem.appendChild(document.createTextNode(obj.name + ": " + obj.message + " ")); + + if (obj.sourceURL) { + var urlElement = document.createElement("a"); + urlElement.className = "console-message-url webkit-html-resource-link"; + urlElement.href = obj.sourceURL; + urlElement.lineNumber = obj.line; + urlElement.preferredPanel = "scripts"; + + if (obj.line > 0) + urlElement.textContent = WebInspector.UIString("%s (line %d)", obj.sourceURL, obj.line); + else + urlElement.textContent = obj.sourceURL; + + elem.appendChild(urlElement); + } + }, +} + +WebInspector.Console.prototype.__proto__ = WebInspector.View.prototype; + +WebInspector.ConsoleMessage = function(source, level, line, url, groupLevel, repeatCount) +{ + this.source = source; + this.level = level; + this.line = line; + this.url = url; + this.groupLevel = groupLevel; + this.repeatCount = repeatCount; + + switch (this.level) { + case WebInspector.ConsoleMessage.MessageLevel.Object: + var propertiesSection = new WebInspector.ObjectPropertiesSection(arguments[6], null, null, null, true); + propertiesSection.element.addStyleClass("console-message"); + this.propertiesSection = propertiesSection; + break; + case WebInspector.ConsoleMessage.MessageLevel.Node: + var node = arguments[6]; + if (!(node instanceof InspectorController.inspectedWindow().Node)) + return; + this.elementsTreeOutline = new WebInspector.ElementsTreeOutline(); + this.elementsTreeOutline.rootDOMNode = node; + break; + case WebInspector.ConsoleMessage.MessageLevel.Trace: + var span = document.createElement("span"); + span.addStyleClass("console-formatted-trace"); + var stack = Array.prototype.slice.call(arguments, 6); + var funcNames = stack.map(function(f) { + return f.name || WebInspector.UIString("(anonymous function)"); + }); + span.appendChild(document.createTextNode(funcNames.join("\n"))); + this.formattedMessage = span; + break; + default: + // The formatedMessage property is used for the rich and interactive console. + this.formattedMessage = this._format(Array.prototype.slice.call(arguments, 6)); + + // This is used for inline message bubbles in SourceFrames, or other plain-text representations. + this.message = this.formattedMessage.textContent; + break; + } +} + +WebInspector.ConsoleMessage.prototype = { + 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); + } + + if (Object.type(parameters[0], InspectorController.inspectedWindow()) === "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; + + 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.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.Warning: + element.addStyleClass("console-warning-level"); + break; + case WebInspector.ConsoleMessage.MessageLevel.Error: + element.addStyleClass("console-error-level"); + break; + case WebInspector.ConsoleMessage.MessageLevel.StartGroup: + element.addStyleClass("console-group-title-level"); + } + + 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.UIString("%s (line %d)", 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.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 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.Error: + levelString = "Error"; + break; + case WebInspector.ConsoleMessage.MessageLevel.Object: + levelString = "Object"; + break; + case WebInspector.ConsoleMessage.MessageLevel.GroupTitle: + levelString = "GroupTitle"; + break; + } + + return sourceString + " " + 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.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 Chrome.h +WebInspector.ConsoleMessage.MessageSource = { + HTML: 0, + XML: 1, + JS: 2, + CSS: 3, + Other: 4, +} + +WebInspector.ConsoleMessage.MessageLevel = { + Tip: 0, + Log: 1, + Warning: 2, + Error: 3, + Object: 4, + Node: 5, + Trace: 6, + StartGroup: 7, + EndGroup: 8 +} + +WebInspector.ConsoleCommand = function(command, result, formattedResultElement, level) +{ + this.command = command; + this.formattedResultElement = formattedResultElement; + this.level = level; +} + +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); + + var resultElement = document.createElement("div"); + resultElement.className = "console-message"; + element.appendChild(resultElement); + + switch (this.level) { + case WebInspector.ConsoleMessage.MessageLevel.Log: + resultElement.addStyleClass("console-log-level"); + break; + case WebInspector.ConsoleMessage.MessageLevel.Warning: + resultElement.addStyleClass("console-warning-level"); + break; + case WebInspector.ConsoleMessage.MessageLevel.Error: + resultElement.addStyleClass("console-error-level"); + } + + var resultTextElement = document.createElement("span"); + resultTextElement.className = "console-message-text"; + resultTextElement.appendChild(this.formattedResultElement); + resultElement.appendChild(resultTextElement); + + return element; + } +} + +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.level === WebInspector.ConsoleMessage.MessageLevel.StartGroup) { + this.messagesElement.parentNode.insertBefore(element, this.messagesElement); + element.addEventListener("click", this._titleClicked.bind(this), true); + } else + this.messagesElement.appendChild(element); + }, + + _titleClicked: function(event) + { + var groupTitleElement = event.target.enclosingNodeOrSelfWithClass("console-group-title-level"); + 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/WebCore/inspector/front-end/DataGrid.js b/WebCore/inspector/front-end/DataGrid.js new file mode 100644 index 0000000..6f103ed --- /dev/null +++ b/WebCore/inspector/front-end/DataGrid.js @@ -0,0 +1,844 @@ +/* + * 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); + + 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"); + } + + headerRow.appendChild(cell); + + ++columnCount; + } + + columnGroup.span = columnCount; + + var cell = document.createElement("th"); + cell.className = "corner"; + headerRow.appendChild(cell); + + this._headerTable.appendChild(columnGroup); + 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._dataTable.appendChild(columnGroup.cloneNode(true)); + 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; +} + +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; + }, + + appendChild: function(child) + { + this.insertChild(child, this.children.length); + }, + + insertChild: function(child, index) + { + if (!child) + throw("Node can't be undefined or null."); + if (child.parent === this) + throw("Node is already a child of this node."); + + if (child.parent) + child.parent.removeChild(child); + + 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.dataGrid = this.dataGrid; + + 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("Node can't be undefined or null."); + if (child.parent !== this) + throw("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; + }, + + 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 = []; + }, + + 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(); + } + } +} + +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 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 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, + + 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; diff --git a/WebCore/inspector/front-end/Database.js b/WebCore/inspector/front-end/Database.js new file mode 100644 index 0000000..ef42e15 --- /dev/null +++ b/WebCore/inspector/front-end/Database.js @@ -0,0 +1,95 @@ +/* + * 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 = { + get database() + { + return this._database; + }, + + set database(x) + { + if (this._database === x) + return; + this._database = x; + }, + + 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); + }, + + get tableNames() + { + return InspectorController.databaseTableNames(this.database).sort(); + } +} diff --git a/WebCore/inspector/front-end/DatabaseQueryView.js b/WebCore/inspector/front-end/DatabaseQueryView.js new file mode 100644 index 0000000..6a91625 --- /dev/null +++ b/WebCore/inspector/front-end/DatabaseQueryView.js @@ -0,0 +1,199 @@ +/* + * 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("database-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) + { + 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; + } + } + + accumulateMatches(this.database.tableNames.map(function(name) { return name + " " })); + accumulateMatches(["SELECT ", "FROM ", "WHERE ", "LIMIT ", "DELETE FROM ", "CREATE ", "DROP ", "TABLE ", "INDEX ", "UPDATE ", "INSERT INTO ", "VALUES ("]); + + return results; + }, + + _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 = ""; + + function queryTransaction(tx) + { + tx.executeSql(query, null, InspectorController.wrapCallback(this._queryFinished.bind(this, query)), InspectorController.wrapCallback(this._executeSqlError.bind(this, query))); + } + + this.database.database.transaction(InspectorController.wrapCallback(queryTransaction.bind(this)), InspectorController.wrapCallback(this._queryError.bind(this, query))); + }, + + _queryFinished: function(query, tx, result) + { + var dataGrid = WebInspector.panels.databases.dataGridForResult(result); + dataGrid.element.addStyleClass("inline"); + this._appendQueryResult(query, dataGrid.element); + + if (query.match(/^create /i) || query.match(/^drop table /i)) + WebInspector.panels.databases.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 occured.", error.code); + + this._appendQueryResult(query, message, "error"); + }, + + _executeSqlError: function(query, tx, error) + { + this._queryError(query, 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/WebCore/inspector/front-end/DatabaseTableView.js b/WebCore/inspector/front-end/DatabaseTableView.js new file mode 100644 index 0000000..2e72240 --- /dev/null +++ b/WebCore/inspector/front-end/DatabaseTableView.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.DatabaseTableView = function(database, tableName) +{ + WebInspector.View.call(this); + + this.database = database; + this.tableName = tableName; + + this.element.addStyleClass("database-view"); + this.element.addStyleClass("table"); +} + +WebInspector.DatabaseTableView.prototype = { + show: function(parentElement) + { + WebInspector.View.prototype.show.call(this, parentElement); + this.update(); + }, + + update: function() + { + function queryTransaction(tx) + { + tx.executeSql("SELECT * FROM " + this.tableName, null, InspectorController.wrapCallback(this._queryFinished.bind(this)), InspectorController.wrapCallback(this._queryError.bind(this))); + } + + this.database.database.transaction(InspectorController.wrapCallback(queryTransaction.bind(this)), InspectorController.wrapCallback(this._queryError.bind(this))); + }, + + _queryFinished: function(tx, result) + { + this.element.removeChildren(); + + var dataGrid = WebInspector.panels.databases.dataGridForResult(result); + if (!dataGrid) { + var emptyMsgElement = document.createElement("div"); + emptyMsgElement.className = "database-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(tx, error) + { + this.element.removeChildren(); + + var errorMsgElement = document.createElement("div"); + errorMsgElement.className = "database-table-error"; + errorMsgElement.textContent = WebInspector.UIString("An error occurred trying to\nread the “%s” table.", this.tableName); + this.element.appendChild(errorMsgElement); + }, + +} + +WebInspector.DatabaseTableView.prototype.__proto__ = WebInspector.View.prototype; diff --git a/WebCore/inspector/front-end/DatabasesPanel.js b/WebCore/inspector/front-end/DatabasesPanel.js new file mode 100644 index 0000000..df5bbb3 --- /dev/null +++ b/WebCore/inspector/front-end/DatabasesPanel.js @@ -0,0 +1,357 @@ +/* + * 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.DatabasesPanel = function(database) +{ + WebInspector.Panel.call(this); + + this.sidebarElement = document.createElement("div"); + this.sidebarElement.id = "databases-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.databaseViews = document.createElement("div"); + this.databaseViews.id = "database-views"; + this.element.appendChild(this.databaseViews); + + this.reset(); +} + +WebInspector.DatabasesPanel.prototype = { + toolbarItemClass: "databases", + + get toolbarItemLabel() + { + return WebInspector.UIString("Databases"); + }, + + show: function() + { + WebInspector.Panel.prototype.show.call(this); + this._updateSidebarWidth(); + }, + + 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.sidebarTree.removeChildren(); + this.databaseViews.removeChildren(); + }, + + handleKeyEvent: function(event) + { + this.sidebarTree.handleKeyEvent(event); + }, + + addDatabase: function(database) + { + this._databases.push(database); + + var databaseTreeElement = new WebInspector.DatabaseSidebarTreeElement(database); + database._databasesTreeElement = databaseTreeElement; + + this.sidebarTree.appendChild(databaseTreeElement); + }, + + showDatabase: function(database, tableName) + { + if (!database) + return; + + if (this.visibleDatabaseView) + this.visibleDatabaseView.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.databaseViews); + + this.visibleDatabaseView = view; + }, + + closeVisibleView: function() + { + if (this.visibleDatabaseView) + this.visibleDatabaseView.hide(); + delete this.visibleDatabaseView; + }, + + updateDatabaseTables: function(database) + { + if (!database || !database._databasesTreeElement) + return; + + database._databasesTreeElement.shouldRefreshChildren = true; + + if (!("_tableViews" in database)) + return; + + var tableNamesHash = {}; + var tableNames = database.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 (this.visibleDatabaseView === database._tableViews[tableName]) + this.closeVisibleView(); + delete database._tableViews[tableName]; + } + } + }, + + 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; + }, + + _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.databaseViews.style.left = width + "px"; + this.sidebarResizeElement.style.left = (width - 3) + "px"; + } +} + +WebInspector.DatabasesPanel.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.databases.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 tableNames = this.database.tableNames; + var tableNamesLength = tableNames.length; + for (var i = 0; i < tableNamesLength; ++i) + this.appendChild(new WebInspector.SidebarDatabaseTableTreeElement(this.database, tableNames[i])); + }, + + 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.databases.showDatabase(this.database, this.tableName); + } +} + +WebInspector.SidebarDatabaseTableTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype; diff --git a/WebCore/inspector/front-end/ElementsPanel.js b/WebCore/inspector/front-end/ElementsPanel.js new file mode 100644 index 0000000..3c9be54 --- /dev/null +++ b/WebCore/inspector/front-end/ElementsPanel.js @@ -0,0 +1,1206 @@ +/* + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2008 Matt Lilek <webkit@mattlilek.com> + * + * 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"); + } + }; + + 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 = document.createElement("button"); + this.nodeSearchButton.title = WebInspector.UIString("Select an element in the page to inspect it."); + this.nodeSearchButton.id = "node-search-status-bar-item"; + this.nodeSearchButton.className = "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._mutationMonitoredWindows = []; + this._nodeInsertedEventListener = InspectorController.wrapCallback(this._nodeInserted.bind(this)); + this._nodeRemovedEventListener = InspectorController.wrapCallback(this._nodeRemoved.bind(this)); + this._contentLoadedEventListener = InspectorController.wrapCallback(this._contentLoaded.bind(this)); + + this.reset(); +} + +WebInspector.ElementsPanel.prototype = { + toolbarItemClass: "elements", + + get toolbarItemLabel() + { + return WebInspector.UIString("Elements"); + }, + + get statusBarItems() + { + return [this.nodeSearchButton, 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.removeStyleClass("toggled-on"); + } + }, + + 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.removeStyleClass("toggled-on"); + } + + this.recentlyModifiedNodes = []; + this.unregisterAllMutationEventListeners(); + + delete this.currentQuery; + this.searchCanceled(); + + var inspectedWindow = InspectorController.inspectedWindow(); + if (!inspectedWindow || !inspectedWindow.document) + return; + + if (!inspectedWindow.document.firstChild) { + function contentLoaded() + { + inspectedWindow.document.removeEventListener("DOMContentLoaded", contentLoadedCallback, false); + + this.reset(); + } + + var contentLoadedCallback = InspectorController.wrapCallback(contentLoaded.bind(this)); + inspectedWindow.document.addEventListener("DOMContentLoaded", contentLoadedCallback, false); + 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; + + this.registerMutationEventListeners(inspectedWindow); + + var inspectedRootDocument = inspectedWindow.document; + 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(); + } + }, + + includedInSearchResultsPropertyName: "__includedInInspectorSearchResults", + + searchCanceled: function() + { + if (this._searchResults) { + const searchResultsProperty = this.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]; + + var treeElement = this.treeOutline.findTreeElement(node); + if (treeElement) + treeElement.highlighted = false; + } + } + + WebInspector.updateSearchMatchesCount(0, this); + + if (this._currentSearchChunkIntervalIdentifier) { + clearInterval(this._currentSearchChunkIntervalIdentifier); + delete this._currentSearchChunkIntervalIdentifier; + } + + this._currentSearchResultIndex = 0; + this._searchResults = []; + }, + + 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; + + 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 = query.escapeCharacters("'"); + const escapedTagNameQuery = (tagNameQuery ? tagNameQuery.escapeCharacters("'") : null); + const escapedWhitespaceTrimmedQuery = whitespaceTrimmedQuery.escapeCharacters("'"); + const searchResultsProperty = this.includedInSearchResultsPropertyName; + + var updatedMatchCountOnce = false; + var matchesCountUpdateTimeout = null; + + function updateMatchesCount() + { + WebInspector.updateSearchMatchesCount(this._searchResults.length, this); + matchesCountUpdateTimeout = null; + updatedMatchCountOnce = true; + } + + function updateMatchesCountSoon() + { + if (!updatedMatchCountOnce) + return updateMatchesCount.call(this); + if (matchesCountUpdateTimeout) + return; + // Update the matches count every half-second so it doesn't feel twitchy. + matchesCountUpdateTimeout = setTimeout(updateMatchesCount.bind(this), 500); + } + + function addNodesToResults(nodes, length, getItem) + { + if (!length) + return; + + 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 (!this._searchResults.length) { + this._currentSearchResultIndex = 0; + this.focusedDOMNode = node; + } + + node[searchResultsProperty] = true; + 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; + } + + updateMatchesCountSoon.call(this); + } + + 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 < this._searchResults.length; ++i) + delete this._searchResults[i][searchResultsProperty]; + } + + const mainFrameDocument = InspectorController.inspectedWindow().document; + const searchDocuments = [mainFrameDocument]; + + if (tagNameQuery && startTagFound && endTagFound) + const searchFunctions = [matchExactTagNames, matchPlainText]; + else if (tagNameQuery && startTagFound) + const 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(). + const 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. + const searchFunctions = [matchPartialAttributeValues, matchPlainText]; + } else + const searchFunctions = [matchExactItems, matchStyleSelector, matchPartialTagNamesAndAttributeValues, matchPlainText, matchXPathQuery]; + + // Find all frames, iframes and object elements to search their documents. + const querySelectorAllFunction = InspectorController.inspectedWindow().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 = this; + 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); + this._currentSearchChunkIntervalIdentifier = chunkIntervalIdentifier; + }, + + 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]; + }, + + inspectedWindowCleared: function(window) + { + if (InspectorController.isWindowVisible()) + this.updateMutationEventListeners(window); + }, + + _addMutationEventListeners: function(monitoredWindow) + { + monitoredWindow.document.addEventListener("DOMNodeInserted", this._nodeInsertedEventListener, true); + monitoredWindow.document.addEventListener("DOMNodeRemoved", this._nodeRemovedEventListener, true); + if (monitoredWindow.frameElement) + monitoredWindow.addEventListener("DOMContentLoaded", this._contentLoadedEventListener, true); + }, + + _removeMutationEventListeners: function(monitoredWindow) + { + if (monitoredWindow.frameElement) + monitoredWindow.removeEventListener("DOMContentLoaded", this._contentLoadedEventListener, true); + if (!monitoredWindow.document) + return; + monitoredWindow.document.removeEventListener("DOMNodeInserted", this._nodeInsertedEventListener, true); + monitoredWindow.document.removeEventListener("DOMNodeRemoved", this._nodeRemovedEventListener, true); + }, + + updateMutationEventListeners: function(monitoredWindow) + { + this._addMutationEventListeners(monitoredWindow); + }, + + registerMutationEventListeners: function(monitoredWindow) + { + if (!monitoredWindow || this._mutationMonitoredWindows.indexOf(monitoredWindow) !== -1) + return; + this._mutationMonitoredWindows.push(monitoredWindow); + if (InspectorController.isWindowVisible()) + this._addMutationEventListeners(monitoredWindow); + }, + + unregisterMutationEventListeners: function(monitoredWindow) + { + if (!monitoredWindow || this._mutationMonitoredWindows.indexOf(monitoredWindow) === -1) + return; + this._mutationMonitoredWindows.remove(monitoredWindow); + this._removeMutationEventListeners(monitoredWindow); + }, + + unregisterAllMutationEventListeners: function() + { + for (var i = 0; i < this._mutationMonitoredWindows.length; ++i) + this._removeMutationEventListeners(this._mutationMonitoredWindows[i]); + this._mutationMonitoredWindows = []; + }, + + 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; + }, + + _contentLoaded: function(event) + { + this.recentlyModifiedNodes.push({node: event.target, parent: event.target.defaultView.frameElement, replaced: true}); + if (this.visible) + this._updateModifiedNodesSoon(); + }, + + _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, null, null, objectsAreSame); + if (parentNodeItem && !parentNodeItem.alreadyUpdatedChildren) { + parentNodeItem.updateChildren(replaced); + parentNodeItem.alreadyUpdatedChildren = true; + updatedParentTreeElements.push(parentNodeItem); + } + + if (!updateBreadcrumbs && (objectsAreSame(this.focusedDOMNode, parent) || isAncestorIncludingParentFrames(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 (objectsAreSame(crumb.representedObject, this.rootDOMNode)) + foundRoot = true; + + if (foundRoot) + crumb.addStyleClass("dimmed"); + else + crumb.removeStyleClass("dimmed"); + + if (objectsAreSame(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 = parentNodeOrFrameElement(current)) { + if (current.nodeType === Node.DOCUMENT_NODE) + continue; + + if (objectsAreSame(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 (objectsAreSame(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.console.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: + var data = this.focusedDOMNode.outerHTML; + 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(); + + if (InspectorController.searchingForNode()) + this.nodeSearchButton.addStyleClass("toggled-on"); + else + this.nodeSearchButton.removeStyleClass("toggled-on"); + } +} + +WebInspector.ElementsPanel.prototype.__proto__ = WebInspector.Panel.prototype; diff --git a/WebCore/inspector/front-end/ElementsTreeOutline.js b/WebCore/inspector/front-end/ElementsTreeOutline.js new file mode 100644 index 0000000..16e31b8 --- /dev/null +++ b/WebCore/inspector/front-end/ElementsTreeOutline.js @@ -0,0 +1,626 @@ +/* + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2008 Matt Lilek <webkit@mattlilek.com> + * + * 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 (objectsAreSame(this._rootDOMNode, x)) + return; + + this._rootDOMNode = x; + + this.update(); + }, + + get focusedDOMNode() + { + return this._focusedDOMNode; + }, + + set focusedDOMNode(x) + { + if (objectsAreSame(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 (objectsAreSame(this._focusedDOMNode, x)) { + this.focusedNodeChanged(); + + if (x && !this.suppressSelectHighlight) { + InspectorController.highlightDOMNode(x); + + if ("_restorePreviousHighlightNodeTimeout" in this) + clearTimeout(this._restorePreviousHighlightNodeTimeout); + + function restoreHighlightToHoveredNode() + { + var hoveredNode = WebInspector.hoveredDOMNode; + if (hoveredNode) + InspectorController.highlightDOMNode(hoveredNode); + 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, isAncestor, getParent, equal) + { + if (typeof isAncestor === "undefined") + isAncestor = isAncestorIncludingParentFrames; + if (typeof getParent === "undefined") + getParent = parentNodeOrFrameElement; + if (typeof equal === "undefined") + equal = objectsAreSame; + + var treeElement = TreeOutline.prototype.findTreeElement.call(this, node, isAncestor, getParent, equal); + 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, isAncestor, getParent, equal); + } + + 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.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 = node.contentDocument || (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); +} + +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"); + } + }, + + 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) + { + 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 || !objectsAreSame(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 (objectsAreSame(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 (objectsAreSame(currentParentNode, this.representedObject)) + continue; + if (this.representedObject.contentDocument && objectsAreSame(currentParentNode, this.representedObject.contentDocument)) + continue; + + var selectedTreeElement = this.treeOutline.selectedTreeElement; + if (selectedTreeElement && (selectedTreeElement === currentChild || selectedTreeElement.hasAncestor(currentChild))) + this.select(); + + this.removeChildAtIndex(i); + + if (this.treeOutline.panel && currentNode.contentDocument) + this.treeOutline.panel.unregisterMutationEventListeners(currentNode.contentDocument.defaultView); + } + + if (this.representedObject.contentDocument) + updateChildrenOfNode(this.representedObject.contentDocument); + 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(); + + if (this.treeOutline.panel && this.representedObject.contentDocument) + this.treeOutline.panel.registerMutationEventListeners(this.representedObject.contentDocument.defaultView); + }, + + 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)) + return; + + if (this.treeOutline.panel) { + this.treeOutline.rootDOMNode = this.parent.representedObject; + this.treeOutline.focusedDOMNode = this.representedObject; + } + + if (this.hasChildren && !this.expanded) + this.expand(); + }, + + _startEditing: function(event) + { + 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); + + return false; + }, + + _startEditingAttribute: function(attribute, event) + { + 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(event.target, 0, event.target, 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) + { + delete this._editing; + + var parseContainerElement = document.createElement("span"); + parseContainerElement.innerHTML = "<span " + newText + "></span>"; + var parseElement = parseContainerElement.firstChild; + if (!parseElement || !parseElement.hasAttributes()) { + editingCancelled(element, context); + return; + } + + var foundOriginalAttribute = false; + for (var i = 0; i < parseElement.attributes.length; ++i) { + var attr = parseElement.attributes[i]; + foundOriginalAttribute = foundOriginalAttribute || attr.name === attributeName; + InspectorController.inspectedWindow().Element.prototype.setAttribute.call(this.representedObject, attr.name, attr.value); + } + + if (!foundOriginalAttribute) + InspectorController.inspectedWindow().Element.prototype.removeAttribute.call(this.representedObject, attributeName); + + this._updateTitle(); + + this.treeOutline.focusedNodeChanged(true); + }, + + _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/WebCore/inspector/front-end/FontView.js b/WebCore/inspector/front-end/FontView.js new file mode 100644 index 0000000..4e1c931 --- /dev/null +++ b/WebCore/inspector/front-end/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/WebCore/inspector/front-end/ImageView.js b/WebCore/inspector/front-end/ImageView.js new file mode 100644 index 0000000..001ffdd --- /dev/null +++ b/WebCore/inspector/front-end/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/WebCore/inspector/front-end/Images/back.png b/WebCore/inspector/front-end/Images/back.png Binary files differnew file mode 100644 index 0000000..9363960 --- /dev/null +++ b/WebCore/inspector/front-end/Images/back.png diff --git a/WebCore/inspector/front-end/Images/checker.png b/WebCore/inspector/front-end/Images/checker.png Binary files differnew file mode 100644 index 0000000..8349908 --- /dev/null +++ b/WebCore/inspector/front-end/Images/checker.png diff --git a/WebCore/inspector/front-end/Images/clearConsoleButtons.png b/WebCore/inspector/front-end/Images/clearConsoleButtons.png Binary files differnew file mode 100644 index 0000000..140a4fb --- /dev/null +++ b/WebCore/inspector/front-end/Images/clearConsoleButtons.png diff --git a/WebCore/inspector/front-end/Images/closeButtons.png b/WebCore/inspector/front-end/Images/closeButtons.png Binary files differnew file mode 100644 index 0000000..28158a4 --- /dev/null +++ b/WebCore/inspector/front-end/Images/closeButtons.png diff --git a/WebCore/inspector/front-end/Images/consoleButtons.png b/WebCore/inspector/front-end/Images/consoleButtons.png Binary files differnew file mode 100644 index 0000000..fb5f089 --- /dev/null +++ b/WebCore/inspector/front-end/Images/consoleButtons.png diff --git a/WebCore/inspector/front-end/Images/database.png b/WebCore/inspector/front-end/Images/database.png Binary files differnew file mode 100644 index 0000000..339efa6 --- /dev/null +++ b/WebCore/inspector/front-end/Images/database.png diff --git a/WebCore/inspector/front-end/Images/databaseTable.png b/WebCore/inspector/front-end/Images/databaseTable.png Binary files differnew file mode 100644 index 0000000..3718708 --- /dev/null +++ b/WebCore/inspector/front-end/Images/databaseTable.png diff --git a/WebCore/inspector/front-end/Images/databasesIcon.png b/WebCore/inspector/front-end/Images/databasesIcon.png Binary files differnew file mode 100644 index 0000000..79c7bb3 --- /dev/null +++ b/WebCore/inspector/front-end/Images/databasesIcon.png diff --git a/WebCore/inspector/front-end/Images/debuggerContinue.png b/WebCore/inspector/front-end/Images/debuggerContinue.png Binary files differnew file mode 100644 index 0000000..d90a855 --- /dev/null +++ b/WebCore/inspector/front-end/Images/debuggerContinue.png diff --git a/WebCore/inspector/front-end/Images/debuggerPause.png b/WebCore/inspector/front-end/Images/debuggerPause.png Binary files differnew file mode 100644 index 0000000..97f958a --- /dev/null +++ b/WebCore/inspector/front-end/Images/debuggerPause.png diff --git a/WebCore/inspector/front-end/Images/debuggerStepInto.png b/WebCore/inspector/front-end/Images/debuggerStepInto.png Binary files differnew file mode 100644 index 0000000..277f126 --- /dev/null +++ b/WebCore/inspector/front-end/Images/debuggerStepInto.png diff --git a/WebCore/inspector/front-end/Images/debuggerStepOut.png b/WebCore/inspector/front-end/Images/debuggerStepOut.png Binary files differnew file mode 100644 index 0000000..3032e32 --- /dev/null +++ b/WebCore/inspector/front-end/Images/debuggerStepOut.png diff --git a/WebCore/inspector/front-end/Images/debuggerStepOver.png b/WebCore/inspector/front-end/Images/debuggerStepOver.png Binary files differnew file mode 100644 index 0000000..7d47245 --- /dev/null +++ b/WebCore/inspector/front-end/Images/debuggerStepOver.png diff --git a/WebCore/inspector/front-end/Images/disclosureTriangleSmallDown.png b/WebCore/inspector/front-end/Images/disclosureTriangleSmallDown.png Binary files differnew file mode 100644 index 0000000..cffc835 --- /dev/null +++ b/WebCore/inspector/front-end/Images/disclosureTriangleSmallDown.png diff --git a/WebCore/inspector/front-end/Images/disclosureTriangleSmallDownBlack.png b/WebCore/inspector/front-end/Images/disclosureTriangleSmallDownBlack.png Binary files differnew file mode 100644 index 0000000..4b49c13 --- /dev/null +++ b/WebCore/inspector/front-end/Images/disclosureTriangleSmallDownBlack.png diff --git a/WebCore/inspector/front-end/Images/disclosureTriangleSmallDownWhite.png b/WebCore/inspector/front-end/Images/disclosureTriangleSmallDownWhite.png Binary files differnew file mode 100644 index 0000000..aebae12 --- /dev/null +++ b/WebCore/inspector/front-end/Images/disclosureTriangleSmallDownWhite.png diff --git a/WebCore/inspector/front-end/Images/disclosureTriangleSmallRight.png b/WebCore/inspector/front-end/Images/disclosureTriangleSmallRight.png Binary files differnew file mode 100644 index 0000000..a3102ea --- /dev/null +++ b/WebCore/inspector/front-end/Images/disclosureTriangleSmallRight.png diff --git a/WebCore/inspector/front-end/Images/disclosureTriangleSmallRightBlack.png b/WebCore/inspector/front-end/Images/disclosureTriangleSmallRightBlack.png Binary files differnew file mode 100644 index 0000000..2c45859 --- /dev/null +++ b/WebCore/inspector/front-end/Images/disclosureTriangleSmallRightBlack.png diff --git a/WebCore/inspector/front-end/Images/disclosureTriangleSmallRightDown.png b/WebCore/inspector/front-end/Images/disclosureTriangleSmallRightDown.png Binary files differnew file mode 100644 index 0000000..035c069 --- /dev/null +++ b/WebCore/inspector/front-end/Images/disclosureTriangleSmallRightDown.png diff --git a/WebCore/inspector/front-end/Images/disclosureTriangleSmallRightDownBlack.png b/WebCore/inspector/front-end/Images/disclosureTriangleSmallRightDownBlack.png Binary files differnew file mode 100644 index 0000000..86f67bd --- /dev/null +++ b/WebCore/inspector/front-end/Images/disclosureTriangleSmallRightDownBlack.png diff --git a/WebCore/inspector/front-end/Images/disclosureTriangleSmallRightDownWhite.png b/WebCore/inspector/front-end/Images/disclosureTriangleSmallRightDownWhite.png Binary files differnew file mode 100644 index 0000000..972d794 --- /dev/null +++ b/WebCore/inspector/front-end/Images/disclosureTriangleSmallRightDownWhite.png diff --git a/WebCore/inspector/front-end/Images/disclosureTriangleSmallRightWhite.png b/WebCore/inspector/front-end/Images/disclosureTriangleSmallRightWhite.png Binary files differnew file mode 100644 index 0000000..a10168f --- /dev/null +++ b/WebCore/inspector/front-end/Images/disclosureTriangleSmallRightWhite.png diff --git a/WebCore/inspector/front-end/Images/dockButtons.png b/WebCore/inspector/front-end/Images/dockButtons.png Binary files differnew file mode 100644 index 0000000..4b01d66 --- /dev/null +++ b/WebCore/inspector/front-end/Images/dockButtons.png diff --git a/WebCore/inspector/front-end/Images/elementsIcon.png b/WebCore/inspector/front-end/Images/elementsIcon.png Binary files differnew file mode 100644 index 0000000..fde3db9 --- /dev/null +++ b/WebCore/inspector/front-end/Images/elementsIcon.png diff --git a/WebCore/inspector/front-end/Images/enableButtons.png b/WebCore/inspector/front-end/Images/enableButtons.png Binary files differnew file mode 100644 index 0000000..facee60 --- /dev/null +++ b/WebCore/inspector/front-end/Images/enableButtons.png diff --git a/WebCore/inspector/front-end/Images/errorIcon.png b/WebCore/inspector/front-end/Images/errorIcon.png Binary files differnew file mode 100644 index 0000000..c697263 --- /dev/null +++ b/WebCore/inspector/front-end/Images/errorIcon.png diff --git a/WebCore/inspector/front-end/Images/errorMediumIcon.png b/WebCore/inspector/front-end/Images/errorMediumIcon.png Binary files differnew file mode 100644 index 0000000..6ca32bb --- /dev/null +++ b/WebCore/inspector/front-end/Images/errorMediumIcon.png diff --git a/WebCore/inspector/front-end/Images/excludeButtons.png b/WebCore/inspector/front-end/Images/excludeButtons.png Binary files differnew file mode 100644 index 0000000..f1c53a9 --- /dev/null +++ b/WebCore/inspector/front-end/Images/excludeButtons.png diff --git a/WebCore/inspector/front-end/Images/focusButtons.png b/WebCore/inspector/front-end/Images/focusButtons.png Binary files differnew file mode 100644 index 0000000..47eaa04 --- /dev/null +++ b/WebCore/inspector/front-end/Images/focusButtons.png diff --git a/WebCore/inspector/front-end/Images/forward.png b/WebCore/inspector/front-end/Images/forward.png Binary files differnew file mode 100644 index 0000000..ad70f3e --- /dev/null +++ b/WebCore/inspector/front-end/Images/forward.png diff --git a/WebCore/inspector/front-end/Images/glossyHeader.png b/WebCore/inspector/front-end/Images/glossyHeader.png Binary files differnew file mode 100644 index 0000000..6cbefb7 --- /dev/null +++ b/WebCore/inspector/front-end/Images/glossyHeader.png diff --git a/WebCore/inspector/front-end/Images/glossyHeaderPressed.png b/WebCore/inspector/front-end/Images/glossyHeaderPressed.png Binary files differnew file mode 100644 index 0000000..1153506 --- /dev/null +++ b/WebCore/inspector/front-end/Images/glossyHeaderPressed.png diff --git a/WebCore/inspector/front-end/Images/glossyHeaderSelected.png b/WebCore/inspector/front-end/Images/glossyHeaderSelected.png Binary files differnew file mode 100644 index 0000000..71d5af6 --- /dev/null +++ b/WebCore/inspector/front-end/Images/glossyHeaderSelected.png diff --git a/WebCore/inspector/front-end/Images/glossyHeaderSelectedPressed.png b/WebCore/inspector/front-end/Images/glossyHeaderSelectedPressed.png Binary files differnew file mode 100644 index 0000000..7047dbe --- /dev/null +++ b/WebCore/inspector/front-end/Images/glossyHeaderSelectedPressed.png diff --git a/WebCore/inspector/front-end/Images/goArrow.png b/WebCore/inspector/front-end/Images/goArrow.png Binary files differnew file mode 100644 index 0000000..f318a56 --- /dev/null +++ b/WebCore/inspector/front-end/Images/goArrow.png diff --git a/WebCore/inspector/front-end/Images/graphLabelCalloutLeft.png b/WebCore/inspector/front-end/Images/graphLabelCalloutLeft.png Binary files differnew file mode 100644 index 0000000..6426dbd --- /dev/null +++ b/WebCore/inspector/front-end/Images/graphLabelCalloutLeft.png diff --git a/WebCore/inspector/front-end/Images/graphLabelCalloutRight.png b/WebCore/inspector/front-end/Images/graphLabelCalloutRight.png Binary files differnew file mode 100644 index 0000000..8c87eae --- /dev/null +++ b/WebCore/inspector/front-end/Images/graphLabelCalloutRight.png diff --git a/WebCore/inspector/front-end/Images/largerResourcesButtons.png b/WebCore/inspector/front-end/Images/largerResourcesButtons.png Binary files differnew file mode 100644 index 0000000..caf3f14 --- /dev/null +++ b/WebCore/inspector/front-end/Images/largerResourcesButtons.png diff --git a/WebCore/inspector/front-end/Images/nodeSearchButtons.png b/WebCore/inspector/front-end/Images/nodeSearchButtons.png Binary files differnew file mode 100644 index 0000000..0599bd4 --- /dev/null +++ b/WebCore/inspector/front-end/Images/nodeSearchButtons.png diff --git a/WebCore/inspector/front-end/Images/paneBottomGrow.png b/WebCore/inspector/front-end/Images/paneBottomGrow.png Binary files differnew file mode 100644 index 0000000..d55b865 --- /dev/null +++ b/WebCore/inspector/front-end/Images/paneBottomGrow.png diff --git a/WebCore/inspector/front-end/Images/paneBottomGrowActive.png b/WebCore/inspector/front-end/Images/paneBottomGrowActive.png Binary files differnew file mode 100644 index 0000000..ef3f259 --- /dev/null +++ b/WebCore/inspector/front-end/Images/paneBottomGrowActive.png diff --git a/WebCore/inspector/front-end/Images/paneGrowHandleLine.png b/WebCore/inspector/front-end/Images/paneGrowHandleLine.png Binary files differnew file mode 100644 index 0000000..4eaf61b --- /dev/null +++ b/WebCore/inspector/front-end/Images/paneGrowHandleLine.png diff --git a/WebCore/inspector/front-end/Images/pauseOnExceptionButtons.png b/WebCore/inspector/front-end/Images/pauseOnExceptionButtons.png Binary files differnew file mode 100644 index 0000000..a4dd33a --- /dev/null +++ b/WebCore/inspector/front-end/Images/pauseOnExceptionButtons.png diff --git a/WebCore/inspector/front-end/Images/percentButtons.png b/WebCore/inspector/front-end/Images/percentButtons.png Binary files differnew file mode 100644 index 0000000..2635b24 --- /dev/null +++ b/WebCore/inspector/front-end/Images/percentButtons.png diff --git a/WebCore/inspector/front-end/Images/profileGroupIcon.png b/WebCore/inspector/front-end/Images/profileGroupIcon.png Binary files differnew file mode 100644 index 0000000..44616d4 --- /dev/null +++ b/WebCore/inspector/front-end/Images/profileGroupIcon.png diff --git a/WebCore/inspector/front-end/Images/profileIcon.png b/WebCore/inspector/front-end/Images/profileIcon.png Binary files differnew file mode 100644 index 0000000..8008f9b --- /dev/null +++ b/WebCore/inspector/front-end/Images/profileIcon.png diff --git a/WebCore/inspector/front-end/Images/profileSmallIcon.png b/WebCore/inspector/front-end/Images/profileSmallIcon.png Binary files differnew file mode 100644 index 0000000..7935520 --- /dev/null +++ b/WebCore/inspector/front-end/Images/profileSmallIcon.png diff --git a/WebCore/inspector/front-end/Images/profilesIcon.png b/WebCore/inspector/front-end/Images/profilesIcon.png Binary files differnew file mode 100644 index 0000000..ecd5b04 --- /dev/null +++ b/WebCore/inspector/front-end/Images/profilesIcon.png diff --git a/WebCore/inspector/front-end/Images/profilesSilhouette.png b/WebCore/inspector/front-end/Images/profilesSilhouette.png Binary files differnew file mode 100644 index 0000000..42bb966 --- /dev/null +++ b/WebCore/inspector/front-end/Images/profilesSilhouette.png diff --git a/WebCore/inspector/front-end/Images/recordButtons.png b/WebCore/inspector/front-end/Images/recordButtons.png Binary files differnew file mode 100644 index 0000000..3676154 --- /dev/null +++ b/WebCore/inspector/front-end/Images/recordButtons.png diff --git a/WebCore/inspector/front-end/Images/reloadButtons.png b/WebCore/inspector/front-end/Images/reloadButtons.png Binary files differnew file mode 100644 index 0000000..1e45671 --- /dev/null +++ b/WebCore/inspector/front-end/Images/reloadButtons.png diff --git a/WebCore/inspector/front-end/Images/resourceCSSIcon.png b/WebCore/inspector/front-end/Images/resourceCSSIcon.png Binary files differnew file mode 100644 index 0000000..aead6a7 --- /dev/null +++ b/WebCore/inspector/front-end/Images/resourceCSSIcon.png diff --git a/WebCore/inspector/front-end/Images/resourceDocumentIcon.png b/WebCore/inspector/front-end/Images/resourceDocumentIcon.png Binary files differnew file mode 100644 index 0000000..1683a09 --- /dev/null +++ b/WebCore/inspector/front-end/Images/resourceDocumentIcon.png diff --git a/WebCore/inspector/front-end/Images/resourceDocumentIconSmall.png b/WebCore/inspector/front-end/Images/resourceDocumentIconSmall.png Binary files differnew file mode 100644 index 0000000..468ced9 --- /dev/null +++ b/WebCore/inspector/front-end/Images/resourceDocumentIconSmall.png diff --git a/WebCore/inspector/front-end/Images/resourceJSIcon.png b/WebCore/inspector/front-end/Images/resourceJSIcon.png Binary files differnew file mode 100644 index 0000000..9ef6ed0 --- /dev/null +++ b/WebCore/inspector/front-end/Images/resourceJSIcon.png diff --git a/WebCore/inspector/front-end/Images/resourcePlainIcon.png b/WebCore/inspector/front-end/Images/resourcePlainIcon.png Binary files differnew file mode 100644 index 0000000..0ed37b6 --- /dev/null +++ b/WebCore/inspector/front-end/Images/resourcePlainIcon.png diff --git a/WebCore/inspector/front-end/Images/resourcePlainIconSmall.png b/WebCore/inspector/front-end/Images/resourcePlainIconSmall.png Binary files differnew file mode 100644 index 0000000..0fa967d --- /dev/null +++ b/WebCore/inspector/front-end/Images/resourcePlainIconSmall.png diff --git a/WebCore/inspector/front-end/Images/resourcesIcon.png b/WebCore/inspector/front-end/Images/resourcesIcon.png Binary files differnew file mode 100644 index 0000000..982424d --- /dev/null +++ b/WebCore/inspector/front-end/Images/resourcesIcon.png diff --git a/WebCore/inspector/front-end/Images/resourcesSizeGraphIcon.png b/WebCore/inspector/front-end/Images/resourcesSizeGraphIcon.png Binary files differnew file mode 100644 index 0000000..e60dbe5 --- /dev/null +++ b/WebCore/inspector/front-end/Images/resourcesSizeGraphIcon.png diff --git a/WebCore/inspector/front-end/Images/resourcesTimeGraphIcon.png b/WebCore/inspector/front-end/Images/resourcesTimeGraphIcon.png Binary files differnew file mode 100644 index 0000000..c6953e9 --- /dev/null +++ b/WebCore/inspector/front-end/Images/resourcesTimeGraphIcon.png diff --git a/WebCore/inspector/front-end/Images/scriptsIcon.png b/WebCore/inspector/front-end/Images/scriptsIcon.png Binary files differnew file mode 100644 index 0000000..213b31e --- /dev/null +++ b/WebCore/inspector/front-end/Images/scriptsIcon.png diff --git a/WebCore/inspector/front-end/Images/scriptsSilhouette.png b/WebCore/inspector/front-end/Images/scriptsSilhouette.png Binary files differnew file mode 100644 index 0000000..206396f --- /dev/null +++ b/WebCore/inspector/front-end/Images/scriptsSilhouette.png diff --git a/WebCore/inspector/front-end/Images/searchSmallBlue.png b/WebCore/inspector/front-end/Images/searchSmallBlue.png Binary files differnew file mode 100644 index 0000000..9c990f4 --- /dev/null +++ b/WebCore/inspector/front-end/Images/searchSmallBlue.png diff --git a/WebCore/inspector/front-end/Images/searchSmallBrightBlue.png b/WebCore/inspector/front-end/Images/searchSmallBrightBlue.png Binary files differnew file mode 100644 index 0000000..b1d8055 --- /dev/null +++ b/WebCore/inspector/front-end/Images/searchSmallBrightBlue.png diff --git a/WebCore/inspector/front-end/Images/searchSmallGray.png b/WebCore/inspector/front-end/Images/searchSmallGray.png Binary files differnew file mode 100644 index 0000000..4f3c068 --- /dev/null +++ b/WebCore/inspector/front-end/Images/searchSmallGray.png diff --git a/WebCore/inspector/front-end/Images/searchSmallWhite.png b/WebCore/inspector/front-end/Images/searchSmallWhite.png Binary files differnew file mode 100644 index 0000000..85f430d --- /dev/null +++ b/WebCore/inspector/front-end/Images/searchSmallWhite.png diff --git a/WebCore/inspector/front-end/Images/segment.png b/WebCore/inspector/front-end/Images/segment.png Binary files differnew file mode 100644 index 0000000..759266e --- /dev/null +++ b/WebCore/inspector/front-end/Images/segment.png diff --git a/WebCore/inspector/front-end/Images/segmentEnd.png b/WebCore/inspector/front-end/Images/segmentEnd.png Binary files differnew file mode 100644 index 0000000..72672ff --- /dev/null +++ b/WebCore/inspector/front-end/Images/segmentEnd.png diff --git a/WebCore/inspector/front-end/Images/segmentHover.png b/WebCore/inspector/front-end/Images/segmentHover.png Binary files differnew file mode 100644 index 0000000..c5017f4 --- /dev/null +++ b/WebCore/inspector/front-end/Images/segmentHover.png diff --git a/WebCore/inspector/front-end/Images/segmentHoverEnd.png b/WebCore/inspector/front-end/Images/segmentHoverEnd.png Binary files differnew file mode 100644 index 0000000..d51363d --- /dev/null +++ b/WebCore/inspector/front-end/Images/segmentHoverEnd.png diff --git a/WebCore/inspector/front-end/Images/segmentSelected.png b/WebCore/inspector/front-end/Images/segmentSelected.png Binary files differnew file mode 100644 index 0000000..c92f584 --- /dev/null +++ b/WebCore/inspector/front-end/Images/segmentSelected.png diff --git a/WebCore/inspector/front-end/Images/segmentSelectedEnd.png b/WebCore/inspector/front-end/Images/segmentSelectedEnd.png Binary files differnew file mode 100644 index 0000000..be5e085 --- /dev/null +++ b/WebCore/inspector/front-end/Images/segmentSelectedEnd.png diff --git a/WebCore/inspector/front-end/Images/splitviewDimple.png b/WebCore/inspector/front-end/Images/splitviewDimple.png Binary files differnew file mode 100644 index 0000000..584ffd4 --- /dev/null +++ b/WebCore/inspector/front-end/Images/splitviewDimple.png diff --git a/WebCore/inspector/front-end/Images/splitviewDividerBackground.png b/WebCore/inspector/front-end/Images/splitviewDividerBackground.png Binary files differnew file mode 100644 index 0000000..1120a7f --- /dev/null +++ b/WebCore/inspector/front-end/Images/splitviewDividerBackground.png diff --git a/WebCore/inspector/front-end/Images/statusbarBackground.png b/WebCore/inspector/front-end/Images/statusbarBackground.png Binary files differnew file mode 100644 index 0000000..b466a49 --- /dev/null +++ b/WebCore/inspector/front-end/Images/statusbarBackground.png diff --git a/WebCore/inspector/front-end/Images/statusbarBottomBackground.png b/WebCore/inspector/front-end/Images/statusbarBottomBackground.png Binary files differnew file mode 100644 index 0000000..fb5c9e4 --- /dev/null +++ b/WebCore/inspector/front-end/Images/statusbarBottomBackground.png diff --git a/WebCore/inspector/front-end/Images/statusbarButtons.png b/WebCore/inspector/front-end/Images/statusbarButtons.png Binary files differnew file mode 100644 index 0000000..e8090cb --- /dev/null +++ b/WebCore/inspector/front-end/Images/statusbarButtons.png diff --git a/WebCore/inspector/front-end/Images/statusbarMenuButton.png b/WebCore/inspector/front-end/Images/statusbarMenuButton.png Binary files differnew file mode 100644 index 0000000..9b3abdd --- /dev/null +++ b/WebCore/inspector/front-end/Images/statusbarMenuButton.png diff --git a/WebCore/inspector/front-end/Images/statusbarMenuButtonSelected.png b/WebCore/inspector/front-end/Images/statusbarMenuButtonSelected.png Binary files differnew file mode 100644 index 0000000..8189c43 --- /dev/null +++ b/WebCore/inspector/front-end/Images/statusbarMenuButtonSelected.png diff --git a/WebCore/inspector/front-end/Images/statusbarResizerHorizontal.png b/WebCore/inspector/front-end/Images/statusbarResizerHorizontal.png Binary files differnew file mode 100644 index 0000000..56deeab --- /dev/null +++ b/WebCore/inspector/front-end/Images/statusbarResizerHorizontal.png diff --git a/WebCore/inspector/front-end/Images/statusbarResizerVertical.png b/WebCore/inspector/front-end/Images/statusbarResizerVertical.png Binary files differnew file mode 100644 index 0000000..7fc1452 --- /dev/null +++ b/WebCore/inspector/front-end/Images/statusbarResizerVertical.png diff --git a/WebCore/inspector/front-end/Images/timelineHollowPillBlue.png b/WebCore/inspector/front-end/Images/timelineHollowPillBlue.png Binary files differnew file mode 100644 index 0000000..c7c273b --- /dev/null +++ b/WebCore/inspector/front-end/Images/timelineHollowPillBlue.png diff --git a/WebCore/inspector/front-end/Images/timelineHollowPillGray.png b/WebCore/inspector/front-end/Images/timelineHollowPillGray.png Binary files differnew file mode 100644 index 0000000..9ff37ef --- /dev/null +++ b/WebCore/inspector/front-end/Images/timelineHollowPillGray.png diff --git a/WebCore/inspector/front-end/Images/timelineHollowPillGreen.png b/WebCore/inspector/front-end/Images/timelineHollowPillGreen.png Binary files differnew file mode 100644 index 0000000..cc5a8f3 --- /dev/null +++ b/WebCore/inspector/front-end/Images/timelineHollowPillGreen.png diff --git a/WebCore/inspector/front-end/Images/timelineHollowPillOrange.png b/WebCore/inspector/front-end/Images/timelineHollowPillOrange.png Binary files differnew file mode 100644 index 0000000..08a81e4 --- /dev/null +++ b/WebCore/inspector/front-end/Images/timelineHollowPillOrange.png diff --git a/WebCore/inspector/front-end/Images/timelineHollowPillPurple.png b/WebCore/inspector/front-end/Images/timelineHollowPillPurple.png Binary files differnew file mode 100644 index 0000000..565a05c --- /dev/null +++ b/WebCore/inspector/front-end/Images/timelineHollowPillPurple.png diff --git a/WebCore/inspector/front-end/Images/timelineHollowPillRed.png b/WebCore/inspector/front-end/Images/timelineHollowPillRed.png Binary files differnew file mode 100644 index 0000000..c3a1b9b --- /dev/null +++ b/WebCore/inspector/front-end/Images/timelineHollowPillRed.png diff --git a/WebCore/inspector/front-end/Images/timelineHollowPillYellow.png b/WebCore/inspector/front-end/Images/timelineHollowPillYellow.png Binary files differnew file mode 100644 index 0000000..780045b --- /dev/null +++ b/WebCore/inspector/front-end/Images/timelineHollowPillYellow.png diff --git a/WebCore/inspector/front-end/Images/timelinePillBlue.png b/WebCore/inspector/front-end/Images/timelinePillBlue.png Binary files differnew file mode 100644 index 0000000..c897faa --- /dev/null +++ b/WebCore/inspector/front-end/Images/timelinePillBlue.png diff --git a/WebCore/inspector/front-end/Images/timelinePillGray.png b/WebCore/inspector/front-end/Images/timelinePillGray.png Binary files differnew file mode 100644 index 0000000..2128896 --- /dev/null +++ b/WebCore/inspector/front-end/Images/timelinePillGray.png diff --git a/WebCore/inspector/front-end/Images/timelinePillGreen.png b/WebCore/inspector/front-end/Images/timelinePillGreen.png Binary files differnew file mode 100644 index 0000000..9b66125 --- /dev/null +++ b/WebCore/inspector/front-end/Images/timelinePillGreen.png diff --git a/WebCore/inspector/front-end/Images/timelinePillOrange.png b/WebCore/inspector/front-end/Images/timelinePillOrange.png Binary files differnew file mode 100644 index 0000000..dd944fb --- /dev/null +++ b/WebCore/inspector/front-end/Images/timelinePillOrange.png diff --git a/WebCore/inspector/front-end/Images/timelinePillPurple.png b/WebCore/inspector/front-end/Images/timelinePillPurple.png Binary files differnew file mode 100644 index 0000000..21b96f7 --- /dev/null +++ b/WebCore/inspector/front-end/Images/timelinePillPurple.png diff --git a/WebCore/inspector/front-end/Images/timelinePillRed.png b/WebCore/inspector/front-end/Images/timelinePillRed.png Binary files differnew file mode 100644 index 0000000..f5e213b --- /dev/null +++ b/WebCore/inspector/front-end/Images/timelinePillRed.png diff --git a/WebCore/inspector/front-end/Images/timelinePillYellow.png b/WebCore/inspector/front-end/Images/timelinePillYellow.png Binary files differnew file mode 100644 index 0000000..ae2a5a2 --- /dev/null +++ b/WebCore/inspector/front-end/Images/timelinePillYellow.png diff --git a/WebCore/inspector/front-end/Images/tipBalloon.png b/WebCore/inspector/front-end/Images/tipBalloon.png Binary files differnew file mode 100644 index 0000000..4cdf738 --- /dev/null +++ b/WebCore/inspector/front-end/Images/tipBalloon.png diff --git a/WebCore/inspector/front-end/Images/tipBalloonBottom.png b/WebCore/inspector/front-end/Images/tipBalloonBottom.png Binary files differnew file mode 100644 index 0000000..3317a5a --- /dev/null +++ b/WebCore/inspector/front-end/Images/tipBalloonBottom.png diff --git a/WebCore/inspector/front-end/Images/tipIcon.png b/WebCore/inspector/front-end/Images/tipIcon.png Binary files differnew file mode 100644 index 0000000..8ca6124 --- /dev/null +++ b/WebCore/inspector/front-end/Images/tipIcon.png diff --git a/WebCore/inspector/front-end/Images/tipIconPressed.png b/WebCore/inspector/front-end/Images/tipIconPressed.png Binary files differnew file mode 100644 index 0000000..443e410 --- /dev/null +++ b/WebCore/inspector/front-end/Images/tipIconPressed.png diff --git a/WebCore/inspector/front-end/Images/toolbarItemSelected.png b/WebCore/inspector/front-end/Images/toolbarItemSelected.png Binary files differnew file mode 100644 index 0000000..bd681f1 --- /dev/null +++ b/WebCore/inspector/front-end/Images/toolbarItemSelected.png diff --git a/WebCore/inspector/front-end/Images/treeDownTriangleBlack.png b/WebCore/inspector/front-end/Images/treeDownTriangleBlack.png Binary files differnew file mode 100644 index 0000000..0821112 --- /dev/null +++ b/WebCore/inspector/front-end/Images/treeDownTriangleBlack.png diff --git a/WebCore/inspector/front-end/Images/treeDownTriangleWhite.png b/WebCore/inspector/front-end/Images/treeDownTriangleWhite.png Binary files differnew file mode 100644 index 0000000..1667b51 --- /dev/null +++ b/WebCore/inspector/front-end/Images/treeDownTriangleWhite.png diff --git a/WebCore/inspector/front-end/Images/treeRightTriangleBlack.png b/WebCore/inspector/front-end/Images/treeRightTriangleBlack.png Binary files differnew file mode 100644 index 0000000..90de820 --- /dev/null +++ b/WebCore/inspector/front-end/Images/treeRightTriangleBlack.png diff --git a/WebCore/inspector/front-end/Images/treeRightTriangleWhite.png b/WebCore/inspector/front-end/Images/treeRightTriangleWhite.png Binary files differnew file mode 100644 index 0000000..2b6a82f --- /dev/null +++ b/WebCore/inspector/front-end/Images/treeRightTriangleWhite.png diff --git a/WebCore/inspector/front-end/Images/treeUpTriangleBlack.png b/WebCore/inspector/front-end/Images/treeUpTriangleBlack.png Binary files differnew file mode 100644 index 0000000..ef69dbc --- /dev/null +++ b/WebCore/inspector/front-end/Images/treeUpTriangleBlack.png diff --git a/WebCore/inspector/front-end/Images/treeUpTriangleWhite.png b/WebCore/inspector/front-end/Images/treeUpTriangleWhite.png Binary files differnew file mode 100644 index 0000000..43ce4be --- /dev/null +++ b/WebCore/inspector/front-end/Images/treeUpTriangleWhite.png diff --git a/WebCore/inspector/front-end/Images/userInputIcon.png b/WebCore/inspector/front-end/Images/userInputIcon.png Binary files differnew file mode 100644 index 0000000..325023f --- /dev/null +++ b/WebCore/inspector/front-end/Images/userInputIcon.png diff --git a/WebCore/inspector/front-end/Images/userInputPreviousIcon.png b/WebCore/inspector/front-end/Images/userInputPreviousIcon.png Binary files differnew file mode 100644 index 0000000..068d572 --- /dev/null +++ b/WebCore/inspector/front-end/Images/userInputPreviousIcon.png diff --git a/WebCore/inspector/front-end/Images/warningIcon.png b/WebCore/inspector/front-end/Images/warningIcon.png Binary files differnew file mode 100644 index 0000000..d5e4c82 --- /dev/null +++ b/WebCore/inspector/front-end/Images/warningIcon.png diff --git a/WebCore/inspector/front-end/Images/warningMediumIcon.png b/WebCore/inspector/front-end/Images/warningMediumIcon.png Binary files differnew file mode 100644 index 0000000..291e111 --- /dev/null +++ b/WebCore/inspector/front-end/Images/warningMediumIcon.png diff --git a/WebCore/inspector/front-end/Images/warningsErrors.png b/WebCore/inspector/front-end/Images/warningsErrors.png Binary files differnew file mode 100644 index 0000000..878b593 --- /dev/null +++ b/WebCore/inspector/front-end/Images/warningsErrors.png diff --git a/WebCore/inspector/front-end/MetricsSidebarPane.js b/WebCore/inspector/front-end/MetricsSidebarPane.js new file mode 100644 index 0000000..a22a000 --- /dev/null +++ b/WebCore/inspector/front-end/MetricsSidebarPane.js @@ -0,0 +1,195 @@ +/* + * 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")); +} + +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; + + var style; + if (node.nodeType === Node.ELEMENT_NODE) + style = node.ownerDocument.defaultView.getComputedStyle(node); + if (!style) + return; + + 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"; + + this.node.style.setProperty(context.styleProperty, userInput, ""); + + this.dispatchEventToListeners("metrics edited"); + + this.update(); + } +} + +WebInspector.MetricsSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype; diff --git a/WebCore/inspector/front-end/Object.js b/WebCore/inspector/front-end/Object.js new file mode 100644 index 0000000..80202b0 --- /dev/null +++ b/WebCore/inspector/front-end/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/WebCore/inspector/front-end/ObjectPropertiesSection.js b/WebCore/inspector/front-end/ObjectPropertiesSection.js new file mode 100644 index 0000000..d240998 --- /dev/null +++ b/WebCore/inspector/front-end/ObjectPropertiesSection.js @@ -0,0 +1,276 @@ +/* + * 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.ObjectPropertiesSection = function(object, title, subtitle, emptyPlaceholder, ignoreHasOwnProperty, extraProperties, treeElementConstructor) +{ + if (!title) { + title = Object.describe(object); + if (title.match(/Prototype$/)) { + title = title.replace(/Prototype$/, ""); + if (!subtitle) + subtitle = WebInspector.UIString("Prototype"); + } + } + + 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 properties = []; + for (var prop in this.object) + properties.push(prop); + if (this.extraProperties) + for (var prop in this.extraProperties) + properties.push(prop); + properties.sort(); + + this.propertiesTreeOutline.removeChildren(); + + for (var i = 0; i < properties.length; ++i) { + var object = this.object; + var propertyName = properties[i]; + if (this.extraProperties && propertyName in this.extraProperties) + object = this.extraProperties; + if (propertyName === "__treeElementIdentifier") + continue; + if (!this.ignoreHasOwnProperty && "hasOwnProperty" in object && !object.hasOwnProperty(propertyName)) + continue; + this.propertiesTreeOutline.appendChild(new this.treeElementConstructor(object, propertyName)); + } + + 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.ObjectPropertyTreeElement = function(parentObject, propertyName) +{ + this.parentObject = parentObject; + this.propertyName = propertyName; + + // Pass an empty title, the title gets made later in onattach. + TreeElement.call(this, "", null, false); +} + +WebInspector.ObjectPropertyTreeElement.prototype = { + safePropertyValue: function(object, propertyName) + { + if (object["__lookupGetter__"] && object.__lookupGetter__(propertyName)) + return; + return object[propertyName]; + }, + + onpopulate: function() + { + if (this.children.length && !this.shouldRefreshChildren) + return; + + this.removeChildren(); + + var childObject = this.safePropertyValue(this.parentObject, this.propertyName); + var properties = Object.sortedProperties(childObject); + for (var i = 0; i < properties.length; ++i) { + var propertyName = properties[i]; + if (propertyName === "__treeElementIdentifier") + continue; + this.appendChild(new this.treeOutline.section.treeElementConstructor(childObject, propertyName)); + } + }, + + ondblclick: function(element, event) + { + this.startEditing(); + }, + + onattach: function() + { + this.update(); + }, + + update: function() + { + var childObject = this.safePropertyValue(this.parentObject, this.propertyName); + var isGetter = ("__lookupGetter__" in this.parentObject && this.parentObject.__lookupGetter__(this.propertyName)); + + var nameElement = document.createElement("span"); + nameElement.className = "name"; + nameElement.textContent = this.propertyName; + + this.valueElement = document.createElement("span"); + this.valueElement.className = "value"; + if (!isGetter) { + this.valueElement.textContent = Object.describe(childObject, true); + } else { + // FIXME: this should show something like "getter" (bug 16734). + this.valueElement.textContent = "\u2014"; // em dash + this.valueElement.addStyleClass("dimmed"); + } + + this.listItemElement.removeChildren(); + + this.listItemElement.appendChild(nameElement); + this.listItemElement.appendChild(document.createTextNode(": ")); + this.listItemElement.appendChild(this.valueElement); + + var hasSubProperties = false; + var type = typeof childObject; + if (childObject && (type === "object" || type === "function")) { + for (subPropertyName in childObject) { + if (subPropertyName === "__treeElementIdentifier") + continue; + hasSubProperties = true; + break; + } + } + + this.hasChildren = hasSubProperties; + }, + + 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); + }, + + evaluateExpression: function(expression) + { + // Evaluate in the currently selected call frame if the debugger is paused. + // Otherwise evaluate in against the inspected window. + if (WebInspector.panels.scripts.paused && this.treeOutline.section.editInSelectedCallFrameWhenPaused) + return WebInspector.panels.scripts.evaluateInSelectedCallFrame(expression, false); + return InspectorController.inspectedWindow().eval(expression); + }, + + applyExpression: function(expression, updateInterface) + { + var expressionLength = expression.trimWhitespace().length; + + if (!expressionLength) { + // The user deleted everything, so try to delete the property. + delete this.parentObject[this.propertyName]; + + if (updateInterface) { + if (this.propertyName in this.parentObject) { + // The property was not deleted, so update. + this.update(); + } else { + // The property was deleted, so remove this tree element. + this.parent.removeChild(this); + } + } + + return; + } + + 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. + var result = this.evaluateExpression("(" + expression + ")"); + + // Store the result in the property. + this.parentObject[this.propertyName] = result; + } catch(e) { + try { + // Try to update as a string + var result = this.evaluateExpression("\"" + expression.escapeCharacters("\"") + "\""); + + // Store the result in the property. + this.parentObject[this.propertyName] = result; + } catch(e) { + // The expression failed so don't change the value. So just update and return. + if (updateInterface) + this.update(); + return; + } + } + + if (updateInterface) { + // Call updateSiblings since their value might be based on the value that just changed. + this.updateSiblings(); + } + } +} + +WebInspector.ObjectPropertyTreeElement.prototype.__proto__ = TreeElement.prototype; diff --git a/WebCore/inspector/front-end/Panel.js b/WebCore/inspector/front-end/Panel.js new file mode 100644 index 0000000..5046f6b --- /dev/null +++ b/WebCore/inspector/front-end/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/WebCore/inspector/front-end/PanelEnablerView.js b/WebCore/inspector/front-end/PanelEnablerView.js new file mode 100644 index 0000000..6ec565b --- /dev/null +++ b/WebCore/inspector/front-end/PanelEnablerView.js @@ -0,0 +1,76 @@ +/* + * 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); + + 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"); + } +} + +WebInspector.PanelEnablerView.prototype.__proto__ = WebInspector.View.prototype; diff --git a/WebCore/inspector/front-end/Placard.js b/WebCore/inspector/front-end/Placard.js new file mode 100644 index 0000000..69a168e --- /dev/null +++ b/WebCore/inspector/front-end/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/WebCore/inspector/front-end/ProfileView.js b/WebCore/inspector/front-end/ProfileView.js new file mode 100644 index 0000000..92e9726 --- /dev/null +++ b/WebCore/inspector/front-end/ProfileView.js @@ -0,0 +1,642 @@ +/* + * 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; + + var columns = { "self": { title: WebInspector.UIString("Self"), width: "72px", sort: "descending", sortable: true }, + "total": { title: WebInspector.UIString("Total"), width: "72px", sortable: true }, + "calls": { title: WebInspector.UIString("Calls"), width: "54px", sortable: true }, + "function": { title: WebInspector.UIString("Function"), disclosure: true, 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.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 = document.createElement("button"); + this.percentButton.className = "percent-time-status-bar-item status-bar-item"; + this.percentButton.addEventListener("click", this._percentClicked.bind(this), false); + + this.focusButton = document.createElement("button"); + this.focusButton.title = WebInspector.UIString("Focus selected function."); + this.focusButton.className = "focus-profile-node-status-bar-item status-bar-item"; + this.focusButton.disabled = true; + this.focusButton.addEventListener("click", this._focusClicked.bind(this), false); + + this.excludeButton = document.createElement("button"); + this.excludeButton.title = WebInspector.UIString("Exclude selected function."); + this.excludeButton.className = "exclude-profile-node-status-bar-item status-bar-item"; + this.excludeButton.disabled = true; + this.excludeButton.addEventListener("click", this._excludeClicked.bind(this), false); + + this.resetButton = document.createElement("button"); + this.resetButton.title = WebInspector.UIString("Restore all functions."); + this.resetButton.className = "reset-profile-status-bar-item status-bar-item hidden"; + this.resetButton.addEventListener("click", this._resetClicked.bind(this), false); + + // Default to the heavy profile. + profile = profile.heavyProfile; + + // By default the profile isn't sorted, so sort based on our default sort + // column and direction added to the DataGrid columns above. + profile.sortSelfTimeDescending(); + + this._updatePercentButton(); + + this.profile = profile; +} + +WebInspector.ProfileView.prototype = { + get statusBarItems() + { + return [this.viewSelectElement, this.percentButton, this.focusButton, this.excludeButton, this.resetButton]; + }, + + get profile() + { + return this._profile; + }, + + set profile(profile) + { + this._profile = profile; + this.refresh(); + }, + + hide: function() + { + WebInspector.View.prototype.hide.call(this); + this._currentSearchResultIndex = -1; + }, + + refresh: function() + { + var selectedProfileNode = this.dataGrid.selectedNode ? this.dataGrid.selectedNode.profileNode : null; + + this.dataGrid.removeChildren(); + + var children = this.profile.head.children; + var childrenLength = children.length; + for (var i = 0; i < childrenLength; ++i) + if (children[i].visible) + this.dataGrid.appendChild(new WebInspector.ProfileDataGridNode(this, children[i])); + + if (selectedProfileNode && selectedProfileNode._dataGridNode) + selectedProfileNode._dataGridNode.selected = true; + }, + + refreshShowAsPercents: function() + { + this._updatePercentButton(); + + var child = this.dataGrid.children[0]; + while (child) { + child.refresh(); + child = child.traverseNextNode(false, null, true); + } + }, + + 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; + + if (profileNode._dataGridNode) + profileNode._dataGridNode.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(profileNode) + { + delete profileNode._searchMatchedSelfColumn; + delete profileNode._searchMatchedTotalColumn; + delete profileNode._searchMatchedCallsColumn; + delete profileNode._searchMatchedFunctionColumn; + + if (percentUnits) { + if (lessThan) { + if (profileNode.selfPercent < queryNumber) + profileNode._searchMatchedSelfColumn = true; + if (profileNode.totalPercent < queryNumber) + profileNode._searchMatchedTotalColumn = true; + } else if (greaterThan) { + if (profileNode.selfPercent > queryNumber) + profileNode._searchMatchedSelfColumn = true; + if (profileNode.totalPercent > queryNumber) + profileNode._searchMatchedTotalColumn = true; + } + + if (equalTo) { + if (profileNode.selfPercent == queryNumber) + profileNode._searchMatchedSelfColumn = true; + if (profileNode.totalPercent == queryNumber) + profileNode._searchMatchedTotalColumn = true; + } + } else if (millisecondsUnits || secondsUnits) { + if (lessThan) { + if (profileNode.selfTime < queryNumberMilliseconds) + profileNode._searchMatchedSelfColumn = true; + if (profileNode.totalTime < queryNumberMilliseconds) + profileNode._searchMatchedTotalColumn = true; + } else if (greaterThan) { + if (profileNode.selfTime > queryNumberMilliseconds) + profileNode._searchMatchedSelfColumn = true; + if (profileNode.totalTime > queryNumberMilliseconds) + profileNode._searchMatchedTotalColumn = true; + } + + if (equalTo) { + if (profileNode.selfTime == queryNumberMilliseconds) + profileNode._searchMatchedSelfColumn = true; + if (profileNode.totalTime == queryNumberMilliseconds) + profileNode._searchMatchedTotalColumn = true; + } + } else { + if (equalTo && profileNode.numberOfCalls == queryNumber) + profileNode._searchMatchedCallsColumn = true; + if (greaterThan && profileNode.numberOfCalls > queryNumber) + profileNode._searchMatchedCallsColumn = true; + if (lessThan && profileNode.numberOfCalls < queryNumber) + profileNode._searchMatchedCallsColumn = true; + } + + if (profileNode.functionName.hasSubstring(query, true) || profileNode.url.hasSubstring(query, true)) + profileNode._searchMatchedFunctionColumn = true; + + var matched = (profileNode._searchMatchedSelfColumn || profileNode._searchMatchedTotalColumn || profileNode._searchMatchedCallsColumn || profileNode._searchMatchedFunctionColumn); + if (matched && profileNode._dataGridNode) + profileNode._dataGridNode.refresh(); + + return matched; + } + + var current = this.profile.head; + var ancestors = []; + var nextIndexes = []; + var startIndex = 0; + + while (current) { + var children = current.children; + var childrenLength = children.length; + + if (startIndex >= childrenLength) { + current = ancestors.pop(); + startIndex = nextIndexes.pop(); + continue; + } + + for (var i = startIndex; i < childrenLength; ++i) { + var child = children[i]; + + if (matchesQuery(child)) { + if (child._dataGridNode) { + // The child has a data grid node already, no need to remember the ancestors. + this._searchResults.push({ profileNode: child }); + } else { + var ancestorsCopy = [].concat(ancestors); + ancestorsCopy.push(current); + this._searchResults.push({ profileNode: child, ancestors: ancestorsCopy }); + } + } + + if (child.children.length) { + ancestors.push(current); + nextIndexes.push(i + 1); + current = child; + startIndex = 0; + break; + } + + if (i === (childrenLength - 1)) { + current = ancestors.pop(); + startIndex = nextIndexes.pop(); + } + } + } + + 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 = this._searchResults[index].profileNode; + if (!profileNode._dataGridNode && searchResult.ancestors) { + var ancestors = searchResult.ancestors; + for (var i = 0; i < ancestors.length; ++i) { + var ancestorProfileNode = ancestors[i]; + var gridNode = ancestorProfileNode._dataGridNode; + if (gridNode) + gridNode.expand(); + } + + // No need to keep the ancestors around. + delete searchResult.ancestors; + } + + gridNode = profileNode._dataGridNode; + if (!gridNode) + return; + + gridNode.reveal(); + gridNode.select(); + }, + + _changeView: function(event) + { + if (!event || !this.profile) + return; + + if (event.target.selectedIndex == 1 && this.view == "Heavy") { + this._sortProfile(this.profile.treeProfile); + this.profile = this.profile.treeProfile; + this.view = "Tree"; + } else if (event.target.selectedIndex == 0 && this.view == "Tree") { + this._sortProfile(this.profile.heavyProfile); + this.profile = this.profile.heavyProfile; + 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.showSelfTimeAsPercent = !currentState; + this.showTotalTimeAsPercent = !currentState; + this.refreshShowAsPercents(); + }, + + _updatePercentButton: function() + { + if (this.showSelfTimeAsPercent && this.showTotalTimeAsPercent) { + this.percentButton.title = WebInspector.UIString("Show absolute total and self times."); + this.percentButton.addStyleClass("toggled-on"); + } else { + this.percentButton.title = WebInspector.UIString("Show total and self times as percentages."); + this.percentButton.removeStyleClass("toggled-on"); + } + }, + + _focusClicked: function(event) + { + if (!this.dataGrid.selectedNode || !this.dataGrid.selectedNode.profileNode) + return; + this.resetButton.removeStyleClass("hidden"); + this.profile.focus(this.dataGrid.selectedNode.profileNode); + this.refresh(); + }, + + _excludeClicked: function(event) + { + if (!this.dataGrid.selectedNode || !this.dataGrid.selectedNode.profileNode) + return; + this.resetButton.removeStyleClass("hidden"); + this.profile.exclude(this.dataGrid.selectedNode.profileNode); + this.dataGrid.selectedNode.deselect(); + this.refresh(); + }, + + _resetClicked: function(event) + { + this.resetButton.addStyleClass("hidden"); + this.profile.restoreAll(); + this.refresh(); + }, + + _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(profile) + { + if (!profile) + return; + + var sortOrder = this.dataGrid.sortOrder; + var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier; + + var sortingFunctionName = "sort"; + + if (sortColumnIdentifier === "self") + sortingFunctionName += "SelfTime"; + else if (sortColumnIdentifier === "total") + sortingFunctionName += "TotalTime"; + else if (sortColumnIdentifier === "calls") + sortingFunctionName += "Calls"; + else if (sortColumnIdentifier === "function") + sortingFunctionName += "FunctionName"; + + if (sortOrder === "ascending") + sortingFunctionName += "Ascending"; + else + sortingFunctionName += "Descending"; + + if (!(sortingFunctionName in this.profile)) + return; + + profile[sortingFunctionName](); + + if (profile === this.profile) + 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"))) + return; + + if (cell.hasStyleClass("total-column")) + this.showTotalTimeAsPercent = !this.showTotalTimeAsPercent; + else if (cell.hasStyleClass("self-column")) + this.showSelfTimeAsPercent = !this.showSelfTimeAsPercent; + + this.refreshShowAsPercents(); + + event.preventDefault(); + event.stopPropagation(); + } +} + +WebInspector.ProfileView.prototype.__proto__ = WebInspector.View.prototype; + +WebInspector.ProfileDataGridNode = function(profileView, profileNode) +{ + this.profileView = profileView; + + this.profileNode = profileNode; + profileNode._dataGridNode = this; + + // Find the first child that is visible. Since we don't want to claim + // we have children if all the children are invisible. + var hasChildren = false; + var children = this.profileNode.children; + var childrenLength = children.length; + for (var i = 0; i < childrenLength; ++i) { + if (children[i].visible) { + hasChildren = true; + break; + } + } + + WebInspector.DataGridNode.call(this, null, hasChildren); + + this.addEventListener("populate", this._populate, this); + + this.expanded = profileNode._expanded; +} + +WebInspector.ProfileDataGridNode.prototype = { + get data() + { + function formatMilliseconds(time) + { + return Number.secondsToString(time / 1000, WebInspector.UIString.bind(WebInspector), true); + } + + var data = {}; + data["function"] = this.profileNode.functionName; + data["calls"] = this.profileNode.numberOfCalls; + + if (this.profileView.showSelfTimeAsPercent) + data["self"] = WebInspector.UIString("%.2f%%", this.profileNode.selfPercent); + else + data["self"] = formatMilliseconds(this.profileNode.selfTime); + + if (this.profileView.showTotalTimeAsPercent) + data["total"] = WebInspector.UIString("%.2f%%", this.profileNode.totalPercent); + else + data["total"] = formatMilliseconds(this.profileNode.totalTime); + + return data; + }, + + createCell: function(columnIdentifier) + { + var cell = WebInspector.DataGridNode.prototype.createCell.call(this, columnIdentifier); + + if (columnIdentifier === "self" && this.profileNode._searchMatchedSelfColumn) + cell.addStyleClass("highlight"); + else if (columnIdentifier === "total" && this.profileNode._searchMatchedTotalColumn) + cell.addStyleClass("highlight"); + else if (columnIdentifier === "calls" && this.profileNode._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() + { + WebInspector.DataGridNode.prototype.expand.call(this); + this.profileNode._expanded = true; + }, + + collapse: function() + { + WebInspector.DataGridNode.prototype.collapse.call(this); + this.profileNode._expanded = false; + }, + + _populate: function(event) + { + var children = this.profileNode.children; + var childrenLength = children.length; + for (var i = 0; i < childrenLength; ++i) + if (children[i].visible) + this.appendChild(new WebInspector.ProfileDataGridNode(this.profileView, children[i])); + this.removeEventListener("populate", this._populate, this); + } +} + +WebInspector.ProfileDataGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype; diff --git a/WebCore/inspector/front-end/ProfilesPanel.js b/WebCore/inspector/front-end/ProfilesPanel.js new file mode 100644 index 0000000..ea3b85c --- /dev/null +++ b/WebCore/inspector/front-end/ProfilesPanel.js @@ -0,0 +1,504 @@ +/* + * 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.profileViews = document.createElement("div"); + this.profileViews.id = "profile-views"; + this.element.appendChild(this.profileViews); + + this.enableToggleButton = document.createElement("button"); + this.enableToggleButton.className = "enable-toggle-status-bar-item status-bar-item"; + this.enableToggleButton.addEventListener("click", this._toggleProfiling.bind(this), false); + + this.recordButton = document.createElement("button"); + this.recordButton.title = WebInspector.UIString("Start profiling."); + this.recordButton.id = "record-profile-status-bar-item"; + this.recordButton.className = "status-bar-item"; + this.recordButton.addEventListener("click", this._recordClicked.bind(this), false); + + this.recording = 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, this.recordButton, 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.sidebarTree.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.sidebarTree; + 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) + { + // Always use the treeProfile, since the heavy profile might be showing. + this.showProfile(view.profile.treeProfile); + }, + + 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) + title += " " + WebInspector.UIString("Run %d", groupNumber); + } + + 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) + { + // Always use the treeProfile, since the heavy profile might be showing. + view.profile.treeProfile._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.addStyleClass("toggled-on"); + this.recordButton.title = WebInspector.UIString("Stop profiling."); + } else { + this.recordButton.removeStyleClass("toggled-on"); + this.recordButton.title = WebInspector.UIString("Start profiling."); + } + }, + + _updateInterface: function() + { + if (InspectorController.profilerEnabled()) { + this.enableToggleButton.title = WebInspector.UIString("Profiling enabled. Click to disable."); + this.enableToggleButton.addStyleClass("toggled-on"); + this.recordButton.removeStyleClass("hidden"); + this.profileViewStatusBarItemsContainer.removeStyleClass("hidden"); + this.panelEnablerView.visible = false; + } else { + this.enableToggleButton.title = WebInspector.UIString("Profiling disabled. Click to enable."); + this.enableToggleButton.removeStyleClass("toggled-on"); + this.recordButton.addStyleClass("hidden"); + this.profileViewStatusBarItemsContainer.addStyleClass("hidden"); + this.panelEnablerView.visible = true; + } + }, + + _recordClicked: function() + { + this.recording = !this.recording; + + if (this.recording) + InspectorController.startProfiling(); + else + InspectorController.stopProfiling(); + }, + + _enableProfiling: function() + { + if (InspectorController.profilerEnabled()) + return; + this._toggleProfiling(); + }, + + _toggleProfiling: function() + { + if (InspectorController.profilerEnabled()) + InspectorController.disableProfiler(); + else + InspectorController.enableProfiler(); + }, + + _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"; + } +} + +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/WebCore/inspector/front-end/PropertiesSection.js b/WebCore/inspector/front-end/PropertiesSection.js new file mode 100644 index 0000000..3f1b937 --- /dev/null +++ b/WebCore/inspector/front-end/PropertiesSection.js @@ -0,0 +1,140 @@ +/* + * 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; + 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/WebCore/inspector/front-end/PropertiesSidebarPane.js b/WebCore/inspector/front-end/PropertiesSidebarPane.js new file mode 100644 index 0000000..70db805 --- /dev/null +++ b/WebCore/inspector/front-end/PropertiesSidebarPane.js @@ -0,0 +1,54 @@ +/* + * 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(object) + { + var body = this.bodyElement; + + body.removeChildren(); + + this.sections = []; + + if (!object) + return; + + for (var prototype = object; prototype; prototype = prototype.__proto__) { + var section = new WebInspector.ObjectPropertiesSection(prototype); + this.sections.push(section); + body.appendChild(section.element); + } + } +} + +WebInspector.PropertiesSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype; diff --git a/WebCore/inspector/front-end/Resource.js b/WebCore/inspector/front-end/Resource.js new file mode 100644 index 0000000..058f232 --- /dev/null +++ b/WebCore/inspector/front-end/Resource.js @@ -0,0 +1,625 @@ +/* + * 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) +{ + 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.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 documentNode() { + if ("identifier" in this) + return InspectorController.getResourceDocumentNode(this.identifier); + return null; + }, + + 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.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 addWarning = false; + var msg; + switch (warning.id) { + case WebInspector.Warnings.IncorrectMIMEType.id: + if (!this._mimeTypeIsConsistentWithType()) + msg = new WebInspector.ConsoleMessage(WebInspector.ConsoleMessage.MessageSource.Other, + 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/WebCore/inspector/front-end/ResourceCategory.js b/WebCore/inspector/front-end/ResourceCategory.js new file mode 100644 index 0000000..fc508d0 --- /dev/null +++ b/WebCore/inspector/front-end/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/WebCore/inspector/front-end/ResourceView.js b/WebCore/inspector/front-end/ResourceView.js new file mode 100644 index 0000000..b480362 --- /dev/null +++ b/WebCore/inspector/front-end/ResourceView.js @@ -0,0 +1,140 @@ +/* + * 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.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.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() + { + this.urlTreeElement.title = this.resource.url.escapeHTML(); + }, + + _refreshRequestHeaders: function() + { + this._refreshHeaders(WebInspector.UIString("Request Headers"), this.resource.sortedRequestHeaders, this.requestHeadersTreeElement); + }, + + _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/WebCore/inspector/front-end/ResourcesPanel.js b/WebCore/inspector/front-end/ResourcesPanel.js new file mode 100644 index 0000000..e02baf3 --- /dev/null +++ b/WebCore/inspector/front-end/ResourcesPanel.js @@ -0,0 +1,1649 @@ +/* + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2008 Anthony Ricaud (rik24d@gmail.com) + * + * 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.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.summaryElement = document.createElement("div"); + this.summaryElement.id = "resources-summary"; + this.containerContentElement.appendChild(this.summaryElement); + + 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.summaryGraphElement = document.createElement("canvas"); + this.summaryGraphElement.setAttribute("width", "450"); + this.summaryGraphElement.setAttribute("height", "38"); + this.summaryGraphElement.id = "resources-summary-graph"; + this.summaryElement.appendChild(this.summaryGraphElement); + + this.legendElement = document.createElement("div"); + this.legendElement.id = "resources-graph-legend"; + this.summaryElement.appendChild(this.legendElement); + + 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(); + + this.largerResourcesButton = document.createElement("button"); + this.largerResourcesButton.id = "resources-larger-resources-status-bar-item"; + this.largerResourcesButton.className = "status-bar-item toggled-on"; + this.largerResourcesButton.title = WebInspector.UIString("Use small resource rows."); + 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); + + this.reset(); + + timeGraphItem.select(); +} + +WebInspector.ResourcesPanel.prototype = { + toolbarItemClass: "resources", + + get toolbarItemLabel() + { + return WebInspector.UIString("Resources"); + }, + + get statusBarItems() + { + return [this.largerResourcesButton, 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(); + }, + + 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.legendElement.removeChildren(); + + this._updateGraphDividersIfNeeded(true); + + this._drawSummaryGraph(); // draws an empty graph + }, + + 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); + }, + + _makeLegendElement: function(label, value, color) + { + var legendElement = document.createElement("label"); + legendElement.className = "resources-graph-legend-item"; + + if (color) { + var swatch = document.createElement("canvas"); + swatch.className = "resources-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 = "resources-graph-legend-label"; + legendElement.appendChild(labelElement); + + var headerElement = document.createElement("div"); + var headerElement = document.createElement("div"); + headerElement.className = "resources-graph-legend-header"; + headerElement.textContent = label; + labelElement.appendChild(headerElement); + + var valueElement = document.createElement("div"); + valueElement.className = "resources-graph-legend-value"; + valueElement.textContent = value; + labelElement.appendChild(valueElement); + + return legendElement; + }, + + _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); + } + }, + + _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(); + }, + + _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 (!this.summaryGraphElement) + return; + + 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.summaryGraphElement.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); + }, + + _updateSummaryGraph: function() + { + var graphInfo = this.calculator.computeSummaryValues(this._resources); + + var categoryOrder = ["documents", "stylesheets", "images", "scripts", "xhr", "fonts", "other"]; + var categoryColors = {documents: {r: 47, g: 102, b: 236}, stylesheets: {r: 157, g: 231, b: 119}, images: {r: 164, g: 60, b: 255}, scripts: {r: 255, g: 121, b: 0}, xhr: {r: 231, g: 231, b: 10}, fonts: {r: 255, g: 82, b: 62}, other: {r: 186, g: 186, b: 186}}; + var fillSegments = []; + + this.legendElement.removeChildren(); + + for (var i = 0; i < categoryOrder.length; ++i) { + var category = categoryOrder[i]; + var size = graphInfo.categoryValues[category]; + if (!size) + continue; + + var color = categoryColors[category]; + var colorString = "rgb(" + color.r + ", " + color.g + ", " + color.b + ")"; + + var fillSegment = {color: colorString, value: size}; + fillSegments.push(fillSegment); + + var legendLabel = this._makeLegendElement(WebInspector.resourceCategories[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); + }, + + _updateDividersLabelBarPosition: function() + { + var scrollTop = this.containerElement.scrollTop; + var dividersTop = (scrollTop < this.summaryElement.offsetHeight ? this.summaryElement.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.removeStyleClass("toggled-on"); + this._adjustScrollPosition(); + } else { + this.resourcesGraphsElement.removeStyleClass("small"); + this.largerResourcesButton.title = WebInspector.UIString("Use small resource rows."); + this.largerResourcesButton.addStyleClass("toggled-on"); + } + }, + + _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 = 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(); + } +} + +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); + + this._listItemNode.addStyleClass("resources-category-" + this.resource.category.name); + }, + + onselect: function() + { + WebInspector.panels.resources.showResource(this.resource); + }, + + 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. + }, + + 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/WebCore/inspector/front-end/ScopeChainSidebarPane.js b/WebCore/inspector/front-end/ScopeChainSidebarPane.js new file mode 100644 index 0000000..157cee9 --- /dev/null +++ b/WebCore/inspector/front-end/ScopeChainSidebarPane.js @@ -0,0 +1,156 @@ +/* + * 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")); +} + +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; + } + + if (!callFrame._expandedProperties) { + // FIXME: fix this when https://bugs.webkit.org/show_bug.cgi?id=19410 is fixed. + // The callFrame is a JSInspectedObjectWrapper, so we are not allowed to assign + // an object created in the Inspector's context to that object. So create an + // Object from the inspectedWindow. + var inspectedWindow = InspectorController.inspectedWindow(); + callFrame._expandedProperties = new inspectedWindow.Object; + } + + var foundLocalScope = false; + var scopeChain = callFrame.scopeChain; + for (var i = 0; i < scopeChain.length; ++i) { + var scopeObject = scopeChain[i]; + var title = null; + var subtitle = Object.describe(scopeObject, true); + var emptyPlaceholder = null; + var localScope = false; + var extraProperties = null; + + if (Object.prototype.toString.call(scopeObject) === "[object JSActivation]") { + if (!foundLocalScope) { + extraProperties = { "this": callFrame.thisObject }; + title = WebInspector.UIString("Local"); + } else + title = WebInspector.UIString("Closure"); + emptyPlaceholder = WebInspector.UIString("No Variables"); + subtitle = null; + foundLocalScope = true; + localScope = true; + } else if (i === (scopeChain.length - 1)) + title = WebInspector.UIString("Global"); + else if (foundLocalScope && scopeObject instanceof InspectorController.inspectedWindow().Element) + title = WebInspector.UIString("Event Target"); + else if (foundLocalScope && scopeObject instanceof InspectorController.inspectedWindow().Document) + title = WebInspector.UIString("Event Document"); + else if (!foundLocalScope && !localScope) + title = WebInspector.UIString("With Block"); + + if (!title || title === subtitle) + subtitle = null; + + var section = new WebInspector.ObjectPropertiesSection(scopeObject, title, subtitle, emptyPlaceholder, true, extraProperties, WebInspector.ScopeVariableTreeElement); + section.editInSelectedCallFrameWhenPaused = true; + section.pane = this; + + if (!foundLocalScope || localScope) + section.expanded = true; + + this.sections.push(section); + this.bodyElement.appendChild(section.element); + } + } +} + +WebInspector.ScopeChainSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype; + +WebInspector.ScopeVariableTreeElement = function(parentObject, propertyName) +{ + WebInspector.ObjectPropertyTreeElement.call(this, parentObject, propertyName); +} + +WebInspector.ScopeVariableTreeElement.prototype = { + onattach: function() + { + WebInspector.ObjectPropertyTreeElement.prototype.onattach.call(this); + if (this.hasChildren && this.propertyIdentifier in this.treeOutline.section.pane.callFrame._expandedProperties) + this.expand(); + }, + + onexpand: function() + { + this.treeOutline.section.pane.callFrame._expandedProperties[this.propertyIdentifier] = true; + }, + + oncollapse: function() + { + delete this.treeOutline.section.pane.callFrame._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.propertyName + "." + result; + else + result = current.propertyName; + current = current.parent; + } while (current && !current.root); + + this._propertyPath = result; + return result; + } +} + +WebInspector.ScopeVariableTreeElement.prototype.__proto__ = WebInspector.ObjectPropertyTreeElement.prototype; diff --git a/WebCore/inspector/front-end/Script.js b/WebCore/inspector/front-end/Script.js new file mode 100644 index 0000000..46502a6 --- /dev/null +++ b/WebCore/inspector/front-end/Script.js @@ -0,0 +1,37 @@ +/* + * 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; +} + +WebInspector.Script.prototype = { +} diff --git a/WebCore/inspector/front-end/ScriptView.js b/WebCore/inspector/front-end/ScriptView.js new file mode 100644 index 0000000..124190c --- /dev/null +++ b/WebCore/inspector/front-end/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/WebCore/inspector/front-end/ScriptsPanel.js b/WebCore/inspector/front-end/ScriptsPanel.js new file mode 100644 index 0000000..1120adf --- /dev/null +++ b/WebCore/inspector/front-end/ScriptsPanel.js @@ -0,0 +1,802 @@ +/* + * 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.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.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); + + // FIXME: remove the following line of code when the Breakpoints pane has content. + this.sidebarElement.removeChild(this.sidebarPanes.breakpoints.element); + + this.sidebarPanes.callstack.expanded = true; + this.sidebarPanes.callstack.addEventListener("call frame selected", this._callFrameSelected, this); + + this.sidebarPanes.scopechain.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 = document.createElement("button"); + this.enableToggleButton.className = "enable-toggle-status-bar-item status-bar-item"; + this.enableToggleButton.addEventListener("click", this._toggleDebugging.bind(this), false); + + this.pauseOnExceptionButton = document.createElement("button"); + this.pauseOnExceptionButton.id = "scripts-pause-on-exceptions-status-bar-item"; + this.pauseOnExceptionButton.className = "status-bar-item"; + this.pauseOnExceptionButton.addEventListener("click", this._togglePauseOnExceptions.bind(this), false); + + this._breakpointsURLMap = {}; + + this.reset(); +} + +WebInspector.ScriptsPanel.prototype = { + toolbarItemClass: "scripts", + + get toolbarItemLabel() + { + return WebInspector.UIString("Scripts"); + }, + + get statusBarItems() + { + return [this.enableToggleButton, this.pauseOnExceptionButton]; + }, + + 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; + } + }, + + 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.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); + } + } + } + + if (sourceID) + this._sourceIDMap[sourceID] = (resource || script); + + this._addScriptToFilesMenu(script); + }, + + 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) + { + var selectedCallFrame = this.sidebarPanes.callstack.selectedCallFrame; + if (!this._paused || !selectedCallFrame) + return; + if (typeof updateInterface === "undefined") + updateInterface = true; + var result = selectedCallFrame.evaluate(code); + if (updateInterface) + this.sidebarPanes.scopechain.update(selectedCallFrame); + return result; + }, + + variablesInScopeForSelectedCallFrame: 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 scopeObject = scopeChain[i]; + for (var property in scopeObject) + result[property] = true; + } + + return result; + }, + + debuggerPaused: function() + { + this._paused = true; + this._waitingToPause = false; + this._stepping = false; + + this._updateDebuggerButtons(); + + var callStackPane = this.sidebarPanes.callstack; + var currentFrame = InspectorController.currentCallFrame(); + callStackPane.update(currentFrame, this._sourceIDMap); + callStackPane.selectedCallFrame = currentFrame; + + WebInspector.currentPanel = this; + window.focus(); + }, + + 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 = {}; + }, + + 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)); + }, + + 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) + return WebInspector.panels.resources.resourceViewForResource(scriptOrResource); + if (scriptOrResource instanceof WebInspector.Script) + return this.scriptViewForScript(scriptOrResource); + }, + + _sourceFrameForScriptOrResource: function(scriptOrResource) + { + if (scriptOrResource instanceof WebInspector.Resource) + 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) { + 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; + + // FIXME: Append in some meaningful order. + var option = document.createElement("option"); + option.representedObject = (script.resource || script); + option.text = (script.sourceURL ? WebInspector.displayNameForURL(script.sourceURL) : WebInspector.UIString("(program)")); + select.appendChild(option); + + 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); + + 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.addStyleClass("toggled-on"); + } else { + this.pauseOnExceptionButton.title = WebInspector.UIString("Pause on exceptions."); + this.pauseOnExceptionButton.removeStyleClass("toggled-on"); + } + }, + + _updateDebuggerButtons: function() + { + if (InspectorController.debuggerEnabled()) { + this.enableToggleButton.title = WebInspector.UIString("Debugging enabled. Click to disable."); + this.enableToggleButton.addStyleClass("toggled-on"); + this.pauseOnExceptionButton.removeStyleClass("hidden"); + this.panelEnablerView.visible = false; + } else { + this.enableToggleButton.title = WebInspector.UIString("Debugging disabled. Click to enable."); + this.enableToggleButton.removeStyleClass("toggled-on"); + this.pauseOnExceptionButton.addStyleClass("hidden"); + 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(); + }, + + _toggleDebugging: function() + { + this._paused = false; + this._waitingToPause = false; + this._stepping = false; + + if (InspectorController.debuggerEnabled()) + InspectorController.disableDebugger(); + else + InspectorController.enableDebugger(); + }, + + _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/WebCore/inspector/front-end/SidebarPane.js b/WebCore/inspector/front-end/SidebarPane.js new file mode 100644 index 0000000..af9e5f9 --- /dev/null +++ b/WebCore/inspector/front-end/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/WebCore/inspector/front-end/SidebarTreeElement.js b/WebCore/inspector/front-end/SidebarTreeElement.js new file mode 100644 index 0000000..c08b0ef --- /dev/null +++ b/WebCore/inspector/front-end/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/WebCore/inspector/front-end/SourceFrame.js b/WebCore/inspector/front-end/SourceFrame.js new file mode 100644 index 0000000..8d6d6d3 --- /dev/null +++ b/WebCore/inspector/front-end/SourceFrame.js @@ -0,0 +1,699 @@ +/* + * 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.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) + { + 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) + { + 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("mousedown", this._documentMouseDown.bind(this), true); + this.element.contentDocument.addEventListener("webkitAnimationEnd", this._highlightLineEnds.bind(this), false); + + 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-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-execution-line .webkit-line-number { color: transparent; background-image: -webkit-canvas(program-counter); }\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-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"; + + 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.hasStyleClass = Element.prototype.hasStyleClass; + this.element.contentWindow.Node.prototype.enclosingNodeOrSelfWithNodeName = Node.prototype.enclosingNodeOrSelfWithNodeName; + + this._addExistingMessagesToSource(); + this._addExistingBreakpointsToSource(); + this._updateExecutionLine(); + + if (this.autoSizesToFitContentHeight) + this.sizeToFitContentHeight(); + }, + + _windowResized: function(event) + { + if (!this._autoSizesToFitContentHeight) + return; + this.sizeToFitContentHeight(); + }, + + _documentMouseDown: function(event) + { + if (!event.target.hasStyleClass("webkit-line-number")) + return; + + var sourceRow = event.target.enclosingNodeOrSelfWithNodeName("tr"); + if (sourceRow._breakpointObject) + sourceRow._breakpointObject.enabled = !sourceRow._breakpointObject.enabled; + else if (this.addBreakpointDelegate) + this.addBreakpointDelegate(this.lineNumberForSourceRow(sourceRow)); + }, + + _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; + + this._drawBreakpointImagesIfNeeded(); + + sourceRow._breakpointObject = breakpoint; + + sourceRow.addStyleClass("webkit-breakpoint"); + if (!breakpoint.enabled) + sourceRow.addStyleClass("webkit-breakpoint-disabled"); + }, + + _removeBreakpointFromSource: function(breakpoint) + { + var sourceRow = this.sourceRow(breakpoint.line); + if (!sourceRow) + return; + + delete sourceRow._breakpointObject; + + sourceRow.removeStyleClass("webkit-breakpoint"); + sourceRow.removeStyleClass("webkit-breakpoint-disabled"); + }, + + _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() + { + if (!this._needsBreakpointImages || !this.element.contentDocument) + return; + + function drawBreakpoint(ctx, disabled) + { + 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 = "rgb(1, 142, 217)"; + ctx.strokeStyle = "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(); + } + + 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); + + 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/WebCore/inspector/front-end/SourceView.js b/WebCore/inspector/front-end/SourceView.js new file mode 100644 index 0000000..19f2521 --- /dev/null +++ b/WebCore/inspector/front-end/SourceView.js @@ -0,0 +1,301 @@ +/* + * 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(); + + if (!InspectorController.addResourceSourceToFrame(this.resource.identifier, this.sourceFrame.element)) + return; + + delete this._frameNeedsSetup; + + if (this.resource.type === WebInspector.Resource.Type.Script) { + 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; + } + } + + 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 = window.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/WebCore/inspector/front-end/StylesSidebarPane.js b/WebCore/inspector/front-end/StylesSidebarPane.js new file mode 100644 index 0000000..f0a9afb --- /dev/null +++ b/WebCore/inspector/front-end/StylesSidebarPane.js @@ -0,0 +1,927 @@ +/* + * 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.StylesSidebarPane = function() +{ + WebInspector.SidebarPane.call(this, WebInspector.UIString("Styles")); +} + +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 styleRules = []; + + if (refresh) { + for (var i = 0; i < this.sections.length; ++i) { + var section = this.sections[i]; + if (section.computedStyle) + section.styleRule.style = node.ownerDocument.defaultView.getComputedStyle(node); + var styleRule = { section: section, style: section.styleRule.style, computedStyle: section.computedStyle }; + 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); + } + } + + if (node.style && (node.style.length || Object.hasProperties(node.style.__disabledProperties))) { + var inlineStyle = { selectorText: WebInspector.UIString("Inline Style Attribute"), style: node.style }; + 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 }); + } + } + } + + 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; + + 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 = getUniqueStyleProperties(style); + 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; + + // 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 + section.expand(true); + + body.appendChild(section.element); + this.sections.push(section); + } + } + } +} + +WebInspector.StylesSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype; + +WebInspector.StylePropertiesSection = function(styleRule, subtitle, computedStyle, usedProperties, editable) +{ + WebInspector.PropertiesSection.call(this, styleRule.selectorText); + + this.styleRule = styleRule; + this.computedStyle = computedStyle; + this.editable = (editable && !computedStyle); + + // Prevent editing the user agent and user rules. + var isUserAgent = this.styleRule.parentStyleSheet && !this.styleRule.parentStyleSheet.ownerNode && !this.styleRule.parentStyleSheet.href; + var isUser = this.styleRule.parentStyleSheet && this.styleRule.parentStyleSheet.ownerNode && this.styleRule.parentStyleSheet.ownerNode.nodeName == '#document'; + 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 + 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) + 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) + 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 = getLonghandProperties(this.styleRule.style, property); + for (var j = 0; j < longhandProperties.length; ++j) { + var individualProperty = longhandProperties[j]; + if (individualProperty in this.usedProperties) + return false; + } + + return true; + }, + + 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); + } + } + }, + + onpopulate: function() + { + var style = this.styleRule.style; + + var foundShorthands = {}; + var uniqueProperties = getUniqueStyleProperties(style); + 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(style, name, isShorthand, inherited, overloaded, disabled); + this.propertiesTreeOutline.appendChild(item); + } + } +} + +WebInspector.StylePropertiesSection.prototype.__proto__ = WebInspector.PropertiesSection.prototype; + +WebInspector.StylePropertyTreeElement = function(style, name, shorthand, inherited, overloaded, disabled) +{ + 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 ? getShorthandPriority(this.style, 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 ? getShorthandValue(this.style, this.name) : this.style.getPropertyValue(this.name)); + }, + + onattach: function() + { + this.updateTitle(); + }, + + updateTitle: function() + { + // "Nicknames" for some common values that are easier to read. + var valueNicknames = { + "rgb(0, 0, 0)": "black", + "#000": "black", + "#000000": "black", + "rgb(255, 255, 255)": "white", + "#fff": "white", + "#ffffff": "white", + "#FFF": "white", + "#FFFFFF": "white", + "rgba(0, 0, 0, 0)": "transparent", + "rgb(255, 0, 0)": "red", + "rgb(0, 255, 0)": "lime", + "rgb(0, 0, 255)": "blue", + "rgb(255, 255, 0)": "yellow", + "rgb(255, 0, 255)": "magenta", + "rgb(0, 255, 255)": "cyan" + }; + + var priority = this.priority; + var value = this.value; + var htmlValue = value; + + if (priority && !priority.length) + delete priority; + if (priority) + priority = "!" + priority; + + if (value) { + var urls = value.match(/url\([^)]+\)/); + if (urls) { + for (var i = 0; i < urls.length; ++i) { + var url = urls[i].substring(4, urls[i].length - 1); + htmlValue = htmlValue.replace(urls[i], "url(" + WebInspector.linkifyURL(url) + ")"); + } + } else { + if (value in valueNicknames) + htmlValue = valueNicknames[value]; + htmlValue = htmlValue.escapeHTML(); + } + } else + htmlValue = value = ""; + + 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; + + var valueElement = document.createElement("span"); + valueElement.className = "value"; + valueElement.innerHTML = htmlValue; + + 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(";")); + + if (value) { + // FIXME: this dosen't catch keyword based colors like black and white + var colors = value.match(/((rgb|hsl)a?\([^)]+\))|(#[0-9a-fA-F]{6})|(#[0-9a-fA-F]{3})/g); + if (colors) { + var colorsLength = colors.length; + for (var i = 0; i < colorsLength; ++i) { + var swatchElement = document.createElement("span"); + swatchElement.className = "swatch"; + swatchElement.style.setProperty("background-color", colors[i]); + this.listItemElement.appendChild(swatchElement); + } + } + } + + this.tooltip = this.name + ": " + (valueNicknames[value] || value) + (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; + + if (disabled) { + if (!this.style.__disabledPropertyValues || !this.style.__disabledPropertyPriorities) { + var inspectedWindow = InspectorController.inspectedWindow(); + this.style.__disabledProperties = new inspectedWindow.Object; + this.style.__disabledPropertyValues = new inspectedWindow.Object; + this.style.__disabledPropertyPriorities = new inspectedWindow.Object; + } + + this.style.__disabledPropertyValues[this.name] = this.value; + this.style.__disabledPropertyPriorities[this.name] = this.priority; + + if (this.shorthand) { + var longhandProperties = getLonghandProperties(this.style, this.name); + for (var i = 0; i < longhandProperties.length; ++i) { + this.style.__disabledProperties[longhandProperties[i]] = true; + this.style.removeProperty(longhandProperties[i]); + } + } else { + this.style.__disabledProperties[this.name] = true; + this.style.removeProperty(this.name); + } + } else { + this.style.setProperty(this.name, this.value, this.priority); + delete this.style.__disabledProperties[this.name]; + delete this.style.__disabledPropertyValues[this.name]; + delete this.style.__disabledPropertyPriorities[this.name]; + } + + // Set the disabled property here, since the code above replies on it not changing + // until after the value and priority are retrieved. + this.disabled = disabled; + + if (this.treeOutline.section && this.treeOutline.section.pane) + this.treeOutline.section.pane.dispatchEventToListeners("style property toggled"); + + this.updateAll(true); + }, + + 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 = getLonghandProperties(this.style, 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.style, name, false, inherited, overloaded); + this.appendChild(item); + } + }, + + ondblclick: function(element, event) + { + this.startEditing(event.target); + }, + + 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 = getStyleTextWithShorthands(this.style); + } 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. + this.style.cssText = 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.originalCSSText) { + this.style.cssText = 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) + { + this.editingEnded(context); + + if (userInput === previousContent) + return; // nothing changed, so do nothing else + + this.applyStyleText(userInput, true); + }, + + applyStyleText: function(styleText, updateInterface) + { + var styleTextLength = styleText.trimWhitespace().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 (this.shorthand) { + var longhandProperties = getLonghandProperties(this.style, this.name); + for (var i = 0; i < longhandProperties.length; ++i) + this.style.removeProperty(longhandProperties[i]); + } else + this.style.removeProperty(this.name); + } + + if (!styleTextLength) { + if (updateInterface) { + // The user deleted the everything, so remove the tree element and update. + if (this.treeOutline.section && this.treeOutline.section.pane) + this.treeOutline.section.pane.update(); + this.parent.removeChild(this); + } + return; + } + + if (!tempStyle.length) { + // The user typed something, but it didn't parse. Just abort and restore + // the original title for this property. + if (updateInterface) + this.updateTitle(); + return; + } + + // 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 uniqueProperties = 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 = getShorthandValue(tempStyle, shorthand); + var priority = 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. + this.style.setProperty((shorthand || name), value, priority); + } + + if (this.treeOutline.section && this.treeOutline.section.pane) + this.treeOutline.section.pane.dispatchEventToListeners("style edited"); + + if (updateInterface) + this.updateAll(true); + } +} + +WebInspector.StylePropertyTreeElement.prototype.__proto__ = TreeElement.prototype; diff --git a/WebCore/inspector/front-end/TextPrompt.js b/WebCore/inspector/front-end/TextPrompt.js new file mode 100644 index 0000000..61e1b52 --- /dev/null +++ b/WebCore/inspector/front-end/TextPrompt.js @@ -0,0 +1,312 @@ +/* + * 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": + 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"); + var completions = this.completions(wordPrefixRange, auto); + + if (!completions || !completions.length) + return; + + var fullWordRange = document.createRange(); + fullWordRange.setStart(wordPrefixRange.startContainer, wordPrefixRange.startOffset); + fullWordRange.setEnd(selectionRange.endContainer, selectionRange.endOffset); + + 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 = wordPrefixRange.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/WebCore/inspector/front-end/View.js b/WebCore/inspector/front-end/View.js new file mode 100644 index 0000000..632a61a --- /dev/null +++ b/WebCore/inspector/front-end/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/WebCore/inspector/front-end/WebKit.qrc b/WebCore/inspector/front-end/WebKit.qrc new file mode 100644 index 0000000..4376a57 --- /dev/null +++ b/WebCore/inspector/front-end/WebKit.qrc @@ -0,0 +1,148 @@ +<!DOCTYPE RCC><RCC version="1.0"> +<qresource prefix="/webkit/inspector"> + <file>Breakpoint.js</file> + <file>BreakpointsSidebarPane.js</file> + <file>CallStackSidebarPane.js</file> + <file>Console.js</file> + <file>Database.js</file> + <file>DatabaseQueryView.js</file> + <file>DatabasesPanel.js</file> + <file>DatabaseTableView.js</file> + <file>DataGrid.js</file> + <file>ElementsPanel.js</file> + <file>ElementsTreeOutline.js</file> + <file>FontView.js</file> + <file>ImageView.js</file> + <file>inspector.css</file> + <file>inspector.html</file> + <file>inspector.js</file> + <file>MetricsSidebarPane.js</file> + <file>Object.js</file> + <file>ObjectPropertiesSection.js</file> + <file>Panel.js</file> + <file>PanelEnablerView.js</file> + <file>Placard.js</file> + <file>ProfilesPanel.js</file> + <file>ProfileView.js</file> + <file>PropertiesSection.js</file> + <file>PropertiesSidebarPane.js</file> + <file>ResourceCategory.js</file> + <file>Resource.js</file> + <file>ResourcesPanel.js</file> + <file>ResourceView.js</file> + <file>ScopeChainSidebarPane.js</file> + <file>Script.js</file> + <file>ScriptsPanel.js</file> + <file>ScriptView.js</file> + <file>SidebarPane.js</file> + <file>SidebarTreeElement.js</file> + <file>SourceFrame.js</file> + <file>SourceView.js</file> + <file>StylesSidebarPane.js</file> + <file>TextPrompt.js</file> + <file>treeoutline.js</file> + <file>utilities.js</file> + <file>View.js</file> + <file>Images/back.png</file> + <file>Images/checker.png</file> + <file>Images/clearConsoleButtons.png</file> + <file>Images/closeButtons.png</file> + <file>Images/consoleButtons.png</file> + <file>Images/database.png</file> + <file>Images/databasesIcon.png</file> + <file>Images/databaseTable.png</file> + <file>Images/debuggerContinue.png</file> + <file>Images/debuggerPause.png</file> + <file>Images/debuggerStepInto.png</file> + <file>Images/debuggerStepOut.png</file> + <file>Images/debuggerStepOver.png</file> + <file>Images/disclosureTriangleSmallDownBlack.png</file> + <file>Images/disclosureTriangleSmallDown.png</file> + <file>Images/disclosureTriangleSmallDownWhite.png</file> + <file>Images/disclosureTriangleSmallRightBlack.png</file> + <file>Images/disclosureTriangleSmallRightDownBlack.png</file> + <file>Images/disclosureTriangleSmallRightDown.png</file> + <file>Images/disclosureTriangleSmallRightDownWhite.png</file> + <file>Images/disclosureTriangleSmallRight.png</file> + <file>Images/disclosureTriangleSmallRightWhite.png</file> + <file>Images/dockButtons.png</file> + <file>Images/elementsIcon.png</file> + <file>Images/errorIcon.png</file> + <file>Images/errorMediumIcon.png</file> + <file>Images/excludeButtons.png</file> + <file>Images/focusButtons.png</file> + <file>Images/forward.png</file> + <file>Images/glossyHeader.png</file> + <file>Images/glossyHeaderPressed.png</file> + <file>Images/glossyHeaderSelected.png</file> + <file>Images/glossyHeaderSelectedPressed.png</file> + <file>Images/goArrow.png</file> + <file>Images/graphLabelCalloutLeft.png</file> + <file>Images/graphLabelCalloutRight.png</file> + <file>Images/largerResourcesButtons.png</file> + <file>Images/nodeSearchButtons.png</file> + <file>Images/paneBottomGrowActive.png</file> + <file>Images/paneBottomGrow.png</file> + <file>Images/paneGrowHandleLine.png</file> + <file>Images/pauseOnExceptionButtons.png</file> + <file>Images/percentButtons.png</file> + <file>Images/profileGroupIcon.png</file> + <file>Images/profileIcon.png</file> + <file>Images/profilesIcon.png</file> + <file>Images/profileSmallIcon.png</file> + <file>Images/recordButtons.png</file> + <file>Images/reloadButtons.png</file> + <file>Images/resourceCSSIcon.png</file> + <file>Images/resourceDocumentIcon.png</file> + <file>Images/resourceDocumentIconSmall.png</file> + <file>Images/resourceJSIcon.png</file> + <file>Images/resourcePlainIcon.png</file> + <file>Images/resourcePlainIconSmall.png</file> + <file>Images/resourcesIcon.png</file> + <file>Images/resourcesSizeGraphIcon.png</file> + <file>Images/resourcesTimeGraphIcon.png</file> + <file>Images/scriptsIcon.png</file> + <file>Images/searchSmallBlue.png</file> + <file>Images/searchSmallBrightBlue.png</file> + <file>Images/searchSmallGray.png</file> + <file>Images/searchSmallWhite.png</file> + <file>Images/segmentEnd.png</file> + <file>Images/segmentHoverEnd.png</file> + <file>Images/segmentHover.png</file> + <file>Images/segment.png</file> + <file>Images/segmentSelectedEnd.png</file> + <file>Images/segmentSelected.png</file> + <file>Images/splitviewDimple.png</file> + <file>Images/splitviewDividerBackground.png</file> + <file>Images/statusbarBackground.png</file> + <file>Images/statusbarBottomBackground.png</file> + <file>Images/statusbarButtons.png</file> + <file>Images/statusbarMenuButton.png</file> + <file>Images/statusbarMenuButtonSelected.png</file> + <file>Images/statusbarResizerHorizontal.png</file> + <file>Images/statusbarResizerVertical.png</file> + <file>Images/timelinePillBlue.png</file> + <file>Images/timelinePillGray.png</file> + <file>Images/timelinePillGreen.png</file> + <file>Images/timelinePillOrange.png</file> + <file>Images/timelinePillPurple.png</file> + <file>Images/timelinePillRed.png</file> + <file>Images/timelinePillYellow.png</file> + <file>Images/tipBalloonBottom.png</file> + <file>Images/tipBalloon.png</file> + <file>Images/tipIcon.png</file> + <file>Images/tipIconPressed.png</file> + <file>Images/toolbarItemSelected.png</file> + <file>Images/treeDownTriangleBlack.png</file> + <file>Images/treeDownTriangleWhite.png</file> + <file>Images/treeRightTriangleBlack.png</file> + <file>Images/treeRightTriangleWhite.png</file> + <file>Images/treeUpTriangleBlack.png</file> + <file>Images/treeUpTriangleWhite.png</file> + <file>Images/userInputIcon.png</file> + <file>Images/userInputPreviousIcon.png</file> + <file>Images/warningIcon.png</file> + <file>Images/warningMediumIcon.png</file> + <file>Images/warningsErrors.png</file> +</qresource> +</RCC> diff --git a/WebCore/inspector/front-end/inspector.css b/WebCore/inspector/front-end/inspector.css new file mode 100644 index 0000000..b8aa2c8 --- /dev/null +++ b/WebCore/inspector/front-end/inspector.css @@ -0,0 +1,2990 @@ +/* + * 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. + */ + +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.databases .toolbar-icon { + background-image: url(Images/databasesIcon.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.console-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.console-visible #main-status-bar * { + cursor: default; +} + +body.console-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; +} + +button.status-bar-item { + width: 32px; + background-image: url(Images/statusbarButtons.png); + background-position: 0 0; +} + +button.status-bar-item:active { + background-position: 32px 0; +} + +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 { + background-image: url(Images/dockButtons.png); +} + +body.attached #dock-status-bar-item:active { + background-position: 32px 0; +} + +body.detached #dock-status-bar-item { + background-position: 0 24px; +} + +body.detached #dock-status-bar-item.toggled-on:active { + background-position: 32px 24px; +} + +#console-status-bar-item { + background-image: url(Images/consoleButtons.png); +} + +#console-status-bar-item:active { + background-position: 32px 0; +} + +#console-status-bar-item.toggled-on { + background-position: 0 24px; +} + +#console-status-bar-item.toggled-on:active { + background-position: 32px 24px; +} + +#clear-console-status-bar-item { + background-image: url(Images/clearConsoleButtons.png); +} + +#clear-console-status-bar-item:active { + background-position: 32px 0; +} + +#error-warning-count { + position: absolute; + right: 16px; + top: 0; + cursor: pointer; + padding: 6px 2px; + font-size: 10px; + height: 19px; +} + +#error-warning-count:hover { + border-bottom: 1px solid rgb(96, 96, 96); +} + +#error-count::before { + content: url(Images/errorIcon.png); + width: 10px; + height: 10px; + vertical-align: -1px; + margin-right: 2px; +} + +#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; +} + +#console { + 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.console-visible #console { + display: block; +} + +#console-status-bar { + position: absolute; + bottom: 0; + left: 0; + right: 0; + background: none; +} + +#console-messages { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 23px; + font-size: 10px; + font-family: Monaco, Lucida Console, 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-message, .console-user-command { + position: relative; + border-bottom: 1px solid rgb(240, 240, 240); + padding: 1px 22px 1px 24px; + min-height: 16px; +} + +.console-message::before, .console-user-command::before, #console-prompt::before, .console-group-title-level::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 { + visibility: hidden; +} + +.console-group .console-group > .console-group-messages { + margin-left: 16px; +} + +.console-group-title-level { + font-weight: bold; +} + +.console-group-title-level::before { + background-image: url(Images/disclosureTriangleSmallDown.png); + top: 0.6em; + width: 11px; + height: 12px; +} + +.console-group.collapsed .console-group-title-level::before { + background-image: url(Images/disclosureTriangleSmallRight.png); +} + +.console-group.collapsed > .console-group-messages { + display: none; +} + +.console-error-level .console-message-text { + color: red; +} + +.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-message-url { + color: rgb(33%, 33%, 33%) !important; + cursor: pointer; + float: right; +} + +.console-message-url:hover { + color: rgb(15%, 15%, 15%); +} + +.console-message-url:hover::after { + opacity: 1; +} + +.console-group-messages .section { + margin: 0; +} + +.console-group-messages .section .header { + padding: 0 8px 0 0; + background-image: none; + border: none; + min-height: 16px; +} + +.console-group-messages .section .header::before { + position: absolute; + top: 1px; + left: 12px; + 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; +} + +.console-group-messages .outline-disclosure, .console-group-messages .outline-disclosure ol { + font-size: inherit; + line-height: 1em; +} + +.console-group-messages .outline-disclosure li { + padding-top: 2px; + padding-bottom: 2px; +} + +.console-group-messages .outline-disclosure li .selection { + z-index: 0; + margin-top: -1px; +} + +.auto-complete-text { + color: rgb(128, 128, 128); + -webkit-user-select: none; + -webkit-user-modify: read-only; +} + +.inspectible-node:hover { + background-color: rgba(56, 121, 217, 0.1); + -webkit-border-radius: 5px; + padding: 0 5px 1px; + margin: 0 -5px -1px; +} + +.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 .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; +} + +.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; +} + +.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; +} + +.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 .header::before { + position: absolute; + top: 4px; + left: 7px; + width: 8px; + height: 8px; + content: url(Images/treeRightTriangleWhite.png); +} + +.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 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; +} + +.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 { + display: none !important; +} + +.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; +} + +.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: 4px; + margin-bottom: -1px; + width: 1em; + height: 1em; + border: 1px solid rgb(180, 180, 180); +} + +.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 > .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); +} + +#database-views { + position: absolute; + top: 0; + right: 0; + left: 200px; + bottom: 0; +} + +.database-view { + display: none; + overflow: hidden; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; +} + +.database-view.visible { + display: block; +} + +.database-view.table { + overflow: hidden; +} + +.database-view.table .data-grid { + border: none; + height: 100%; +} + +.database-view.table .database-table-empty, .database-view.table .database-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; +} + +.database-view.table .database-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 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; +} + +.database-view.query { + font-size: 10px; + font-family: Monaco, Lucida Console, 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; +} + +.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; + margin-left: 50px; + margin-bottom: 6px; + line-height: 18px; + word-break: break-word; +} + +.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.scripts img { + content: url(Images/scriptsSilhouette.png); +} + +.panel-enabler-view.profiles img { + content: url(Images/profilesSilhouette.png); +} + +button.enable-toggle-status-bar-item { + background-image: url(Images/enableButtons.png); +} + +button.enable-toggle-status-bar-item:active { + background-position: 32px 0; +} + +button.enable-toggle-status-bar-item.toggled-on { + background-position: 0 24px; +} + +button.enable-toggle-status-bar-item.toggled-on:active { + background-position: 32px 24px; +} + +#scripts-pause-on-exceptions-status-bar-item { + background-image: url(Images/pauseOnExceptionButtons.png); +} + +#scripts-pause-on-exceptions-status-bar-item:active { + background-position: 32px 0; +} + +#scripts-pause-on-exceptions-status-bar-item.toggled-on { + background-position: 0 24px; +} + +#scripts-pause-on-exceptions-status-bar-item.toggled-on:active { + background-position: 32px 24px; +} + +#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 { + background-image: url(Images/largerResourcesButtons.png); +} + +#resources-larger-resources-status-bar-item:active { + background-position: 32px 0; +} + +#resources-larger-resources-status-bar-item.toggled-on { + background-position: 0 24px; +} + +#resources-larger-resources-status-bar-item.toggled-on:active { + background-position: 32px 24px; +} + +#resources-container { + position: absolute; + top: 0; + 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; +} + +#resources-graph-legend { + margin-top: -10px; + padding-left: 15px; +} + +.resources-graph-legend-item { + display: inline-block; + font-weight: bold; + margin-right: 15px; + vertical-align: top; +} + +.resources-graph-legend-item.total { + margin-left: 10px; +} + +.resources-graph-legend-label { + display: inline-block; + text-align: left; +} + +.resources-graph-legend-header { + font-size: 12px; +} + +.resources-graph-legend-value { + font-size: 10px; +} + +.resources-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-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: 0; + 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; +} + +.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.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; +} + +.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.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.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; +} + +#record-profile-status-bar-item { + background-image: url(Images/recordButtons.png); +} + +#record-profile-status-bar-item:active { + background-position: 32px 0; +} + +#record-profile-status-bar-item.toggled-on { + background-position: 0 24px; +} + +#record-profile-status-bar-item.toggled-on:active { + background-position: 32px 24px; +} + +#node-search-status-bar-item { + background-image: url(Images/nodeSearchButtons.png); +} + +#node-search-status-bar-item:active { + background-position: 32px 0; +} + +#node-search-status-bar-item.toggled-on { + background-position: 0 24px; +} + +#node-search-status-bar-item.toggled-on:active { + background-position: 32px 24px; +} + +.percent-time-status-bar-item { + background-image: url(Images/percentButtons.png) !important; +} + +.percent-time-status-bar-item:active { + background-position: 32px 0; +} + +.percent-time-status-bar-item.toggled-on { + background-position: 0 24px; +} + +.percent-time-status-bar-item.toggled-on:active { + background-position: 32px 24px; +} + +.focus-profile-node-status-bar-item { + background-image: url(Images/focusButtons.png) !important; +} + +.focus-profile-node-status-bar-item:active { + background-position: 32px 0; +} + +.exclude-profile-node-status-bar-item { + background-image: url(Images/excludeButtons.png) !important; +} + +.exclude-profile-node-status-bar-item:active { + background-position: 32px 0; +} + +.reset-profile-status-bar-item { + background-image: url(Images/reloadButtons.png) !important; +} + +.reset-profile-status-bar-item:active { + background-position: 32px 0; +} diff --git a/WebCore/inspector/front-end/inspector.html b/WebCore/inspector/front-end/inspector.html new file mode 100644 index 0000000..cb38886 --- /dev/null +++ b/WebCore/inspector/front-end/inspector.html @@ -0,0 +1,91 @@ +<!-- +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="TextPrompt.js"></script> + <script type="text/javascript" src="Placard.js"></script> + <script type="text/javascript" src="View.js"></script> + <script type="text/javascript" src="Console.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="DataGrid.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="BreakpointsSidebarPane.js"></script> + <script type="text/javascript" src="CallStackSidebarPane.js"></script> + <script type="text/javascript" src="ScopeChainSidebarPane.js"></script> + <script type="text/javascript" src="MetricsSidebarPane.js"></script> + <script type="text/javascript" src="PropertiesSidebarPane.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="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="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> +</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"></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"></button><button id="console-status-bar-item" class="status-bar-item"></button><div id="error-warning-count" class="hidden"></div></div></div> + </div> + <div id="console"> + <div id="console-messages"><div id="console-prompt"><br></div></div> + <div id="console-status-bar" class="status-bar"><div id="other-console-status-bar-items"><button id="clear-console-status-bar-item" class="status-bar-item"></button></div></div> + </div> +</body> +</html> diff --git a/WebCore/inspector/front-end/inspector.js b/WebCore/inspector/front-end/inspector.js new file mode 100644 index 0000000..0eab6d1 --- /dev/null +++ b/WebCore/inspector/front-end/inspector.js @@ -0,0 +1,1263 @@ +/* + * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2007 Matt Lilek (pewtermoose@gmail.com). + * + * 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 +} + +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 = document.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(); + } + } + } + }, + + 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 hoveredDOMNode() + { + return this._hoveredDOMNode; + }, + + set hoveredDOMNode(x) + { + if (objectsAreSame(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); + this.showingDOMNodeHighlight = true; + } else { + InspectorController.hideDOMNodeHighlight(); + this.showingDOMNodeHighlight = false; + } + } +} + +WebInspector.loaded = function() +{ + var platform = InspectorController.platform(); + document.body.addStyleClass("platform-" + platform); + + this.console = new WebInspector.Console(); + this.panels = { + elements: new WebInspector.ElementsPanel(), + resources: new WebInspector.ResourcesPanel(), + scripts: new WebInspector.ScriptsPanel(), + profiles: new WebInspector.ProfilesPanel(), + databases: new WebInspector.DatabasesPanel() + }; + + var toolbarElement = document.getElementById("toolbar"); + var previousToolbarItem = toolbarElement.children[0]; + + for (var panelName in this.panels) { + var panel = this.panels[panelName]; + var panelToolbarItem = panel.toolbarItem; + panelToolbarItem.addEventListener("click", this._toolbarItemClicked.bind(this)); + if (previousToolbarItem) + toolbarElement.insertBefore(panelToolbarItem, previousToolbarItem.nextSibling); + else + toolbarElement.insertBefore(panelToolbarItem, toolbarElement.firstChild); + previousToolbarItem = panelToolbarItem; + } + + this.currentPanel = this.panels.elements; + + 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.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); + + 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.console.show.bind(this.console), 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.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.console.visible = !this.console.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; + } + } +} + +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.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; + 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.console.show(); +} + +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.showDatabasesPanel = function() +{ + this.currentPanel = this.panels.databases; +} + +WebInspector.addResource = function(resource) +{ + this.resources.push(resource); + this.resourceURLMap[resource.url] = resource; + + if (resource.mainResource) { + this.mainResource = resource; + this.panels.elements.reset(); + } + + this.panels.resources.addResource(resource); +} + +WebInspector.removeResource = function(resource) +{ + resource.category.removeResource(resource); + delete this.resourceURLMap[resource.url]; + + this.resources.remove(resource, true); + + this.panels.resources.removeResource(resource); +} + +WebInspector.addDatabase = function(database) +{ + this.panels.databases.addDatabase(database); +} + +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() +{ + this.panels.scripts.debuggerPaused(); +} + +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.inspectedWindowCleared = function(inspectedWindow) +{ + this.panels.elements.inspectedWindowCleared(inspectedWindow); +} + +WebInspector.resourceURLChanged = function(resource, oldURL) +{ + delete this.resourceURLMap[oldURL]; + this.resourceURLMap[resource.url] = resource; +} + +WebInspector.addMessageToConsole = function(msg) +{ + 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(node) +{ + 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; + 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.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 = element.textContent; + var oldHandleKeyEvent = element.handleKeyEvent; + + element.addStyleClass("editing"); + + var oldTabIndex = element.tabIndex; + if (element.tabIndex < 0) + element.tabIndex = 0; + + function blurEventListener() { + editingCommitted.call(element); + } + + 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() { + this.innerText = oldText; + + cleanUpAfterEditing.call(this); + + cancelledCallback(this, context); + } + + function editingCommitted() { + cleanUpAfterEditing.call(this); + + committedCallback(this, this.textContent, oldText, context); + } + + 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; + } + } + + 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/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/WebCore/inspector/front-end/treeoutline.js b/WebCore/inspector/front-end/treeoutline.js new file mode 100644 index 0000000..579e7fb --- /dev/null +++ b/WebCore/inspector/front-end/treeoutline.js @@ -0,0 +1,846 @@ +/* + * 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.findTreeElement = function(representedObject, isAncestor, getParent, equal) +{ + if (!representedObject) + return null; + + if (!equal) + equal = function(a, b) { return a === b }; + + 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 (equal(elements[i].representedObject, representedObject)) + return elements[i]; + } + } + + if (!isAncestor || !(isAncestor instanceof Function) || !getParent || !(getParent instanceof Function)) + return null; + + // 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 (equal(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 (equal(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 (equal(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, equal); + if (item && item.onpopulate) + item.onpopulate(item); + } + + // Now that all the ancestors are populated, try to find the representedObject again. This time + // without the isAncestor and getParent functions to prevent an infinite recursion if it isn't found. + return this.findTreeElement(representedObject, null, null, equal); +} + +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/WebCore/inspector/front-end/utilities.js b/WebCore/inspector/front-end/utilities.js new file mode 100644 index 0000000..8f86504 --- /dev/null +++ b/WebCore/inspector/front-end/utilities.js @@ -0,0 +1,1098 @@ +/* + * 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.type = function(obj, win) +{ + if (obj === null) + return "null"; + + var type = typeof obj; + if (type !== "object" && type !== "function") + return type; + + win = win || window; + + 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.prototype.toString.call(obj).replace(/^\[object (.*)\]$/i, "$1"); + + switch (type1) { + case "object": + 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.sortedProperties = function(obj) +{ + var properties = []; + for (var prop in obj) + properties.push(prop); + properties.sort(); + return properties; +} + +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); +} + +Node.prototype.enclosingNodeOrSelfWithNodeNameInArray = function(nameArray) +{ + for (var node = this; node && !objectsAreSame(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 && !objectsAreSame(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.firstChildSkippingWhitespace = firstChildSkippingWhitespace; +Element.prototype.lastChildSkippingWhitespace = lastChildSkippingWhitespace; + +Node.prototype.isWhitespace = isNodeWhitespace; +Node.prototype.nodeTypeName = nodeTypeName; +Node.prototype.displayName = nodeDisplayName; +Node.prototype.contentPreview = nodeContentPreview; +Node.prototype.isAncestor = isAncestorNode; +Node.prototype.isDescendant = isDescendantNode; +Node.prototype.firstCommonAncestor = firstCommonNodeAncestor; +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 getStyleTextWithShorthands(style) +{ + var cssText = ""; + var foundProperties = {}; + for (var i = 0; i < style.length; ++i) { + var individualProperty = style[i]; + var shorthandProperty = style.getPropertyShorthand(individualProperty); + var propertyName = (shorthandProperty || individualProperty); + + if (propertyName in foundProperties) + continue; + + if (shorthandProperty) { + var value = getShorthandValue(style, shorthandProperty); + var priority = getShorthandPriority(style, shorthandProperty); + } else { + var value = style.getPropertyValue(individualProperty); + var priority = style.getPropertyPriority(individualProperty); + } + + foundProperties[propertyName] = true; + + cssText += propertyName + ": " + value; + if (priority) + cssText += " !" + priority; + cssText += "; "; + } + + return cssText; +} + +function getShorthandValue(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; +} + +function getShorthandPriority(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; +} + +function getLonghandProperties(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; +} + +function getUniqueStyleProperties(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; +} + +function isNodeWhitespace() +{ + if (!this || this.nodeType !== Node.TEXT_NODE) + return false; + if (!this.nodeValue.length) + return true; + return this.nodeValue.match(/^[\s\xA0]+$/); +} + +function nodeTypeName() +{ + if (!this) + return "(unknown)"; + + switch (this.nodeType) { + case Node.ELEMENT_NODE: return "Element"; + case Node.ATTRIBUTE_NODE: return "Attribute"; + case Node.TEXT_NODE: return "Text"; + case Node.CDATA_SECTION_NODE: return "Character Data"; + case Node.ENTITY_REFERENCE_NODE: return "Entity Reference"; + case Node.ENTITY_NODE: return "Entity"; + case Node.PROCESSING_INSTRUCTION_NODE: return "Processing Instruction"; + case Node.COMMENT_NODE: return "Comment"; + case Node.DOCUMENT_NODE: return "Document"; + case Node.DOCUMENT_TYPE_NODE: return "Document Type"; + case Node.DOCUMENT_FRAGMENT_NODE: return "Document Fragment"; + case Node.NOTATION_NODE: return "Notation"; + } + + return "(unknown)"; +} + +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 nodeContentPreview() +{ + if (!this || !this.hasChildNodes || !this.hasChildNodes()) + return ""; + + var limit = 0; + var preview = ""; + + // always skip whitespace here + var currentNode = traverseNextNode.call(this, true, this); + while (currentNode) { + if (currentNode.nodeType === Node.TEXT_NODE) + preview += currentNode.nodeValue.escapeHTML(); + else + preview += nodeDisplayName.call(currentNode).escapeHTML(); + + currentNode = traverseNextNode.call(currentNode, true, this); + + if (++limit > 4) { + preview += "…"; // ellipsis + break; + } + } + + return preview.collapseWhitespace(); +} + +function objectsAreSame(a, b) +{ + // FIXME: Make this more generic so is works with any wrapped object, not just nodes. + // This function is used to compare nodes that might be JSInspectedObjectWrappers, since + // JavaScript equality is not true for JSInspectedObjectWrappers of the same node wrapped + // with different global ExecStates, we use isSameNode to compare them. + if (a === b) + return true; + if (!a || !b) + return false; + if (a.isSameNode && b.isSameNode) + return a.isSameNode(b); + return false; +} + +function isAncestorNode(ancestor) +{ + if (!this || !ancestor) + return false; + + var currentNode = ancestor.parentNode; + while (currentNode) { + if (objectsAreSame(this, currentNode)) + return true; + currentNode = currentNode.parentNode; + } + + return false; +} + +function isDescendantNode(descendant) +{ + return isAncestorNode.call(descendant, this); +} + +function firstCommonNodeAncestor(node) +{ + if (!this || !node) + return; + + var node1 = this.parentNode; + var node2 = node.parentNode; + + if ((!node1 || !node2) || !objectsAreSame(node1, node2)) + return null; + + while (node1 && node2) { + if (!node1.parentNode || !node2.parentNode) + break; + if (!objectsAreSame(node1, node2)) + break; + + node1 = node1.parentNode; + node2 = node2.parentNode; + } + + return node1; +} + +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 && objectsAreSame(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 || !objectsAreSame(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 && objectsAreSame(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 parentNodeOrFrameElement(node) { + var parent = node.parentNode; + if (parent) + return parent; + + return getDocumentForNode(node).defaultView.frameElement; +} + +function isAncestorIncludingParentFrames(a, b) { + if (objectsAreSame(a, b)) + return false; + for (var node = b; node; node = getDocumentForNode(node).defaultView.frameElement) + if (objectsAreSame(a, node) || isAncestorNode.call(a, node)) + return true; + return false; +} + +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) +{ + if (!formatterFunction) + formatterFunction = String.sprintf; + + if (bytes < 1024) + return formatterFunction("%.0fB", bytes); + + var kilobytes = bytes / 1024; + if (kilobytes < 1024) + return formatterFunction("%.2fKB", kilobytes); + + var megabytes = kilobytes / 1024; + return formatterFunction("%.3fMB", 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); + } +} + +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 }; +} |