diff options
Diffstat (limited to 'WebCore/inspector')
33 files changed, 1124 insertions, 389 deletions
diff --git a/WebCore/inspector/CodeGeneratorInspector.pm b/WebCore/inspector/CodeGeneratorInspector.pm index c7907b4..80e243b 100644 --- a/WebCore/inspector/CodeGeneratorInspector.pm +++ b/WebCore/inspector/CodeGeneratorInspector.pm @@ -606,6 +606,7 @@ sub generateSource my @sourceContent = split("\r", $licenseTemplate); push(@sourceContent, "\n#include \"config.h\""); push(@sourceContent, "#include \"$className.h\""); + push(@sourceContent, "#include <wtf/text/CString.h>"); push(@sourceContent, ""); push(@sourceContent, "#if ENABLE(INSPECTOR)"); push(@sourceContent, ""); diff --git a/WebCore/inspector/Inspector.idl b/WebCore/inspector/Inspector.idl index 169b188..ae136b2 100644 --- a/WebCore/inspector/Inspector.idl +++ b/WebCore/inspector/Inspector.idl @@ -44,13 +44,9 @@ module core { [notify] void evaluateForTestInFrontend(out long testCallId, out String script); [notify] void disconnectFromBackend(); [notify] void inspectedURLChanged(out String url); - [notify] void monitoringXHRWasEnabled(); - [notify] void monitoringXHRWasDisabled(); [notify] void removeResource(out unsigned long identifier); [notify] void reset(); [notify] void resetProfilesPanel(); - [notify] void resourceTrackingWasEnabled(); - [notify] void resourceTrackingWasDisabled(); [notify] void searchingForNodeWasEnabled(); [notify] void searchingForNodeWasDisabled(); [notify] void setChildNodes(out long parentId, out Array nodes); @@ -102,6 +98,7 @@ module core { [handler=Controller] void populateScriptObjects(); [handler=Controller] void getSettings(out Object settings); + [handler=Controller] void getInspectorState(out Object state); [handler=Controller] void storeLastActivePanel(in String panelName); [handler=Controller] void saveApplicationSettings(in String settings); @@ -110,11 +107,9 @@ module core { [handler=Controller] void enableSearchingForNode(); [handler=Controller] void disableSearchingForNode(); - [handler=Controller] void enableMonitoringXHR(); - [handler=Controller] void disableMonitoringXHR(); + [handler=Controller] void setMonitoringXHREnabled(in boolean enable, out boolean newState); - [handler=Controller] void enableResourceTracking(in boolean always); - [handler=Controller] void disableResourceTracking(in boolean always); + [handler=Controller] void setResourceTrackingEnabled(in boolean enabled, in boolean always, out boolean newState); [handler=Controller] void getResourceContent(in unsigned long identifier, out String content); [handler=Controller] void reloadPage(); @@ -188,7 +183,7 @@ module core { [handler=DOM] void getInlineStyle(in long nodeId, out Value style); [handler=DOM] void getComputedStyle(in long nodeId, out Value style); [handler=DOM] void getStyleSheet(in long styleSheetId, out Object styleSheet); - [handler=DOM] void getRuleRanges(in long styleSheetId, out Value ranges); + [handler=DOM] void getStyleSourceData(in long styleSheetId, out Object styleSourceData); [handler=DOM] void applyStyleText(in long styleId, in String styleText, in String propertyName, out boolean success, out Value style, out Array changedProperties); [handler=DOM] void setStyleText(in long styleId, in String styleText, out boolean success); [handler=DOM] void setStyleProperty(in long styleId, in String name, in String value, out boolean success); diff --git a/WebCore/inspector/InspectorCSSStore.cpp b/WebCore/inspector/InspectorCSSStore.cpp index c2dc4f1..b019860 100644 --- a/WebCore/inspector/InspectorCSSStore.cpp +++ b/WebCore/inspector/InspectorCSSStore.cpp @@ -37,11 +37,18 @@ #include "CSSStyleDeclaration.h" #include "CSSStyleRule.h" #include "CSSStyleSheet.h" +#include "CachedResource.h" +#include "DocumentLoader.h" +#include "Element.h" +#include "Frame.h" #include "HTMLHeadElement.h" #include "InspectorController.h" #include "InspectorResource.h" +#include "Node.h" #include "PlatformString.h" +#include "SharedBuffer.h" #include "StyleSheetList.h" +#include "TextEncoding.h" namespace WebCore { @@ -67,6 +74,7 @@ void InspectorCSSStore::reset() m_styleSheetToOffsets.clear(); m_styleSheetToId.clear(); m_idToStyleSheet.clear(); + m_idToStyleSheetText.clear(); m_idToDisabledStyle.clear(); m_documentNodeToInspectorStyleSheetMap.clear(); @@ -104,48 +112,145 @@ CSSStyleSheet* InspectorCSSStore::inspectorStyleSheet(Document* ownerDocument, b return inspectorStyleSheet; } -HashMap<long, SourceRange> InspectorCSSStore::getRuleRanges(CSSStyleSheet* styleSheet) +String InspectorCSSStore::styleSheetText(long styleSheetId) { + IdToStyleSheetTextMap::iterator it = m_idToStyleSheetText.find(styleSheetId); + if (it != m_idToStyleSheetText.end()) + return it->second; + + CSSStyleSheet* styleSheet = styleSheetForId(styleSheetId); + if (!styleSheet) + return String(); + + String result; + bool success = false; + Node* ownerNode = styleSheet->ownerNode(); + if (ownerNode && ownerNode->nodeType() == Node::ELEMENT_NODE) { + Element* ownerElement = static_cast<Element*>(ownerNode); + if (ownerElement->tagName().lower() == "style") { + result = inlineStyleSheetText(styleSheet); + success = true; + } + } + if (!success) + success = resourceStyleSheetText(styleSheet, &result); + + if (success) + m_idToStyleSheetText.set(styleSheetId, result); + return result; +} + +bool InspectorCSSStore::resourceStyleSheetText(CSSStyleSheet* styleSheet, String* result) +{ + return m_inspectorController->resourceContentForURL(styleSheet->finalURL(), styleSheet->document(), result); +} + +String InspectorCSSStore::inlineStyleSheetText(CSSStyleSheet* styleSheet) +{ + Node* ownerNode = styleSheet->ownerNode(); + if (!ownerNode || ownerNode->nodeType() != Node::ELEMENT_NODE || m_styleSheetToId.find(styleSheet) == m_styleSheetToId.end()) + return String(); + Element* ownerElement = static_cast<Element*>(ownerNode); + if (ownerElement->tagName().lower() != "style") + return String(); + return ownerElement->innerText(); +} + + +// All ranges are: [start; end) (start - inclusive, end - exclusive). +bool InspectorCSSStore::getStyleSourceData(CSSStyleDeclaration* style, RefPtr<CSSStyleSourceData>* result) +{ + if (!style) + return false; + + Element* element = inlineStyleElement(style); + if (element) { + // Inline: style="...". + RefPtr<CSSStyleSourceData> styleSourceData = CSSStyleSourceData::create(); + bool success = getStyleAttributeRanges(element, &styleSourceData); + if (!success) + return false; + *result = styleSourceData; + return true; + } + + CSSStyleSheet* styleSheet = getParentStyleSheet(style); if (!styleSheet) - return HashMap<long, SourceRange>(); - RefPtr<CSSRuleList> originalRuleList = CSSRuleList::create(styleSheet, false); + return false; + + Vector<RefPtr<CSSStyleSourceData> >* rangesVector = 0; StyleSheetToOffsetsMap::iterator it = m_styleSheetToOffsets.find(styleSheet); - Vector<SourceRange>* offsetVector = 0; if (it == m_styleSheetToOffsets.end()) { - InspectorResource* resource = m_inspectorController->resourceForURL(styleSheet->finalURL().string()); - if (resource) { - offsetVector = new Vector<SourceRange>; + String text = styleSheetText(bindStyleSheet(styleSheet)); + if (!text.isEmpty()) { RefPtr<CSSStyleSheet> newStyleSheet = CSSStyleSheet::create(styleSheet->ownerNode()); CSSParser p; - CSSParser::StyleRuleRanges ruleRangeMap; - p.parseSheet(newStyleSheet.get(), resource->sourceString(), 0, &ruleRangeMap); - for (unsigned i = 0, length = newStyleSheet->length(); i < length; ++i) { - CSSStyleRule* rule = asCSSStyleRule(newStyleSheet->item(i)); - if (!rule) - continue; - HashMap<CSSStyleRule*, std::pair<unsigned, unsigned> >::iterator it = ruleRangeMap.find(rule); - if (it != ruleRangeMap.end()) - offsetVector->append(it->second); - } - m_styleSheetToOffsets.set(styleSheet, offsetVector); + StyleRuleRangeMap ruleRangeMap; + p.parseSheet(newStyleSheet.get(), text, 0, &ruleRangeMap); + rangesVector = new Vector<RefPtr<CSSStyleSourceData> >; + extractRanges(newStyleSheet.get(), ruleRangeMap, rangesVector); + m_styleSheetToOffsets.set(styleSheet, rangesVector); } } else - offsetVector = it->second; - if (!offsetVector) - return HashMap<long, SourceRange>(); + rangesVector = it->second; + if (!rangesVector) + return false; unsigned ruleIndex = 0; - HashMap<long, SourceRange> result; for (unsigned i = 0, length = styleSheet->length(); i < length; ++i) { - ASSERT(ruleIndex < offsetVector->size()); CSSStyleRule* rule = asCSSStyleRule(styleSheet->item(i)); if (!rule) continue; - // This maps the style id to the rule body range. - result.set(bindStyle(rule->style()), offsetVector->at(ruleIndex)); + if (rule->style() == style) { + ASSERT(ruleIndex < rangesVector->size()); + *result = rangesVector->at(ruleIndex); + return true; + } ruleIndex++; } - return result; + return false; +} + +void InspectorCSSStore::extractRanges(CSSStyleSheet* styleSheet, const StyleRuleRangeMap& ruleRangeMap, Vector<RefPtr<CSSStyleSourceData> >* rangesVector) +{ + for (unsigned i = 0, length = styleSheet->length(); i < length; ++i) { + CSSStyleRule* rule = asCSSStyleRule(styleSheet->item(i)); + if (!rule) + continue; + StyleRuleRangeMap::const_iterator it = ruleRangeMap.find(rule); + if (it != ruleRangeMap.end()) + rangesVector->append(it->second); + } +} + +bool InspectorCSSStore::getStyleAttributeRanges(Node* node, RefPtr<CSSStyleSourceData>* result) +{ + if (!node || !node->isStyledElement()) + return false; + + String styleText = static_cast<StyledElement*>(node)->getAttribute("style"); + if (styleText.isEmpty()) { + (*result)->styleBodyRange.start = 0; + (*result)->styleBodyRange.end = 0; + return true; + } + + RefPtr<CSSMutableStyleDeclaration> tempDeclaration = CSSMutableStyleDeclaration::create(); + CSSParser p; + p.parseDeclaration(tempDeclaration.get(), styleText, result->get()); + return true; +} + +CSSStyleSheet* InspectorCSSStore::getParentStyleSheet(CSSStyleDeclaration* style) +{ + if (!style) + return 0; + + StyleSheet* styleSheet = style->stylesheet(); + if (styleSheet && styleSheet->isCSSStyleSheet()) + return static_cast<CSSStyleSheet*>(styleSheet); + + return 0; } CSSStyleRule* InspectorCSSStore::asCSSStyleRule(StyleBase* styleBase) @@ -217,6 +322,17 @@ long InspectorCSSStore::bindRule(CSSStyleRule* rule) return id; } +// static +Element* InspectorCSSStore::inlineStyleElement(CSSStyleDeclaration* style) +{ + if (!style || !style->isMutableStyleDeclaration()) + return 0; + Node* node = static_cast<CSSMutableStyleDeclaration*>(style)->node(); + if (!node || !node->isStyledElement() || static_cast<StyledElement*>(node)->getInlineStyleDecl() != style) + return 0; + return static_cast<Element*>(node); +} + } // namespace WebCore #endif // ENABLE(INSPECTOR) diff --git a/WebCore/inspector/InspectorCSSStore.h b/WebCore/inspector/InspectorCSSStore.h index 82de622..9b329df 100644 --- a/WebCore/inspector/InspectorCSSStore.h +++ b/WebCore/inspector/InspectorCSSStore.h @@ -29,46 +29,54 @@ #ifndef InspectorCSSStore_h #define InspectorCSSStore_h +#include "CSSPropertySourceData.h" +#include "Cache.h" + #include <wtf/Forward.h> #include <wtf/HashMap.h> #include <wtf/RefPtr.h> -#include <wtf/text/StringHash.h> namespace WebCore { -class Document; -class InspectorController; class CSSMutableStyleDeclaration; class CSSStyleDeclaration; class CSSRuleList; class CSSStyleRule; class CSSStyleSheet; +class Document; +class Element; +class InspectorController; +class Node; class StyleBase; typedef std::pair<String, String> PropertyValueAndPriority; -typedef std::pair<unsigned, unsigned> SourceRange; typedef HashMap<String, PropertyValueAndPriority> DisabledStyleDeclaration; typedef HashMap<CSSStyleDeclaration*, long> StyleToIdMap; typedef HashMap<long, RefPtr<CSSStyleDeclaration> > IdToStyleMap; typedef HashMap<CSSStyleRule*, long> RuleToIdMap; typedef HashMap<long, RefPtr<CSSStyleRule> > IdToRuleMap; -typedef HashMap<CSSStyleSheet*, Vector<SourceRange>* > StyleSheetToOffsetsMap; +typedef HashMap<CSSStyleSheet*, Vector<RefPtr<CSSStyleSourceData> >* > StyleSheetToOffsetsMap; typedef HashMap<CSSStyleSheet*, long> StyleSheetToIdMap; typedef HashMap<long, RefPtr<CSSStyleSheet> > IdToStyleSheetMap; +typedef HashMap<long, String> IdToStyleSheetTextMap; typedef HashMap<long, DisabledStyleDeclaration> IdToDisabledStyleMap; typedef HashMap<RefPtr<Document>, RefPtr<CSSStyleSheet> > DocumentToStyleSheetMap; class InspectorCSSStore { public: + static CSSStyleSheet* getParentStyleSheet(CSSStyleDeclaration*); + static Element* inlineStyleElement(CSSStyleDeclaration*); + InspectorCSSStore(InspectorController* inspectorController); ~InspectorCSSStore(); void reset(); - HashMap<long, SourceRange> getRuleRanges(CSSStyleSheet*); + bool getStyleSourceData(CSSStyleDeclaration*, RefPtr<CSSStyleSourceData>* result); CSSStyleDeclaration* styleForId(long styleId); CSSStyleSheet* styleSheetForId(long styleSheetId); CSSStyleRule* ruleForId(long styleRuleId); DisabledStyleDeclaration* disabledStyleForId(long styleId, bool createIfAbsent); + String styleSheetText(long styleSheetId); CSSStyleSheet* inspectorStyleSheet(Document* ownerDocument, bool createIfAbsent); void removeDocument(Document*); @@ -78,6 +86,10 @@ public: private: static CSSStyleRule* asCSSStyleRule(StyleBase*); + String inlineStyleSheetText(CSSStyleSheet*); + bool resourceStyleSheetText(CSSStyleSheet*, String* result); + void extractRanges(CSSStyleSheet*, const StyleRuleRangeMap&, Vector<RefPtr<CSSStyleSourceData> >* rangesVector); + bool getStyleAttributeRanges(Node* parentNode, RefPtr<CSSStyleSourceData>* result); StyleToIdMap m_styleToId; IdToStyleMap m_idToStyle; @@ -85,8 +97,9 @@ private: IdToRuleMap m_idToRule; StyleSheetToOffsetsMap m_styleSheetToOffsets; StyleSheetToIdMap m_styleSheetToId; - IdToStyleSheetMap m_idToStyleSheet; IdToDisabledStyleMap m_idToDisabledStyle; + IdToStyleSheetMap m_idToStyleSheet; + IdToStyleSheetTextMap m_idToStyleSheetText; DocumentToStyleSheetMap m_documentNodeToInspectorStyleSheetMap; InspectorController* m_inspectorController; diff --git a/WebCore/inspector/InspectorClient.h b/WebCore/inspector/InspectorClient.h index acd8b28..bb71b13 100644 --- a/WebCore/inspector/InspectorClient.h +++ b/WebCore/inspector/InspectorClient.h @@ -54,10 +54,7 @@ public: // Navigation can cause some WebKit implementations to change the view / page / inspector controller instance. // However, there are some inspector controller states that should survive navigation (such as tracking resources // or recording timeline). Following callbacks allow embedders to track these states. - virtual void resourceTrackingWasEnabled() { }; - virtual void resourceTrackingWasDisabled() { }; - virtual void timelineProfilerWasStarted() { }; - virtual void timelineProfilerWasStopped() { }; + virtual void updateInspectorStateCookie(const String&) { }; }; } // namespace WebCore diff --git a/WebCore/inspector/InspectorController.cpp b/WebCore/inspector/InspectorController.cpp index 1e131f7..5f8f190 100644 --- a/WebCore/inspector/InspectorController.cpp +++ b/WebCore/inspector/InspectorController.cpp @@ -33,6 +33,7 @@ #if ENABLE(INSPECTOR) #include "CachedResource.h" +#include "CachedResourceLoader.h" #include "Chrome.h" #include "Console.h" #include "ConsoleMessage.h" @@ -71,6 +72,7 @@ #include "InspectorTimelineAgent.h" #include "InspectorValues.h" #include "InspectorWorkerResource.h" +#include "IntRect.h" #include "Page.h" #include "ProgressTracker.h" #include "Range.h" @@ -117,26 +119,28 @@ using namespace std; namespace WebCore { -static const char* const resourceTrackingEnabledSettingName = "resourceTrackingEnabled"; -static const char* const debuggerEnabledSettingName = "debuggerEnabled"; -static const char* const profilerEnabledSettingName = "profilerEnabled"; -static const char* const inspectorAttachedHeightName = "inspectorAttachedHeight"; +static const char* const frontendSettingsSettingName = "frontendSettings"; + +static const char* const debuggerAlwaysEnabledSettingName = "debuggerEnabled"; static const char* const lastActivePanel = "lastActivePanel"; +static const char* const monitoringXHRSettingName = "xhrMonitor"; +static const char* const resourceTrackingAlwaysEnabledSettingName = "resourceTrackingEnabled"; +static const char* const profilerAlwaysEnabledSettingName = "profilerEnabled"; + +static const char* const timelineProfilerEnabledStateName = "timelineProfilerEnabled"; +static const char* const resourceTrackingEnabledStateName = "resourceTrackingEnabled"; +static const char* const monitoringXHRStateName = "monitoringXHREnabled"; + +static const char* const inspectorAttachedHeightName = "inspectorAttachedHeight"; + const char* const InspectorController::ElementsPanel = "elements"; const char* const InspectorController::ConsolePanel = "console"; const char* const InspectorController::ScriptsPanel = "scripts"; const char* const InspectorController::ProfilesPanel = "profiles"; -static const char* const monitoringXHRSettingName = "xhrMonitor"; static int connectedFrontendCount = 0; -const String& InspectorController::frontendSettingsSettingName() -{ - DEFINE_STATIC_LOCAL(String, settingName, ("frontendSettings")); - return settingName; -} - const String& InspectorController::inspectorStartsAttachedSettingName() { DEFINE_STATIC_LOCAL(String, settingName, ("inspectorStartsAttached")); @@ -240,7 +244,7 @@ void InspectorController::setSetting(const String& key, const String& value) void InspectorController::saveApplicationSettings(const String& settings) { - setSetting(InspectorController::frontendSettingsSettingName(), settings); + setSetting(frontendSettingsSettingName, settings); } void InspectorController::saveSessionSettings(const String& settingsJSON) @@ -248,10 +252,46 @@ void InspectorController::saveSessionSettings(const String& settingsJSON) m_sessionSettings = InspectorValue::parseJSON(settingsJSON); } +void InspectorController::getInspectorState(RefPtr<InspectorObject>* state) +{ + (*state)->setBoolean(monitoringXHRStateName, m_monitoringXHR); + (*state)->setBoolean(resourceTrackingEnabledStateName, m_resourceTrackingEnabled); +} + +void InspectorController::updateInspectorStateCookie() +{ + RefPtr<InspectorObject> state = InspectorObject::create(); + state->setBoolean(monitoringXHRStateName, m_monitoringXHR); + state->setBoolean(resourceTrackingEnabledStateName, m_resourceTrackingEnabled); + state->setBoolean(timelineProfilerEnabledStateName, m_timelineAgent); + m_client->updateInspectorStateCookie(state->toJSONString()); +} + +void InspectorController::restoreInspectorStateFromCookie(const String& inspectorStateString) +{ + RefPtr<InspectorValue> inspectorStateValue = InspectorValue::parseJSON(inspectorStateString); + if (!inspectorStateValue) + return; + + RefPtr<InspectorObject> inspectorState = inspectorStateValue->asObject(); + if (!inspectorState) + return; + + inspectorState->getBoolean(monitoringXHRStateName, &m_monitoringXHR); + inspectorState->getBoolean(resourceTrackingEnabledStateName, &m_resourceTrackingEnabled); + + bool timelineProfilerEnabled = false; + inspectorState->getBoolean(timelineProfilerEnabledStateName, &timelineProfilerEnabled); + if (timelineProfilerEnabled) + startTimelineProfiler(); + else + stopTimelineProfiler(); +} + void InspectorController::getSettings(RefPtr<InspectorObject>* settings) { *settings = InspectorObject::create(); - (*settings)->setString("application", setting(frontendSettingsSettingName())); + (*settings)->setString("application", setting(frontendSettingsSettingName)); (*settings)->setString("session", m_sessionSettings->toJSONString()); } @@ -460,18 +500,14 @@ void InspectorController::setSearchingForNode(bool enabled) } } -void InspectorController::setMonitoringXHR(bool enabled) +void InspectorController::setMonitoringXHREnabled(bool enabled, bool* newState) { + *newState = enabled; if (m_monitoringXHR == enabled) return; m_monitoringXHR = enabled; setSetting(monitoringXHRSettingName, enabled ? "true" : "false"); - if (m_frontend) { - if (enabled) - m_frontend->monitoringXHRWasEnabled(); - else - m_frontend->monitoringXHRWasDisabled(); - } + updateInspectorStateCookie(); } void InspectorController::connectFrontend() @@ -500,6 +536,13 @@ void InspectorController::connectFrontend() connectedFrontendCount++; } +void InspectorController::reuseFrontend() +{ + connectFrontend(); + restoreDebugger(); + restoreProfiler(); +} + void InspectorController::show() { if (!enabled()) @@ -604,18 +647,12 @@ void InspectorController::populateScriptObjects() if (m_showAfterVisible == lastActivePanel) m_showAfterVisible = setting(lastActivePanel); - if (m_nodeToFocus) - focusNode(); - showPanel(m_showAfterVisible); - if (m_resourceTrackingEnabled) - m_frontend->resourceTrackingWasEnabled(); + showPanel(m_showAfterVisible); if (m_searchingForNode) m_frontend->searchingForNodeWasEnabled(); - if (m_monitoringXHR) - m_frontend->monitoringXHRWasEnabled(); #if ENABLE(JAVASCRIPT_DEBUGGER) if (m_profilerAgent->enabled()) @@ -628,6 +665,9 @@ void InspectorController::populateScriptObjects() m_domAgent->setDocument(m_inspectedPage->mainFrame()->document()); + if (m_nodeToFocus) + focusNode(); + if (m_expiredConsoleMessageCount) m_frontend->updateConsoleMessageExpiredCount(m_expiredConsoleMessageCount); unsigned messageCount = m_consoleMessages.size(); @@ -661,17 +701,31 @@ void InspectorController::populateScriptObjects() m_frontend->evaluateForTestInFrontend((*it).first, (*it).second); m_pendingEvaluateTestCommands.clear(); + restoreDebugger(); + restoreProfiler(); +} + +void InspectorController::restoreDebugger() +{ + ASSERT(m_frontend); #if ENABLE(JAVASCRIPT_DEBUGGER) if (InspectorDebuggerAgent::isDebuggerAlwaysEnabled()) enableDebuggerFromFrontend(false); else { - String debuggerEnabled = setting(debuggerEnabledSettingName); + String debuggerEnabled = setting(debuggerAlwaysEnabledSettingName); if (debuggerEnabled == "true" || m_attachDebuggerWhenShown) enableDebugger(); } +#endif +} + +void InspectorController::restoreProfiler() +{ + ASSERT(m_frontend); +#if ENABLE(JAVASCRIPT_DEBUGGER) m_profilerAgent->setFrontend(m_frontend.get()); if (!ScriptProfiler::isProfilerAlwaysEnabled()) { - String profilerEnabledSetting = setting(profilerEnabledSettingName); + String profilerEnabledSetting = setting(profilerAlwaysEnabledSettingName); if (profilerEnabledSetting == "true") enableProfiler(); } @@ -798,6 +852,8 @@ void InspectorController::addResource(InspectorResource* resource) m_knownResources.add(resource->requestURL()); Frame* frame = resource->frame(); + if (!frame) + return; ResourcesMap* resourceMap = m_frameResources.get(frame); if (resourceMap) resourceMap->set(resource->identifier(), resource); @@ -816,6 +872,8 @@ void InspectorController::removeResource(InspectorResource* resource) m_knownResources.remove(requestURL); Frame* frame = resource->frame(); + if (!frame) + return; ResourcesMap* resourceMap = m_frameResources.get(frame); if (!resourceMap) { ASSERT_NOT_REACHED(); @@ -957,7 +1015,7 @@ void InspectorController::willSendRequest(unsigned long identifier, ResourceRequ // Redirect may have empty URL and we'd like to not crash with invalid HashMap entry. // See http/tests/misc/will-send-request-returns-null-on-redirect.html if (!request.url().isEmpty()) { - resource->endTiming(); + resource->endTiming(0); resource->updateResponse(redirectResponse); // We always store last redirect by the original id key. Rest of the redirects are stored within the last one. @@ -980,6 +1038,15 @@ void InspectorController::willSendRequest(unsigned long identifier, ResourceRequ resource->updateScriptObject(m_frontend.get()); } +void InspectorController::markResourceAsCached(unsigned long identifier) +{ + if (!enabled()) + return; + + if (RefPtr<InspectorResource> resource = getTrackedResource(identifier)) + resource->markAsCached(); +} + void InspectorController::didReceiveResponse(unsigned long identifier, const ResourceResponse& response) { if (!enabled()) @@ -1014,7 +1081,7 @@ void InspectorController::didReceiveContentLength(unsigned long identifier, int resource->updateScriptObject(m_frontend.get()); } -void InspectorController::didFinishLoading(unsigned long identifier) +void InspectorController::didFinishLoading(unsigned long identifier, double finishTime) { if (!enabled()) return; @@ -1026,7 +1093,7 @@ void InspectorController::didFinishLoading(unsigned long identifier) if (!resource) return; - resource->endTiming(); + resource->endTiming(finishTime); // No need to mute this event for main resource since it happens after did commit load. if (m_frontend) @@ -1051,7 +1118,7 @@ void InspectorController::didFailLoading(unsigned long identifier, const Resourc return; resource->markFailed(); - resource->endTiming(); + resource->endTiming(0); // No need to mute this event for main resource since it happens after did commit load. if (m_frontend) @@ -1094,40 +1161,33 @@ void InspectorController::scriptImported(unsigned long identifier, const String& resource->updateScriptObject(m_frontend.get()); } -void InspectorController::enableResourceTracking(bool always, bool reload) +void InspectorController::setResourceTrackingEnabled(bool enable) { if (!enabled()) return; - if (always) - setSetting(resourceTrackingEnabledSettingName, "true"); - - if (m_resourceTrackingEnabled) - return; - ASSERT(m_inspectedPage); - m_resourceTrackingEnabled = true; - if (m_frontend) - m_frontend->resourceTrackingWasEnabled(); - m_client->resourceTrackingWasEnabled(); - - if (reload) - m_inspectedPage->mainFrame()->redirectScheduler()->scheduleRefresh(true); + m_resourceTrackingEnabled = enable; + updateInspectorStateCookie(); } -void InspectorController::disableResourceTracking(bool always) +void InspectorController::setResourceTrackingEnabled(bool enable, bool always, bool* newState) { - if (!enabled()) - return; + *newState = enable; if (always) - setSetting(resourceTrackingEnabledSettingName, "false"); + setSetting(resourceTrackingAlwaysEnabledSettingName, enable ? "true" : "false"); + + if (m_resourceTrackingEnabled == enable) + return; ASSERT(m_inspectedPage); - m_resourceTrackingEnabled = false; - if (m_frontend) - m_frontend->resourceTrackingWasDisabled(); - m_client->resourceTrackingWasDisabled(); + m_resourceTrackingEnabled = enable; + + if (enable) + m_inspectedPage->mainFrame()->redirectScheduler()->scheduleRefresh(true); + + updateInspectorStateCookie(); } void InspectorController::ensureSettingsLoaded() @@ -1136,14 +1196,15 @@ void InspectorController::ensureSettingsLoaded() return; m_settingsLoaded = true; - String resourceTracking = setting(resourceTrackingEnabledSettingName); - if (resourceTracking == "true") + String resourceTrackingAlwaysEnabled = setting(resourceTrackingAlwaysEnabledSettingName); + if (resourceTrackingAlwaysEnabled == "true") m_resourceTrackingEnabled = true; - m_client->resourceTrackingWasEnabled(); - String monitoringXHR = setting(monitoringXHRSettingName); - if (monitoringXHR == "true") + String monitoringXHRAlwaysEnabled = setting(monitoringXHRSettingName); + if (monitoringXHRAlwaysEnabled == "true") m_monitoringXHR = true; + + updateInspectorStateCookie(); } void InspectorController::startTimelineProfiler() @@ -1157,7 +1218,8 @@ void InspectorController::startTimelineProfiler() m_timelineAgent = new InspectorTimelineAgent(m_frontend.get()); if (m_frontend) m_frontend->timelineProfilerWasStarted(); - m_client->timelineProfilerWasStarted(); + + updateInspectorStateCookie(); } void InspectorController::stopTimelineProfiler() @@ -1171,7 +1233,8 @@ void InspectorController::stopTimelineProfiler() m_timelineAgent = 0; if (m_frontend) m_frontend->timelineProfilerWasStopped(); - m_client->timelineProfilerWasStopped(); + + updateInspectorStateCookie(); } #if ENABLE(WORKERS) @@ -1442,6 +1505,56 @@ InspectorDOMStorageResource* InspectorController::getDOMStorageResourceForId(lon } #endif +#if ENABLE(WEB_SOCKETS) +void InspectorController::didCreateWebSocket(unsigned long identifier, const KURL& requestURL, const KURL& documentURL) +{ + if (!enabled()) + return; + ASSERT(m_inspectedPage); + + RefPtr<InspectorResource> resource = InspectorResource::createWebSocket(identifier, requestURL, documentURL); + addResource(resource.get()); + + if (m_frontend) + resource->updateScriptObject(m_frontend.get()); +} + +void InspectorController::willSendWebSocketHandshakeRequest(unsigned long identifier, const WebSocketHandshakeRequest& request) +{ + RefPtr<InspectorResource> resource = getTrackedResource(identifier); + if (!resource) + return; + resource->startTiming(); + resource->updateWebSocketRequest(request); + if (m_frontend) + resource->updateScriptObject(m_frontend.get()); +} + +void InspectorController::didReceiveWebSocketHandshakeResponse(unsigned long identifier, const WebSocketHandshakeResponse& response) +{ + RefPtr<InspectorResource> resource = getTrackedResource(identifier); + if (!resource) + return; + // Calling resource->markResponseReceivedTime() here makes resources bar chart confusing, because + // we cannot apply the "latency + download" model of regular resources to WebSocket connections. + // FIXME: Design a new UI for bar charts of WebSocket resources, and record timing here. + resource->updateWebSocketResponse(response); + if (m_frontend) + resource->updateScriptObject(m_frontend.get()); +} + +void InspectorController::didCloseWebSocket(unsigned long identifier) +{ + RefPtr<InspectorResource> resource = getTrackedResource(identifier); + if (!resource) + return; + + resource->endTiming(0); + if (m_frontend) + resource->updateScriptObject(m_frontend.get()); +} +#endif // ENABLE(WEB_SOCKETS) + #if ENABLE(JAVASCRIPT_DEBUGGER) void InspectorController::addProfile(PassRefPtr<ScriptProfile> prpProfile, unsigned lineNumber, const String& sourceURL) { @@ -1493,14 +1606,14 @@ bool InspectorController::profilerEnabled() const void InspectorController::enableProfiler(bool always, bool skipRecompile) { if (always) - setSetting(profilerEnabledSettingName, "true"); + setSetting(profilerAlwaysEnabledSettingName, "true"); m_profilerAgent->enable(skipRecompile); } void InspectorController::disableProfiler(bool always) { if (always) - setSetting(profilerEnabledSettingName, "false"); + setSetting(profilerAlwaysEnabledSettingName, "false"); m_profilerAgent->disable(); } #endif @@ -1510,7 +1623,7 @@ void InspectorController::enableDebuggerFromFrontend(bool always) { ASSERT(!debuggerEnabled()); if (always) - setSetting(debuggerEnabledSettingName, "true"); + setSetting(debuggerAlwaysEnabledSettingName, "true"); ASSERT(m_inspectedPage); @@ -1541,7 +1654,7 @@ void InspectorController::disableDebugger(bool always) return; if (always) - setSetting(debuggerEnabledSettingName, "false"); + setSetting(debuggerAlwaysEnabledSettingName, "false"); ASSERT(m_inspectedPage); @@ -1558,7 +1671,6 @@ void InspectorController::resume() if (m_debuggerAgent) m_debuggerAgent->resume(); } -// JavaScriptDebugListener functions #endif @@ -1585,6 +1697,8 @@ void InspectorController::didEvaluateForTestInFrontend(long callId, const String String InspectorController::breakpointsSettingKey() { DEFINE_STATIC_LOCAL(String, keyPrefix, ("breakpoints:")); + if (!m_mainResource) + return ""; return keyPrefix + InspectorDebuggerAgent::md5Base16(m_mainResource->requestURL()); } @@ -1744,6 +1858,84 @@ void InspectorController::drawNodeHighlight(GraphicsContext& context) const drawHighlightForLineBoxesOrSVGRenderer(context, lineBoxQuads); } + + // Draw node title if necessary. + + if (!m_highlightedNode->isElementNode()) + return; + + WebCore::Settings* settings = containingFrame->settings(); + drawElementTitle(context, boundingBox, overlayRect, settings); +} + +void InspectorController::drawElementTitle(GraphicsContext& context, const IntRect& boundingBox, const FloatRect& overlayRect, WebCore::Settings* settings) const +{ + static const int rectInflatePx = 4; + static const int fontHeightPx = 12; + static const int borderWidthPx = 1; + static const Color tooltipBackgroundColor(255, 255, 194, 255); + static const Color tooltipBorderColor(Color::black); + static const Color tooltipFontColor(Color::black); + + Element* element = static_cast<Element*>(m_highlightedNode.get()); + bool isXHTML = element->document()->isXHTMLDocument(); + String nodeTitle = isXHTML ? element->nodeName() : element->nodeName().lower(); + const AtomicString& idValue = element->getIdAttribute(); + if (!idValue.isNull() && !idValue.isEmpty()) { + nodeTitle += "#"; + nodeTitle += idValue; + } + if (element->hasClass() && element->isStyledElement()) { + const SpaceSplitString& classNamesString = static_cast<StyledElement*>(element)->classNames(); + size_t classNameCount = classNamesString.size(); + if (classNameCount) { + HashSet<AtomicString> usedClassNames; + for (size_t i = 0; i < classNameCount; ++i) { + const AtomicString& className = classNamesString[i]; + if (usedClassNames.contains(className)) + continue; + usedClassNames.add(className); + nodeTitle += "."; + nodeTitle += className; + } + } + } + nodeTitle += " ["; + nodeTitle += String::number(boundingBox.width()); + nodeTitle.append(static_cast<UChar>(0x00D7)); // × + nodeTitle += String::number(boundingBox.height()); + nodeTitle += "]"; + + FontDescription desc; + FontFamily family; + family.setFamily(settings->fixedFontFamily()); + desc.setFamily(family); + desc.setComputedSize(fontHeightPx); + Font font = Font(desc, 0, 0); + font.update(0); + + TextRun nodeTitleRun(nodeTitle); + IntPoint titleBasePoint = boundingBox.bottomLeft(); + titleBasePoint.move(rectInflatePx, rectInflatePx); + IntRect titleRect = enclosingIntRect(font.selectionRectForText(nodeTitleRun, titleBasePoint, fontHeightPx)); + titleRect.inflate(rectInflatePx); + + // The initial offsets needed to compensate for a 1px-thick border stroke (which is not a part of the rectangle). + int dx = -borderWidthPx; + int dy = borderWidthPx; + if (titleRect.right() > overlayRect.right()) + dx += overlayRect.right() - titleRect.right(); + if (titleRect.x() + dx < overlayRect.x()) + dx = overlayRect.x() - titleRect.x(); + if (titleRect.bottom() > overlayRect.bottom()) + dy += overlayRect.bottom() - titleRect.bottom() - borderWidthPx; + titleRect.move(dx, dy); + context.setStrokeColor(tooltipBorderColor, DeviceColorSpace); + context.setStrokeThickness(borderWidthPx); + context.setFillColor(tooltipBackgroundColor, DeviceColorSpace); + context.drawRect(titleRect); + context.setFillColor(tooltipFontColor, DeviceColorSpace); + context.drawText(font, nodeTitleRun, IntPoint(titleRect.x() + rectInflatePx, titleRect.y() + font.height())); } void InspectorController::openInInspectedWindow(const String& url) @@ -1840,6 +2032,49 @@ void InspectorController::getResourceContent(unsigned long identifier, String* c *content = resource ? resource->sourceString() : String(); } +bool InspectorController::resourceContentForURL(const KURL& url, Document* frameDocument, String* result) +{ + if (!frameDocument) + return false; + + String textEncodingName; + RefPtr<SharedBuffer> buffer; + if (equalIgnoringFragmentIdentifier(url, frameDocument->frame()->loader()->documentLoader()->requestURL())) { + textEncodingName = frameDocument->inputEncoding(); + buffer = frameDocument->frame()->loader()->provisionalDocumentLoader()->mainResourceData(); + } else { + const String& urlString = url.string(); + CachedResource* cachedResource = frameDocument->cachedResourceLoader()->cachedResource(urlString); + if (!cachedResource) + cachedResource = cache()->resourceForURL(urlString); + + ASSERT(cachedResource); // FIXME(apavlov): This might be too aggressive. + + bool isUnpurgeable = true; + if (cachedResource->isPurgeable()) { + // If the resource is purgeable then make it unpurgeable to get + // its data. This might fail, in which case we return an + // empty String. + if (!cachedResource->makePurgeable(false)) + isUnpurgeable = false; + } + if (isUnpurgeable) { + textEncodingName = cachedResource->encoding(); + buffer = cachedResource->data(); + } + } + + if (buffer) { + TextEncoding encoding(textEncodingName); + if (!encoding.isValid()) + encoding = WindowsLatin1Encoding(); + *result = encoding.decode(buffer->data(), buffer->size()); + return true; + } + + return false; +} + void InspectorController::reloadPage() { m_inspectedPage->mainFrame()->redirectScheduler()->scheduleRefresh(true); diff --git a/WebCore/inspector/InspectorController.h b/WebCore/inspector/InspectorController.h index 40099c6..6752044 100644 --- a/WebCore/inspector/InspectorController.h +++ b/WebCore/inspector/InspectorController.h @@ -50,6 +50,7 @@ class Database; class Document; class DocumentLoader; class Element; +class FloatRect; class GraphicsContext; class HitTestResult; class InjectedScript; @@ -72,6 +73,7 @@ class InspectorStorageAgent; class InspectorTimelineAgent; class InspectorValue; class InspectorWorkerResource; +class IntRect; class KURL; class Node; class Page; @@ -89,6 +91,11 @@ class StorageArea; class InspectorApplicationCacheAgent; #endif +#if ENABLE(WEB_SOCKETS) +class WebSocketHandshakeRequest; +class WebSocketHandshakeResponse; +#endif + class InspectorController : public Noncopyable { public: typedef HashMap<unsigned long, RefPtr<InspectorResource> > ResourcesMap; @@ -122,6 +129,8 @@ public: void saveSessionSettings(const String&); void getSettings(RefPtr<InspectorObject>*); + void restoreInspectorStateFromCookie(const String& inspectorState); + void inspect(Node*); void highlight(Node*); void hideHighlight(); @@ -136,6 +145,7 @@ public: // transport via InspectorClient. After migration, webInspector parameter should // be removed. void connectFrontend(); + void reuseFrontend(); void disconnectFrontend(); void addMessageToConsole(MessageSource, MessageType, MessageLevel, ScriptCallStack*, const String& message); @@ -158,15 +168,16 @@ public: void identifierForInitialRequest(unsigned long identifier, DocumentLoader*, const ResourceRequest&); void willSendRequest(unsigned long identifier, ResourceRequest&, const ResourceResponse& redirectResponse); + void markResourceAsCached(unsigned long identifier); void didReceiveResponse(unsigned long identifier, const ResourceResponse&); void didReceiveContentLength(unsigned long identifier, int lengthReceived); - void didFinishLoading(unsigned long identifier); + void didFinishLoading(unsigned long identifier, double finishTime); void didFailLoading(unsigned long identifier, const ResourceError&); void resourceRetrievedByXMLHttpRequest(unsigned long identifier, const ScriptString& sourceString, const String& url, const String& sendURL, unsigned sendLineNumber); void scriptImported(unsigned long identifier, const String& sourceString); - void enableResourceTracking(bool always = false, bool reload = true); - void disableResourceTracking(bool always = false); + void setResourceTrackingEnabled(bool enabled); + void setResourceTrackingEnabled(bool enabled, bool always, bool* newState); bool resourceTrackingEnabled() const { return m_resourceTrackingEnabled; } void ensureSettingsLoaded(); @@ -209,13 +220,21 @@ public: void setDOMStorageItem(long storageId, const String& key, const String& value, bool* success); void removeDOMStorageItem(long storageId, const String& key, bool* success); #endif +#if ENABLE(WEB_SOCKETS) + void didCreateWebSocket(unsigned long identifier, const KURL& requestURL, const KURL& documentURL); + void willSendWebSocketHandshakeRequest(unsigned long identifier, const WebSocketHandshakeRequest&); + void didReceiveWebSocketHandshakeResponse(unsigned long identifier, const WebSocketHandshakeResponse&); + void didCloseWebSocket(unsigned long identifier); +#endif const ResourcesMap& resources() const { return m_resources; } InspectorResource* resourceForURL(const String& url); + bool resourceContentForURL(const KURL& url, Document* loaderDocument, String* result); bool hasFrontend() const { return m_frontend; } void drawNodeHighlight(GraphicsContext&) const; void openInInspectedWindow(const String& url); + void drawElementTitle(GraphicsContext&, const IntRect& boundingBox, const FloatRect& overlayRect, WebCore::Settings*) const; void count(const String& title, unsigned lineNumber, const String& sourceID); @@ -258,13 +277,16 @@ public: static const String& inspectorStartsAttachedSettingName(); private: - static const String& frontendSettingsSettingName(); + void updateInspectorStateCookie(); + void getInspectorState(RefPtr<InspectorObject>* state); friend class InspectorBackend; friend class InspectorBackendDispatcher; friend class InjectedScriptHost; void populateScriptObjects(); + void restoreDebugger(); + void restoreProfiler(); void unbindAllResources(); // Following are used from InspectorBackend and internally. @@ -272,9 +294,7 @@ private: void enableSearchingForNode() { setSearchingForNode(true); } void disableSearchingForNode() { setSearchingForNode(false); } - void setMonitoringXHR(bool enabled); - void enableMonitoringXHR() { setMonitoringXHR(true); } - void disableMonitoringXHR() { setMonitoringXHR(false); } + void setMonitoringXHREnabled(bool enabled, bool* newState); void storeLastActivePanel(const String& panelName); InspectorDOMAgent* domAgent() { return m_domAgent.get(); } void releaseFrontendLifetimeAgents(); diff --git a/WebCore/inspector/InspectorDOMAgent.cpp b/WebCore/inspector/InspectorDOMAgent.cpp index 23c22aa..f1df5b0 100644 --- a/WebCore/inspector/InspectorDOMAgent.cpp +++ b/WebCore/inspector/InspectorDOMAgent.cpp @@ -37,6 +37,7 @@ #include "CSSComputedStyleDeclaration.h" #include "CSSMutableStyleDeclaration.h" #include "CSSPropertyNames.h" +#include "CSSPropertySourceData.h" #include "CSSRule.h" #include "CSSRuleList.h" #include "CSSStyleRule.h" @@ -707,7 +708,7 @@ void InspectorDOMAgent::performSearch(const String& whitespaceTrimmedQuery, bool m_pendingMatchJobs.append(new MatchPlainTextJob(document, escapedQuery)); continue; } - + m_pendingMatchJobs.append(new MatchExactIdJob(document, whitespaceTrimmedQuery)); m_pendingMatchJobs.append(new MatchExactClassNamesJob(document, whitespaceTrimmedQuery)); m_pendingMatchJobs.append(new MatchExactTagNamesJob(document, tagNameQuery)); @@ -776,38 +777,70 @@ void InspectorDOMAgent::removeDOMBreakpoint(long nodeId, long type) bool InspectorDOMAgent::shouldBreakOnNodeInsertion(Node*, Node* parent, PassRefPtr<InspectorValue>* details) { - if (!hasBreakpoint(parent, SubtreeModified)) - return false; - RefPtr<InspectorObject> detailsObject = InspectorObject::create(); - detailsObject->setObject("breakpoint", createBreakpoint(parent, SubtreeModified)); - *details = detailsObject; - return true; + if (hasBreakpoint(parent, SubtreeModified)) { + *details = descriptionForDOMEvent(parent, SubtreeModified, true); + return true; + } + return false; } bool InspectorDOMAgent::shouldBreakOnNodeRemoval(Node* node, PassRefPtr<InspectorValue>* details) { - bool hasNodeRemovedBreakpoint = hasBreakpoint(node, NodeRemoved); - bool hasAnyBreakpoint = hasNodeRemovedBreakpoint || hasBreakpoint(innerParentNode(node), SubtreeModified); - if (!hasAnyBreakpoint) - return false; - - RefPtr<InspectorObject> detailsObject = InspectorObject::create(); - if (hasNodeRemovedBreakpoint) - detailsObject->setObject("breakpoint", createBreakpoint(node, NodeRemoved)); - else - detailsObject->setObject("breakpoint", createBreakpoint(innerParentNode(node), SubtreeModified)); - *details = detailsObject; - return true; + if (hasBreakpoint(node, NodeRemoved)) { + *details = descriptionForDOMEvent(node, NodeRemoved, false); + return true; + } + if (hasBreakpoint(innerParentNode(node), SubtreeModified)) { + *details = descriptionForDOMEvent(node, SubtreeModified, false); + return true; + } + return false; } bool InspectorDOMAgent::shouldBreakOnAttributeModification(Element* element, PassRefPtr<InspectorValue>* details) { - if (!hasBreakpoint(element, AttributeModified)) - return false; - RefPtr<InspectorObject> detailsObject = InspectorObject::create(); - detailsObject->setObject("breakpoint", createBreakpoint(element, AttributeModified)); - *details = detailsObject; - return true; + if (hasBreakpoint(element, AttributeModified)) { + *details = descriptionForDOMEvent(element, AttributeModified, false); + return true; + } + return false; +} + +PassRefPtr<InspectorValue> InspectorDOMAgent::descriptionForDOMEvent(Node* target, long breakpointType, bool insertion) +{ + ASSERT(hasBreakpoint(target, breakpointType)); + + RefPtr<InspectorObject> description = InspectorObject::create(); + Node* breakpointOwner = target; + if ((1 << breakpointType) & inheritableDOMBreakpointTypesMask) { + // For inheritable breakpoint types, target node isn't always the same as the node that owns a breakpoint. + // Target node may be unknown to frontend, so we need to push it first. + long targetNodeId = pushNodePathToFrontend(target); + ASSERT(targetNodeId); + description->setNumber("targetNodeId", targetNodeId); + + // Find breakpoint owner node. + if (!insertion) + breakpointOwner = innerParentNode(target); + ASSERT(breakpointOwner); + while (!(m_breakpoints.get(breakpointOwner) & (1 << breakpointType))) { + breakpointOwner = innerParentNode(breakpointOwner); + ASSERT(breakpointOwner); + } + + if (breakpointType == SubtreeModified) + description->setBoolean("insertion", insertion); + } + + long breakpointOwnerNodeId = m_documentNodeToIdMap.get(breakpointOwner); + ASSERT(breakpointOwnerNodeId); + + RefPtr<InspectorObject> breakpoint = InspectorObject::create(); + breakpoint->setNumber("nodeId", breakpointOwnerNodeId); + breakpoint->setNumber("type", breakpointType); + description->setObject("breakpoint", breakpoint); + + return description; } String InspectorDOMAgent::documentURLString(Document* document) const @@ -1087,23 +1120,6 @@ void InspectorDOMAgent::didModifyDOMAttr(Element* element) m_frontend->attributesUpdated(id, buildArrayForElementAttributes(element)); } -PassRefPtr<InspectorObject> InspectorDOMAgent::createBreakpoint(Node* node, long type) -{ - RefPtr<InspectorObject> breakpoint = InspectorObject::create(); - - // Find breakpoint owner. - while (!(m_breakpoints.get(node) & (1 << type))) { - node = innerParentNode(node); - ASSERT(node); - } - long nodeId = m_documentNodeToIdMap.get(node); - ASSERT(nodeId); - - breakpoint->setNumber("nodeId", nodeId); - breakpoint->setNumber("type", type); - return breakpoint.release(); -} - bool InspectorDOMAgent::hasBreakpoint(Node* node, long type) { uint32_t rootBit = 1 << type; @@ -1148,7 +1164,7 @@ void InspectorDOMAgent::getStyles(long nodeId, bool authorOnly, RefPtr<Inspector result->setObject("computedStyle", buildObjectForStyle(computedStyleInfo.get(), false)); CSSStyleSelector* selector = element->ownerDocument()->styleSelector(); - RefPtr<CSSRuleList> matchedRules = selector->styleRulesForElement(element, authorOnly); + RefPtr<CSSRuleList> matchedRules = selector->styleRulesForElement(element, authorOnly, true); result->setArray("matchedCSSRules", buildArrayForCSSRules(node->ownerDocument(), matchedRules.get())); result->setObject("styleAttributes", buildObjectForAttributeStyles(element)); @@ -1163,7 +1179,7 @@ void InspectorDOMAgent::getStyles(long nodeId, bool authorOnly, RefPtr<Inspector parentStyle->setObject("inlineStyle", buildObjectForStyle(parentElement->style(), true)); CSSStyleSelector* parentSelector = parentElement->ownerDocument()->styleSelector(); - RefPtr<CSSRuleList> parentMatchedRules = parentSelector->styleRulesForElement(parentElement, authorOnly); + RefPtr<CSSRuleList> parentMatchedRules = parentSelector->styleRulesForElement(parentElement, authorOnly, true); parentStyle->setArray("matchedCSSRules", buildArrayForCSSRules(parentElement->ownerDocument(), parentMatchedRules.get())); parentElement = parentElement->parentElement(); @@ -1191,26 +1207,34 @@ void InspectorDOMAgent::getStyleSheet(long styleSheetId, RefPtr<InspectorObject> *styleSheetObject = buildObjectForStyleSheet(styleSheet->document(), styleSheet); } -void InspectorDOMAgent::getRuleRanges(long styleSheetId, RefPtr<InspectorValue>* ruleRange) +void InspectorDOMAgent::getStyleSourceData(long styleId, RefPtr<InspectorObject>* dataObject) { - CSSStyleSheet* styleSheet = cssStore()->styleSheetForId(styleSheetId); - if (styleSheet && styleSheet->document()) { - HashMap<long, SourceRange> ruleRanges = cssStore()->getRuleRanges(styleSheet); - if (!ruleRanges.size()) - return; - RefPtr<InspectorObject> result = InspectorObject::create(); - for (HashMap<long, SourceRange>::iterator it = ruleRanges.begin(); it != ruleRanges.end(); ++it) { - if (it->second.second) { - RefPtr<InspectorObject> ruleRange = InspectorObject::create(); - result->setObject(String::number(it->first).utf8().data(), ruleRange); - RefPtr<InspectorObject> bodyRange = InspectorObject::create(); - ruleRange->setObject("bodyRange", bodyRange); - bodyRange->setNumber("start", it->second.first); - bodyRange->setNumber("end", it->second.second); - } - } - *ruleRange = result.release(); + CSSStyleDeclaration* style = cssStore()->styleForId(styleId); + if (!style) + return; + RefPtr<CSSStyleSourceData> sourceData = CSSStyleSourceData::create(); + bool success = cssStore()->getStyleSourceData(style, &sourceData); + if (!success) + return; + RefPtr<InspectorObject> result = InspectorObject::create(); + RefPtr<InspectorObject> bodyRange = InspectorObject::create(); + result->setObject("bodyRange", bodyRange); + bodyRange->setNumber("start", sourceData->styleBodyRange.start); + bodyRange->setNumber("end", sourceData->styleBodyRange.end); + RefPtr<InspectorArray> propertyRanges = InspectorArray::create(); + result->setArray("propertyData", propertyRanges); + Vector<CSSPropertySourceData>& propertyData = sourceData->propertyData; + for (Vector<CSSPropertySourceData>::iterator it = propertyData.begin(); it != propertyData.end(); ++it) { + RefPtr<InspectorObject> propertyRange = InspectorObject::create(); + propertyRange->setString("name", it->name); + propertyRange->setString("value", it->value); + propertyRange->setBoolean("important", it->important); + propertyRange->setBoolean("parsed", it->parsedOk); + propertyRange->setNumber("start", it->range.start); + propertyRange->setNumber("end", it->range.end); + propertyRanges->pushObject(propertyRange); } + *dataObject = result.release(); } void InspectorDOMAgent::getInlineStyle(long nodeId, RefPtr<InspectorValue>* style) @@ -1267,7 +1291,7 @@ PassRefPtr<InspectorArray> InspectorDOMAgent::buildArrayForPseudoElements(Elemen RefPtr<RenderStyle> renderStyle = element->styleForRenderer(); for (PseudoId pseudoId = FIRST_PUBLIC_PSEUDOID; pseudoId < AFTER_LAST_INTERNAL_PSEUDOID; pseudoId = static_cast<PseudoId>(pseudoId + 1)) { - RefPtr<CSSRuleList> matchedRules = selector->pseudoStyleRulesForElement(element, pseudoId, authorOnly); + RefPtr<CSSRuleList> matchedRules = selector->pseudoStyleRulesForElement(element, pseudoId, authorOnly, true); if (matchedRules && matchedRules->length()) { RefPtr<InspectorObject> pseudoStyles = InspectorObject::create(); pseudoStyles->setNumber("pseudoId", static_cast<int>(pseudoId)); @@ -1469,7 +1493,7 @@ PassRefPtr<InspectorObject> InspectorDOMAgent::buildObjectForStyle(CSSStyleDecla if (bind) { long styleId = cssStore()->bindStyle(style); result->setNumber("id", styleId); - CSSStyleSheet* parentStyleSheet = getParentStyleSheet(style); + CSSStyleSheet* parentStyleSheet = InspectorCSSStore::getParentStyleSheet(style); if (parentStyleSheet) result->setNumber("parentStyleSheetId", cssStore()->bindStyleSheet(parentStyleSheet)); @@ -1681,20 +1705,6 @@ PassRefPtr<InspectorArray> InspectorDOMAgent::toArray(const Vector<String>& data return result.release(); } -CSSStyleSheet* InspectorDOMAgent::getParentStyleSheet(CSSStyleDeclaration* style) -{ - CSSStyleSheet* parentStyleSheet = style->parentRule() ? style->parentRule()->parentStyleSheet() : 0; - if (!parentStyleSheet) { - StyleBase* parent = style->parent(); - if (parent && parent->isCSSStyleSheet()) { - parentStyleSheet = static_cast<CSSStyleSheet*>(parent); - if (!parentStyleSheet->length()) - return 0; - } - } - return parentStyleSheet; -} - void InspectorDOMAgent::onMatchJobsTimer(Timer<InspectorDOMAgent>*) { if (!m_pendingMatchJobs.size()) { diff --git a/WebCore/inspector/InspectorDOMAgent.h b/WebCore/inspector/InspectorDOMAgent.h index 9751e8e..7d86997 100644 --- a/WebCore/inspector/InspectorDOMAgent.h +++ b/WebCore/inspector/InspectorDOMAgent.h @@ -123,7 +123,7 @@ namespace WebCore { void getInlineStyle(long nodeId, RefPtr<InspectorValue>* styles); void getComputedStyle(long nodeId, RefPtr<InspectorValue>* styles); void getStyleSheet(long styleSheetId, RefPtr<InspectorObject>* styleSheetObject); - void getRuleRanges(long styleSheetId, RefPtr<InspectorValue>* ruleRange); + void getStyleSourceData(long styleId, RefPtr<InspectorObject>* dataObject); void applyStyleText(long styleId, const String& styleText, const String& propertyName, bool* success, RefPtr<InspectorValue>* styleObject, RefPtr<InspectorArray>* changedProperties); void setStyleText(long styleId, const String& cssText, bool* success); void setStyleProperty(long styleId, const String& name, const String& value, bool* success); @@ -148,7 +148,6 @@ namespace WebCore { void copyNode(long nodeId); private: - static CSSStyleSheet* getParentStyleSheet(CSSStyleDeclaration*); void startListening(Document* document); void stopListening(Document* document); @@ -162,8 +161,8 @@ namespace WebCore { bool pushDocumentToFrontend(); bool hasBreakpoint(Node* node, long type); - PassRefPtr<InspectorObject> createBreakpoint(Node* node, long type); void updateSubtreeBreakpoints(Node* root, uint32_t rootMask, bool value); + PassRefPtr<InspectorValue> descriptionForDOMEvent(Node* target, long breakpointType, bool insertion); PassRefPtr<InspectorObject> buildObjectForAttributeStyles(Element* element); PassRefPtr<InspectorArray> buildArrayForCSSRules(Document* ownerDocument, CSSRuleList*); diff --git a/WebCore/inspector/InspectorDebuggerAgent.cpp b/WebCore/inspector/InspectorDebuggerAgent.cpp index fd9fdf3..3875be0 100644 --- a/WebCore/inspector/InspectorDebuggerAgent.cpp +++ b/WebCore/inspector/InspectorDebuggerAgent.cpp @@ -38,6 +38,7 @@ #include "PlatformString.h" #include "ScriptDebugServer.h" #include <wtf/MD5.h> +#include <wtf/text/CString.h> namespace WebCore { @@ -292,7 +293,7 @@ void InspectorDebuggerAgent::didPause(ScriptState* scriptState) m_pausedScriptState = scriptState; RefPtr<InspectorObject> details = InspectorObject::create(); details->setValue("callFrames", currentCallFrames()); - details->setValue("reason", m_breakProgramReason); + details->setValue("status", m_breakProgramReason); m_frontend->pausedScript(details); } diff --git a/WebCore/inspector/InspectorProfilerAgent.cpp b/WebCore/inspector/InspectorProfilerAgent.cpp index 9161c3b..09b2b32 100644 --- a/WebCore/inspector/InspectorProfilerAgent.cpp +++ b/WebCore/inspector/InspectorProfilerAgent.cpp @@ -43,6 +43,7 @@ #include "ScriptProfile.h" #include "ScriptProfiler.h" #include <wtf/OwnPtr.h> +#include <wtf/text/CString.h> #if USE(JSC) #include "JSDOMWindow.h" diff --git a/WebCore/inspector/InspectorResource.cpp b/WebCore/inspector/InspectorResource.cpp index be77827..ac2c9a0 100644 --- a/WebCore/inspector/InspectorResource.cpp +++ b/WebCore/inspector/InspectorResource.cpp @@ -43,14 +43,39 @@ #include "ResourceLoadTiming.h" #include "ResourceRequest.h" #include "ResourceResponse.h" +#include "StringBuffer.h" #include "TextEncoding.h" +#include "WebSocketHandshakeRequest.h" +#include "WebSocketHandshakeResponse.h" + +#include <wtf/Assertions.h> namespace WebCore { +#if ENABLE(WEB_SOCKETS) +// Create human-readable binary representation, like "01:23:45:67:89:AB:CD:EF". +static String createReadableStringFromBinary(const unsigned char* value, size_t length) +{ + ASSERT(length > 0); + static const char hexDigits[17] = "0123456789ABCDEF"; + size_t bufferSize = length * 3 - 1; + StringBuffer buffer(bufferSize); + size_t index = 0; + for (size_t i = 0; i < length; ++i) { + if (i > 0) + buffer[index++] = ':'; + buffer[index++] = hexDigits[value[i] >> 4]; + buffer[index++] = hexDigits[value[i] & 0xF]; + } + ASSERT(index == bufferSize); + return String::adopt(buffer); +} +#endif + InspectorResource::InspectorResource(unsigned long identifier, DocumentLoader* loader, const KURL& requestURL) : m_identifier(identifier) , m_loader(loader) - , m_frame(loader->frame()) + , m_frame(loader ? loader->frame() : 0) , m_requestURL(requestURL) , m_expectedContentLength(0) , m_cached(false) @@ -66,6 +91,9 @@ InspectorResource::InspectorResource(unsigned long identifier, DocumentLoader* l , m_connectionID(0) , m_connectionReused(false) , m_isMainResource(false) +#if ENABLE(WEB_SOCKETS) + , m_isWebSocket(false) +#endif { } @@ -88,6 +116,12 @@ PassRefPtr<InspectorResource> InspectorResource::appendRedirect(unsigned long id return redirect; } +PassRefPtr<InspectorResource> InspectorResource::create(unsigned long identifier, DocumentLoader* loader, const KURL& requestURL) +{ + ASSERT(loader); + return adoptRef(new InspectorResource(identifier, loader, requestURL)); +} + PassRefPtr<InspectorResource> InspectorResource::createCached(unsigned long identifier, DocumentLoader* loader, const CachedResource* cachedResource) { PassRefPtr<InspectorResource> resource = create(identifier, loader, KURL(ParsedURLString, cachedResource->url())); @@ -107,6 +141,16 @@ PassRefPtr<InspectorResource> InspectorResource::createCached(unsigned long iden return resource; } +#if ENABLE(WEB_SOCKETS) +PassRefPtr<InspectorResource> InspectorResource::createWebSocket(unsigned long identifier, const KURL& requestURL, const KURL& documentURL) +{ + RefPtr<InspectorResource> resource = adoptRef(new InspectorResource(identifier, 0, requestURL)); + resource->markWebSocket(); + resource->m_documentURL = documentURL; + return resource.release(); +} +#endif + void InspectorResource::updateRequest(const ResourceRequest& request) { m_requestHeaderFields = request.httpHeaderFields(); @@ -117,6 +161,11 @@ void InspectorResource::updateRequest(const ResourceRequest& request) m_changes.set(RequestChange); } +void InspectorResource::markAsCached() +{ + m_cached = true; +} + void InspectorResource::updateResponse(const ResourceResponse& response) { m_expectedContentLength = response.expectedContentLength(); @@ -134,7 +183,7 @@ void InspectorResource::updateResponse(const ResourceResponse& response) m_connectionID = response.connectionID(); m_connectionReused = response.connectionReused(); m_loadTiming = response.resourceLoadTiming(); - m_cached = response.wasCached(); + m_cached = m_cached || response.wasCached(); if (!m_cached && m_loadTiming && m_loadTiming->requestTime) m_responseReceivedTime = m_loadTiming->requestTime + m_loadTiming->receiveHeadersEnd / 1000.0; @@ -146,6 +195,27 @@ void InspectorResource::updateResponse(const ResourceResponse& response) m_changes.set(TypeChange); } +#if ENABLE(WEB_SOCKETS) +void InspectorResource::updateWebSocketRequest(const WebSocketHandshakeRequest& request) +{ + m_requestHeaderFields = request.headerFields(); + m_requestMethod = "GET"; // Currently we always use "GET" to request handshake. + m_webSocketRequestKey3.set(new WebSocketHandshakeRequest::Key3(request.key3())); + m_changes.set(RequestChange); + m_changes.set(TypeChange); +} + +void InspectorResource::updateWebSocketResponse(const WebSocketHandshakeResponse& response) +{ + m_responseStatusCode = response.statusCode(); + m_responseStatusText = response.statusText(); + m_responseHeaderFields = response.headerFields(); + m_webSocketChallengeResponse.set(new WebSocketHandshakeResponse::ChallengeResponse(response.challengeResponse())); + m_changes.set(ResponseChange); + m_changes.set(TypeChange); +} +#endif // ENABLE(WEB_SOCKETS) + static PassRefPtr<InspectorObject> buildHeadersObject(const HTTPHeaderMap& headers) { RefPtr<InspectorObject> object = InspectorObject::create(); @@ -183,8 +253,10 @@ void InspectorResource::updateScriptObject(InspectorFrontend* frontend) RefPtr<InspectorObject> jsonObject = InspectorObject::create(); jsonObject->setNumber("id", m_identifier); if (m_changes.hasChange(RequestChange)) { + if (m_frame) + m_documentURL = m_frame->document()->url(); jsonObject->setString("url", m_requestURL.string()); - jsonObject->setString("documentURL", m_frame->document()->url().string()); + jsonObject->setString("documentURL", m_documentURL.string()); jsonObject->setString("host", m_requestURL.host()); jsonObject->setString("path", m_requestURL.path()); jsonObject->setString("lastPathComponent", m_requestURL.lastPathComponent()); @@ -194,6 +266,10 @@ void InspectorResource::updateScriptObject(InspectorFrontend* frontend) jsonObject->setString("requestMethod", m_requestMethod); jsonObject->setString("requestFormData", m_requestFormData); jsonObject->setBoolean("didRequestChange", true); +#if ENABLE(WEB_SOCKETS) + if (m_webSocketRequestKey3) + jsonObject->setString("webSocketRequestKey3", createReadableStringFromBinary(m_webSocketRequestKey3->value, sizeof(m_webSocketRequestKey3->value))); +#endif } if (m_changes.hasChange(ResponseChange)) { @@ -209,6 +285,10 @@ void InspectorResource::updateScriptObject(InspectorFrontend* frontend) jsonObject->setBoolean("cached", m_cached); if (m_loadTiming && !m_cached) jsonObject->setObject("timing", buildObjectForTiming(m_loadTiming.get())); +#if ENABLE(WEB_SOCKETS) + if (m_webSocketChallengeResponse) + jsonObject->setString("webSocketChallengeResponse", createReadableStringFromBinary(m_webSocketChallengeResponse->value, sizeof(m_webSocketChallengeResponse->value))); +#endif jsonObject->setBoolean("didResponseChange", true); } @@ -267,6 +347,8 @@ CachedResource* InspectorResource::cachedResource() const // Try hard to find a corresponding CachedResource. During preloading, CachedResourceLoader may not have the resource in document resources set yet, // but Inspector will already try to fetch data that is only available via CachedResource (and it won't update once the resource is added, // because m_changes will not have the appropriate bits set). + if (!m_frame) + return 0; const String& url = m_requestURL.string(); CachedResource* cachedResource = m_frame->document()->cachedResourceLoader()->cachedResource(url); if (!cachedResource) @@ -303,6 +385,12 @@ InspectorResource::Type InspectorResource::type() const if (!m_overrideContent.isNull()) return m_overrideContentType; +#if ENABLE(WEB_SOCKETS) + if (m_isWebSocket) + return WebSocket; +#endif + + ASSERT(m_loader); if (equalIgnoringFragmentIdentifier(m_requestURL, m_loader->requestURL())) { InspectorResource::Type resourceType = cachedResourceType(); if (resourceType == Other) @@ -342,7 +430,7 @@ String InspectorResource::sourceString() const PassRefPtr<SharedBuffer> InspectorResource::resourceData(String* textEncodingName) const { - if (equalIgnoringFragmentIdentifier(m_requestURL, m_loader->requestURL())) { + if (m_loader && equalIgnoringFragmentIdentifier(m_requestURL, m_loader->requestURL())) { *textEncodingName = m_frame->document()->inputEncoding(); return m_loader->mainResourceData(); } @@ -372,9 +460,18 @@ void InspectorResource::startTiming() m_changes.set(TimingChange); } -void InspectorResource::endTiming() +void InspectorResource::endTiming(double actualEndTime) { - m_endTime = currentTime(); + if (actualEndTime) { + m_endTime = actualEndTime; + // In case of fast load (or in case of cached resources), endTime on network stack + // can be less then m_responseReceivedTime measured in WebCore. Normalize it here, + // prefer actualEndTime to m_responseReceivedTime. + if (m_endTime < m_responseReceivedTime) + m_responseReceivedTime = m_endTime; + } else + m_endTime = currentTime(); + m_finished = true; m_changes.set(TimingChange); m_changes.set(CompletionChange); diff --git a/WebCore/inspector/InspectorResource.h b/WebCore/inspector/InspectorResource.h index 4004142..914232e 100644 --- a/WebCore/inspector/InspectorResource.h +++ b/WebCore/inspector/InspectorResource.h @@ -34,6 +34,8 @@ #include "HTTPHeaderMap.h" #include "KURL.h" #include "ScriptString.h" +#include "WebSocketHandshakeRequest.h" +#include "WebSocketHandshakeResponse.h" #include <wtf/CurrentTime.h> #include <wtf/OwnPtr.h> @@ -51,6 +53,11 @@ namespace WebCore { class ResourceRequest; class ResourceResponse; +#if ENABLE(WEB_SOCKETS) + class WebSocketHandshakeRequest; + class WebSocketHandshakeResponse; +#endif + class InspectorResource : public RefCounted<InspectorResource> { public: @@ -63,16 +70,19 @@ namespace WebCore { Script, XHR, Media, + WebSocket, Other }; - static PassRefPtr<InspectorResource> create(unsigned long identifier, DocumentLoader* loader, const KURL& requestURL) - { - return adoptRef(new InspectorResource(identifier, loader, requestURL)); - } + static PassRefPtr<InspectorResource> create(unsigned long identifier, DocumentLoader* loader, const KURL& requestURL); static PassRefPtr<InspectorResource> createCached(unsigned long identifier, DocumentLoader*, const CachedResource*); +#if ENABLE(WEB_SOCKETS) + // WebSocket resource doesn't have its loader. For WebSocket resources, m_loader and m_frame will become null. + static PassRefPtr<InspectorResource> createWebSocket(unsigned long identifier, const KURL& requestURL, const KURL& documentURL); +#endif + ~InspectorResource(); PassRefPtr<InspectorResource> appendRedirect(unsigned long identifier, const KURL& redirectURL); @@ -80,8 +90,14 @@ namespace WebCore { void releaseScriptObject(InspectorFrontend* frontend); void updateRequest(const ResourceRequest&); + void markAsCached(); void updateResponse(const ResourceResponse&); +#if ENABLE(WEB_SOCKETS) + void updateWebSocketRequest(const WebSocketHandshakeRequest&); + void updateWebSocketResponse(const WebSocketHandshakeResponse&); +#endif + void setOverrideContent(const ScriptString& data, Type); String sourceString() const; @@ -102,7 +118,7 @@ namespace WebCore { void startTiming(); void markLoadEventTime(); void markDOMContentEventTime(); - void endTiming(); + void endTiming(double actualEndTime); void markFailed(); void addLength(int lengthReceived); @@ -149,10 +165,15 @@ namespace WebCore { Type cachedResourceType() const; CachedResource* cachedResource() const; +#if ENABLE(WEB_SOCKETS) + void markWebSocket() { m_isWebSocket = true; } +#endif + unsigned long m_identifier; RefPtr<DocumentLoader> m_loader; RefPtr<Frame> m_frame; KURL m_requestURL; + KURL m_documentURL; HTTPHeaderMap m_requestHeaderFields; HTTPHeaderMap m_responseHeaderFields; String m_mimeType; @@ -179,6 +200,15 @@ namespace WebCore { String m_requestMethod; String m_requestFormData; Vector<RefPtr<InspectorResource> > m_redirects; + +#if ENABLE(WEB_SOCKETS) + bool m_isWebSocket; + + // The following fields are not used for resources other than WebSocket. + // We allocate them dynamically to reduce memory consumption for regular resources. + OwnPtr<WebSocketHandshakeRequest::Key3> m_webSocketRequestKey3; + OwnPtr<WebSocketHandshakeResponse::ChallengeResponse> m_webSocketChallengeResponse; +#endif }; } // namespace WebCore diff --git a/WebCore/inspector/front-end/AuditFormatters.js b/WebCore/inspector/front-end/AuditFormatters.js index de277ad..1bc1803 100755..100644 --- a/WebCore/inspector/front-end/AuditFormatters.js +++ b/WebCore/inspector/front-end/AuditFormatters.js @@ -79,8 +79,14 @@ WebInspector.AuditFormatters = { return parent; }, - url: function(url, displayText) + url: function(url, displayText, allowExternalNavigation) { - return WebInspector.linkifyURLAsNode(url, displayText || url, null, (url in WebInspector.resourceURLMap)); + var a = document.createElement("a"); + a.href = url; + a.title = url; + a.textContent = displayText || url; + if (allowExternalNavigation) + a.target = "_blank"; + return a; } }; diff --git a/WebCore/inspector/front-end/BreakpointsSidebarPane.js b/WebCore/inspector/front-end/BreakpointsSidebarPane.js index cda72fb..9688f3e 100644 --- a/WebCore/inspector/front-end/BreakpointsSidebarPane.js +++ b/WebCore/inspector/front-end/BreakpointsSidebarPane.js @@ -97,6 +97,7 @@ WebInspector.BreakpointItem = function(breakpoint) this._breakpoint = breakpoint; this._element = document.createElement("li"); + this._element.addEventListener("click", this._breakpointClicked.bind(this), false); var checkboxElement = document.createElement("input"); checkboxElement.className = "checkbox-elem"; @@ -141,8 +142,6 @@ WebInspector.JSBreakpointItem = function(breakpoint) { WebInspector.BreakpointItem.call(this, breakpoint); - this._element.addEventListener("click", this._breakpointClicked.bind(this), false); - var displayName = this._breakpoint.url ? WebInspector.displayNameForURL(this._breakpoint.url) : WebInspector.UIString("(program)"); var labelElement = document.createTextNode(displayName + ":" + this._breakpoint.line); this._element.appendChild(labelElement); @@ -183,8 +182,7 @@ WebInspector.DOMBreakpointItem = function(breakpoint) { WebInspector.BreakpointItem.call(this, breakpoint); - var node = WebInspector.domAgent.nodeForId(this._breakpoint.nodeId); - var link = WebInspector.panels.elements.linkifyNodeReference(node); + var link = WebInspector.panels.elements.linkifyNodeById(this._breakpoint.nodeId); this._element.appendChild(link); var type = WebInspector.DOMBreakpoint.labelForType(this._breakpoint.type); @@ -198,6 +196,11 @@ WebInspector.DOMBreakpointItem.prototype = { if (this._breakpoint.type != other._breakpoint.type) return this._breakpoint.type < other._breakpoint.type ? -1 : 1; return 0; + }, + + _breakpointClicked: function() + { + WebInspector.updateFocusedNode(this._breakpoint.nodeId); } } diff --git a/WebCore/inspector/front-end/CallStackSidebarPane.js b/WebCore/inspector/front-end/CallStackSidebarPane.js index 60eee34..91f35a6 100644 --- a/WebCore/inspector/front-end/CallStackSidebarPane.js +++ b/WebCore/inspector/front-end/CallStackSidebarPane.js @@ -26,7 +26,6 @@ WebInspector.CallStackSidebarPane = function() { WebInspector.SidebarPane.call(this, WebInspector.UIString("Call Stack")); - } WebInspector.CallStackSidebarPane.prototype = { @@ -83,6 +82,39 @@ WebInspector.CallStackSidebarPane.prototype = { } }, + updateStatus: function(status) + { + var statusElement = document.createElement("div"); + statusElement.className = "info"; + + var breakpointType = status.breakpoint.type; + var substitutions = [WebInspector.DOMBreakpoint.labelForType(breakpointType), WebInspector.panels.elements.linkifyNodeById(status.breakpoint.nodeId)]; + var formatters = { + s: function(substitution) + { + return substitution; + } + }; + function append(a, b) + { + if (typeof b === "string") + b = document.createTextNode(b); + statusElement.appendChild(b); + } + if (breakpointType === WebInspector.DOMBreakpoint.Types.SubtreeModified) { + var targetNode = WebInspector.panels.elements.linkifyNodeById(status.targetNodeId); + if (status.insertion) { + if (status.targetNodeId !== status.breakpoint.nodeId) + WebInspector.formatLocalized("Paused on a \"%s\" breakpoint set on %s, because a new child was added to its descendant %s.", substitutions.concat(targetNode), formatters, "", append); + else + WebInspector.formatLocalized("Paused on a \"%s\" breakpoint set on %s, because a new child was added to that node.", substitutions, formatters, "", append); + } else + WebInspector.formatLocalized("Paused on a \"%s\" breakpoint set on %s, because its descendant %s was removed.", substitutions.concat(targetNode), formatters, "", append); + } else + WebInspector.formatLocalized("Paused on a \"%s\" breakpoint set on %s.", substitutions, formatters, "", append); + this.bodyElement.appendChild(statusElement); + }, + get selectedCallFrame() { return this._selectedCallFrame; diff --git a/WebCore/inspector/front-end/ConsoleView.js b/WebCore/inspector/front-end/ConsoleView.js index 9785cd0..6f8bd8b 100644 --- a/WebCore/inspector/front-end/ConsoleView.js +++ b/WebCore/inspector/front-end/ConsoleView.js @@ -400,10 +400,13 @@ WebInspector.ConsoleView.prototype = { } var contextMenu = new WebInspector.ContextMenu(); - if (!WebInspector.monitoringXHREnabled) - contextMenu.appendCheckboxItem(WebInspector.UIString("XMLHttpRequest logging"), InspectorBackend.enableMonitoringXHR.bind(InspectorBackend), false); - else - contextMenu.appendCheckboxItem(WebInspector.UIString("XMLHttpRequest logging"), InspectorBackend.disableMonitoringXHR.bind(InspectorBackend), true); + + function monitoringXHRWasChanged(newState) + { + WebInspector.monitoringXHREnabled = newState; + } + var itemAction = InspectorBackend.setMonitoringXHREnabled.bind(InspectorBackend, !WebInspector.monitoringXHREnabled, monitoringXHRWasChanged); + contextMenu.appendCheckboxItem(WebInspector.UIString("XMLHttpRequest logging"), itemAction, WebInspector.monitoringXHREnabled); contextMenu.appendItem(WebInspector.UIString("Clear Console"), this.requestClearMessages.bind(this)); contextMenu.show(event); }, @@ -518,7 +521,7 @@ WebInspector.ConsoleView.prototype = { _enterKeyPressed: function(event) { - if (event.altKey) + if (event.altKey || event.ctrlKey) return; event.preventDefault(); @@ -1091,7 +1094,7 @@ WebInspector.ConsoleGroup.prototype = { if (msg.type === WebInspector.ConsoleMessage.MessageType.StartGroup || msg.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed) { this.messagesElement.parentNode.insertBefore(element, this.messagesElement); - element.addEventListener("click", this._titleClicked.bind(this), true); + element.addEventListener("click", this._titleClicked.bind(this), false); var groupElement = element.enclosingNodeOrSelfWithClass("console-group"); if (groupElement && msg.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed) groupElement.addStyleClass("collapsed"); diff --git a/WebCore/inspector/front-end/ElementsPanel.js b/WebCore/inspector/front-end/ElementsPanel.js index c60d4b1..72b23e1 100644 --- a/WebCore/inspector/front-end/ElementsPanel.js +++ b/WebCore/inspector/front-end/ElementsPanel.js @@ -147,7 +147,7 @@ WebInspector.ElementsPanel.prototype = { { WebInspector.Panel.prototype.hide.call(this); - WebInspector.hoveredDOMNode = null; + WebInspector.highlightDOMNode(0); InspectorBackend.disableSearchingForNode(); }, @@ -165,7 +165,7 @@ WebInspector.ElementsPanel.prototype = { this.rootDOMNode = null; this.focusedDOMNode = null; - WebInspector.hoveredDOMNode = null; + WebInspector.highlightDOMNode(0); this.recentlyModifiedNodes = []; @@ -187,9 +187,7 @@ WebInspector.ElementsPanel.prototype = { inspectedRootDocument.addEventListener("DOMNodeRemoved", this._nodeRemoved.bind(this)); inspectedRootDocument.addEventListener("DOMAttrModified", this._attributesUpdated.bind(this)); - this.treeOutline.suppressSelectHighlight = true; this.rootDOMNode = inspectedRootDocument; - this.treeOutline.suppressSelectHighlight = false; function selectNode(candidateFocusNode) { @@ -199,11 +197,9 @@ WebInspector.ElementsPanel.prototype = { if (!candidateFocusNode) return; - this.treeOutline.suppressSelectHighlight = true; this.focusedDOMNode = candidateFocusNode; if (this.treeOutline.selectedTreeElement) this.treeOutline.selectedTreeElement.expand(); - this.treeOutline.suppressSelectHighlight = false; } function selectLastSelectedNode(nodeId) @@ -261,6 +257,23 @@ WebInspector.ElementsPanel.prototype = { this._nodeSearchButton.toggled = false; }, + populateHrefContextMenu: function(contextMenu, event, anchorElement) + { + if (!anchorElement.href) + return false; + + var resourceURL = WebInspector.resourceURLForRelatedNode(this.focusedDOMNode, anchorElement.href); + if (!resourceURL) + return false; + + // Add resource-related actions. + // Keep these consistent with those added in WebInspector.StylesSidebarPane.prototype._populateHrefContextMenu(). + contextMenu.appendItem(WebInspector.UIString("Open Link in New Window"), WebInspector.openResource.bind(null, resourceURL, false)); + if (WebInspector.resourceForURL(resourceURL)) + contextMenu.appendItem(WebInspector.UIString("Open Link in Resources Panel"), WebInspector.openResource.bind(null, resourceURL, true)); + return true; + }, + _updateMatchesCount: function() { WebInspector.updateSearchMatchesCount(this._searchResults.length, this); @@ -561,7 +574,7 @@ WebInspector.ElementsPanel.prototype = { var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY); var crumbElement = nodeUnderMouse.enclosingNodeOrSelfWithClass("crumb"); - WebInspector.hoveredDOMNode = (crumbElement ? crumbElement.representedObject : null); + WebInspector.highlightDOMNode(crumbElement ? crumbElement.representedObject.id : 0); if ("_mouseOutOfCrumbsTimeout" in this) { clearTimeout(this._mouseOutOfCrumbsTimeout); @@ -575,7 +588,7 @@ WebInspector.ElementsPanel.prototype = { if (nodeUnderMouse && nodeUnderMouse.isDescendant(this.crumbsElement)) return; - WebInspector.hoveredDOMNode = null; + WebInspector.highlightDOMNode(0); this._mouseOutOfCrumbsTimeout = setTimeout(this.updateBreadcrumbSizes.bind(this), 1000); }, @@ -761,11 +774,19 @@ WebInspector.ElementsPanel.prototype = { { var link = document.createElement("span"); link.className = "node-link"; - link.addEventListener("click", WebInspector.updateFocusedNode.bind(WebInspector, node.id), false); this.decorateNodeLabel(node, link); + WebInspector.wireElementWithDOMNode(link, node.id); return link; }, + linkifyNodeById: function(nodeId) + { + var node = WebInspector.domAgent.nodeForId(nodeId); + if (!node) + return document.createTextNode(WebInspector.UIString("<node>")); + return this.linkifyNodeReference(node); + }, + updateBreadcrumbSizes: function(focusedCrumb) { if (!this.visible) diff --git a/WebCore/inspector/front-end/ElementsTreeOutline.js b/WebCore/inspector/front-end/ElementsTreeOutline.js index 10131f4..e261234 100644 --- a/WebCore/inspector/front-end/ElementsTreeOutline.js +++ b/WebCore/inspector/front-end/ElementsTreeOutline.js @@ -93,27 +93,8 @@ WebInspector.ElementsTreeOutline.prototype = { // and the select() call would change the focusedDOMNode and reenter this setter. So to // avoid calling focusedNodeChanged() twice, first check if _focusedDOMNode is the same // node as the one passed in. - if (this._focusedDOMNode === x) { + if (this._focusedDOMNode === x) this.focusedNodeChanged(); - - if (x && !this.suppressSelectHighlight) { - InspectorBackend.highlightDOMNode(x.id); - - if ("_restorePreviousHighlightNodeTimeout" in this) - clearTimeout(this._restorePreviousHighlightNodeTimeout); - - function restoreHighlightToHoveredNode() - { - var hoveredNode = WebInspector.hoveredDOMNode; - if (hoveredNode) - InspectorBackend.highlightDOMNode(hoveredNode.id); - else - InspectorBackend.hideDOMNodeHighlight(); - } - - this._restorePreviousHighlightNodeTimeout = setTimeout(restoreHighlightToHoveredNode, 2000); - } - } }, get editing() @@ -260,7 +241,7 @@ WebInspector.ElementsTreeOutline.prototype = { element._createTooltipForNode(); } - WebInspector.hoveredDOMNode = (element ? element.representedObject : null); + WebInspector.highlightDOMNode(element ? element.representedObject.id : 0); }, _onmouseout: function(event) @@ -274,7 +255,7 @@ WebInspector.ElementsTreeOutline.prototype = { delete this._previousHoveredElement; } - WebInspector.hoveredDOMNode = null; + WebInspector.highlightDOMNode(0); }, _contextMenuEventFired: function(event) @@ -285,12 +266,21 @@ WebInspector.ElementsTreeOutline.prototype = { var contextMenu = new WebInspector.ContextMenu(); + var href = event.target.enclosingNodeOrSelfWithClass("webkit-html-resource-link") || event.target.enclosingNodeOrSelfWithClass("webkit-html-external-link"); var tag = event.target.enclosingNodeOrSelfWithClass("webkit-html-tag"); var textNode = event.target.enclosingNodeOrSelfWithClass("webkit-html-text-node"); - if (tag && listItem.treeElement._populateTagContextMenu) + var needSeparator; + if (href) + needSeparator = WebInspector.panels.elements.populateHrefContextMenu(contextMenu, event, href); + if (tag && listItem.treeElement._populateTagContextMenu) { + if (needSeparator) + contextMenu.appendSeparator(); listItem.treeElement._populateTagContextMenu(contextMenu, event); - else if (textNode && listItem.treeElement._populateTextContextMenu) + } else if (textNode && listItem.treeElement._populateTextContextMenu) { + if (needSeparator) + contextMenu.appendSeparator(); listItem.treeElement._populateTextContextMenu(contextMenu, textNode); + } contextMenu.show(event); } } @@ -1189,29 +1179,6 @@ WebInspector.ElementsTreeElement.prototype = { this._highlightSearchResults(); }, - _rewriteAttrHref: function(node, hrefValue) - { - if (!hrefValue || hrefValue.indexOf("://") > 0) - return hrefValue; - - for (var frameOwnerCandidate = node; frameOwnerCandidate; frameOwnerCandidate = frameOwnerCandidate.parentNode) { - if (frameOwnerCandidate.documentURL) { - var result = WebInspector.completeURL(frameOwnerCandidate.documentURL, hrefValue); - if (result) - return result; - break; - } - } - - // documentURL not found or has bad value - for (var url in WebInspector.resourceURLMap) { - var match = url.match(WebInspector.URLRegExp); - if (match && match[4] === hrefValue) - return url; - } - return hrefValue; - }, - _attributeHTML: function(name, value, node, linkify) { var hasText = (value.length > 0); @@ -1221,7 +1188,7 @@ WebInspector.ElementsTreeElement.prototype = { html += "=​\""; if (linkify && (name === "src" || name === "href")) { - var rewrittenHref = this._rewriteAttrHref(node, value); + var rewrittenHref = WebInspector.resourceURLForRelatedNode(node, value); value = value.replace(/([\/;:\)\]\}])/g, "$1\u200B"); html += linkify(rewrittenHref, value, "webkit-html-attribute-value", node.nodeName.toLowerCase() === "a"); } else { diff --git a/WebCore/inspector/front-end/HAREntry.js b/WebCore/inspector/front-end/HAREntry.js index 9f188ed..85e4f59 100644 --- a/WebCore/inspector/front-end/HAREntry.js +++ b/WebCore/inspector/front-end/HAREntry.js @@ -42,7 +42,7 @@ WebInspector.HAREntry.prototype = { return { pageref: this._resource.documentURL, startedDateTime: new Date(this._resource.startTime * 1000), - time: this._toMilliseconds(this._resource.duration), + time: WebInspector.HAREntry._toMilliseconds(this._resource.duration), request: this._buildRequest(), response: this._buildResponse(), // cache: {...}, -- Not supproted yet. @@ -95,14 +95,34 @@ WebInspector.HAREntry.prototype = { _buildTimings: function() { + var waitForConnection = this._interval("connectStart", "connectEnd"); + var blocked; + var connect; + var dns = this._interval("dnsStart", "dnsEnd"); + var send = this._interval("sendStart", "sendEnd"); + var ssl = this._interval("sslStart", "sslEnd"); + + if (ssl !== -1 && send !== -1) + send -= ssl; + + if (this._resource.connectionReused) { + connect = -1; + blocked = waitForConnection; + } else { + blocked = 0; + connect = waitForConnection; + if (dns !== -1) + connect -= dns; + } + return { - blocked: -1, // Not available. - dns: -1, // Not available. - connect: -1, // Not available. - send: -1, // Not available. - wait: this._toMilliseconds(this._resource.latency), - receive: this._toMilliseconds(this._resource.receiveDuration), - ssl: -1 // Not available. + blocked: blocked, + dns: dns, + connect: connect, + send: send, + wait: this._interval("sendEnd", "receiveHeadersEnd"), + receive: WebInspector.HAREntry._toMilliseconds(this._resource.receiveDuration), + ssl: ssl }; }, @@ -130,8 +150,65 @@ WebInspector.HAREntry.prototype = { return parameters.slice(); }, - _toMilliseconds: function(time) + _interval: function(start, end) + { + var timing = this._resource.timing; + if (!timing) + return -1; + var startTime = timing[start]; + return typeof startTime !== "number" || startTime === -1 ? -1 : Math.round(timing[end] - startTime); + } +}; + +WebInspector.HAREntry._toMilliseconds = function(time) +{ + return time === -1 ? -1 : Math.round(time * 1000); +} + +WebInspector.HARLog = function() +{ +} + +WebInspector.HARLog.prototype = { + build: function() + { + var webKitVersion = /AppleWebKit\/([^ ]+)/.exec(window.navigator.userAgent); + + return { + version: "1.2", + creator: { + name: "WebInspector", + version: webKitVersion ? webKitVersion[1] : "n/a" + }, + pages: this._buildPages(), + entries: Object.properties(WebInspector.resources).map(this._convertResource) + } + }, + + _buildPages: function() + { + return [ + { + startedDateTime: new Date(WebInspector.mainResource.startTime * 1000), + id: WebInspector.mainResource.documentURL, + title: "", + pageTimings: this._buildMainResourceTimings() + } + ]; + }, + + _buildMainResourceTimings: function() + { + var resourcesPanel = WebInspector.panels.resources; + var startTime = WebInspector.mainResource.startTime; + return { + onContentLoad: WebInspector.HAREntry._toMilliseconds(resourcesPanel.mainResourceDOMContentTime - startTime), + onLoad: WebInspector.HAREntry._toMilliseconds(resourcesPanel.mainResourceLoadTime - startTime), + } + }, + + _convertResource: function(id) { - return time === -1 ? -1 : Math.round(time * 1000); + return (new WebInspector.HAREntry(WebInspector.resources[id])).build(); } }; diff --git a/WebCore/inspector/front-end/InjectedScript.js b/WebCore/inspector/front-end/InjectedScript.js index d4e3d80..5544ed5 100644 --- a/WebCore/inspector/front-end/InjectedScript.js +++ b/WebCore/inspector/front-end/InjectedScript.js @@ -464,6 +464,10 @@ InjectedScript.prototype = { return str.replace(/^\[object (.*)\]$/i, "$1"); } else { // V8 + if (isFinite(obj.length) && typeof obj.callee === "function") { + // Arguments.constructor === Object in V8 + return "Arguments"; + } return obj.constructor && obj.constructor.name || "Object"; } }, diff --git a/WebCore/inspector/front-end/ProfileDataGridTree.js b/WebCore/inspector/front-end/ProfileDataGridTree.js index b10f392..adf34f1 100644 --- a/WebCore/inspector/front-end/ProfileDataGridTree.js +++ b/WebCore/inspector/front-end/ProfileDataGridTree.js @@ -96,19 +96,10 @@ WebInspector.ProfileDataGridNode.prototype = { 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; - urlElement.preferredPanel = "scripts"; - + var lineNumber; if (this.profileNode.lineNumber > 0) - urlElement.textContent = fileName + ":" + this.profileNode.lineNumber; - else - urlElement.textContent = fileName; - + lineNumber = this.profileNode.lineNumber; + var urlElement = WebInspector.linkifyResourceAsNode(this.profileNode.url, "scripts", lineNumber, "profile-node-file"); cell.insertBefore(urlElement, cell.firstChild); } diff --git a/WebCore/inspector/front-end/Resource.js b/WebCore/inspector/front-end/Resource.js index de87047..ea9052d 100644 --- a/WebCore/inspector/front-end/Resource.js +++ b/WebCore/inspector/front-end/Resource.js @@ -45,7 +45,8 @@ WebInspector.Resource.Type = { Script: 4, XHR: 5, Media: 6, - Other: 7, + WebSocket: 7, + Other: 8, isTextType: function(type) { @@ -76,6 +77,8 @@ WebInspector.Resource.Type = { return "XHR"; case this.Media: return "media"; + case this.WebSocket: + return "WebSocket"; case this.Other: default: return "other"; @@ -372,6 +375,9 @@ WebInspector.Resource.prototype = { case WebInspector.Resource.Type.XHR: this.category = WebInspector.resourceCategories.xhr; break; + case WebInspector.Resource.Type.WebSocket: + this.category = WebInspector.resourceCategories.websocket; + break; case WebInspector.Resource.Type.Other: default: this.category = WebInspector.resourceCategories.other; @@ -584,7 +590,8 @@ WebInspector.Resource.prototype = { if (typeof this.type === "undefined" || this.type === WebInspector.Resource.Type.Other - || this.type === WebInspector.Resource.Type.XHR) + || this.type === WebInspector.Resource.Type.XHR + || this.type === WebInspector.Resource.Type.WebSocket) return true; if (this.mimeType in WebInspector.MIMETypes) diff --git a/WebCore/inspector/front-end/ResourceView.js b/WebCore/inspector/front-end/ResourceView.js index 7ce09b6..1c2574f 100644 --- a/WebCore/inspector/front-end/ResourceView.js +++ b/WebCore/inspector/front-end/ResourceView.js @@ -279,13 +279,19 @@ WebInspector.ResourceView.prototype = { _refreshRequestHeaders: function() { - this._refreshHeaders(WebInspector.UIString("Request Headers"), this.resource.sortedRequestHeaders, this.requestHeadersTreeElement); + var additionalRow = null; + if (typeof this.resource.webSocketRequestKey3 !== "undefined") + additionalRow = {header: "(Key3)", value: this.resource.webSocketRequestKey3}; + this._refreshHeaders(WebInspector.UIString("Request Headers"), this.resource.sortedRequestHeaders, additionalRow, this.requestHeadersTreeElement); this._refreshFormData(); }, _refreshResponseHeaders: function() { - this._refreshHeaders(WebInspector.UIString("Response Headers"), this.resource.sortedResponseHeaders, this.responseHeadersTreeElement); + var additionalRow = null; + if (typeof this.resource.webSocketChallengeResponse !== "undefined") + additionalRow = {header: "(Challenge Response)", value: this.resource.webSocketChallengeResponse}; + this._refreshHeaders(WebInspector.UIString("Response Headers"), this.resource.sortedResponseHeaders, additionalRow, this.responseHeadersTreeElement); }, _refreshHTTPInformation: function() @@ -316,7 +322,7 @@ WebInspector.ResourceView.prototype = { } }, - _refreshHeaders: function(title, headers, headersTreeElement) + _refreshHeaders: function(title, headers, additionalRow, headersTreeElement) { headersTreeElement.removeChildren(); @@ -333,6 +339,15 @@ WebInspector.ResourceView.prototype = { headerTreeElement.selectable = false; headersTreeElement.appendChild(headerTreeElement); } + + if (additionalRow) { + var title = "<div class=\"header-name\">" + additionalRow.header.escapeHTML() + ":</div>"; + title += "<div class=\"header-value source-code\">" + additionalRow.value.escapeHTML() + "</div>" + + var headerTreeElement = new TreeElement(title, null, false); + headerTreeElement.selectable = false; + headersTreeElement.appendChild(headerTreeElement); + } } } diff --git a/WebCore/inspector/front-end/ResourcesPanel.js b/WebCore/inspector/front-end/ResourcesPanel.js index 27df5cf..f329b1a 100644 --- a/WebCore/inspector/front-end/ResourcesPanel.js +++ b/WebCore/inspector/front-end/ResourcesPanel.js @@ -47,6 +47,8 @@ WebInspector.ResourcesPanel = function() this.filter(this.filterAllElement, false); this.graphsTreeElement.children[0].select(); this._resourceTrackingEnabled = false; + + this.sidebarElement.addEventListener("contextmenu", this._contextMenu.bind(this), true); } WebInspector.ResourcesPanel.prototype = { @@ -751,16 +753,23 @@ WebInspector.ResourcesPanel.prototype = { _toggleResourceTracking: function(optionalAlways) { + function callback(newState) { + if (newState) + WebInspector.panels.resources.resourceTrackingWasEnabled(); + else + WebInspector.panels.resources.resourceTrackingWasDisabled(); + } + if (this._resourceTrackingEnabled) { this.largerResourcesButton.visible = false; this.sortingSelectElement.visible = false; WebInspector.resources = {}; WebInspector.resourceURLMap = {}; - InspectorBackend.disableResourceTracking(true); + InspectorBackend.setResourceTrackingEnabled(false, true, callback); } else { this.largerResourcesButton.visible = true; this.sortingSelectElement.visible = true; - InspectorBackend.enableResourceTracking(!!optionalAlways); + InspectorBackend.setResourceTrackingEnabled(true, !!optionalAlways, callback); } }, @@ -857,7 +866,7 @@ WebInspector.ResourcesPanel.prototype = { var title = document.createElement("span"); title.className = "resource-timing-bar-title"; - if (i >= rows.length - 2) + if (total - rows[i].end < rows[i].start) title.style.right = (scale * (total - rows[i].end) + 3) + "px"; else title.style.left = (scale * rows[i].start + 3) + "px"; @@ -876,6 +885,36 @@ WebInspector.ResourcesPanel.prototype = { { WebInspector.Panel.prototype.hide.call(this); this._popoverHelper.hidePopup(); + }, + + _contextMenu: function(event) + { + // createBlobURL is enabled conditionally, do not expose resource export if it's not available. + if (typeof window.createBlobURL !== "function" || !Preferences.resourceExportEnabled) + return; + + var contextMenu = new WebInspector.ContextMenu(); + var resourceTreeItem = event.target.enclosingNodeOrSelfWithClass("resource-sidebar-tree-item"); + if (resourceTreeItem && resourceTreeItem.treeElement) { + var resource = resourceTreeItem.treeElement.representedObject; + contextMenu.appendItem(WebInspector.UIString("Export to HAR"), this._exportResource.bind(this, resource)); + } + contextMenu.appendItem(WebInspector.UIString("Export all to HAR"), this._exportAll.bind(this)); + contextMenu.show(event); + }, + + _exportAll: function() + { + var harArchive = { + log: (new WebInspector.HARLog()).build() + } + offerFileForDownload(JSON.stringify(harArchive)); + }, + + _exportResource: function(resource) + { + var har = (new WebInspector.HAREntry(resource)).build(); + offerFileForDownload(JSON.stringify(har)); } } diff --git a/WebCore/inspector/front-end/ScriptsPanel.js b/WebCore/inspector/front-end/ScriptsPanel.js index c5267f7..715339d 100644 --- a/WebCore/inspector/front-end/ScriptsPanel.js +++ b/WebCore/inspector/front-end/ScriptsPanel.js @@ -375,7 +375,7 @@ WebInspector.ScriptsPanel.prototype = { InjectedScriptAccess.get(callFrame.worldId).evaluateInCallFrame(callFrame.id, code, objectGroup, evalCallback); }, - debuggerPaused: function(callFrames) + debuggerPaused: function(details) { WebInspector.breakpointManager.removeOneTimeBreakpoint(); this._paused = true; @@ -384,8 +384,11 @@ WebInspector.ScriptsPanel.prototype = { this._updateDebuggerButtons(); - this.sidebarPanes.callstack.update(callFrames, this._sourceIDMap); - this.sidebarPanes.callstack.selectedCallFrame = callFrames[0]; + this.sidebarPanes.callstack.update(details.callFrames, this._sourceIDMap); + this.sidebarPanes.callstack.selectedCallFrame = details.callFrames[0]; + + if (details.status) + this.sidebarPanes.callstack.updateStatus(details.status); WebInspector.currentPanel = this; window.focus(); diff --git a/WebCore/inspector/front-end/Settings.js b/WebCore/inspector/front-end/Settings.js index 41d82f9..63f2641 100644 --- a/WebCore/inspector/front-end/Settings.js +++ b/WebCore/inspector/front-end/Settings.js @@ -44,7 +44,8 @@ var Preferences = { profilerAlwaysEnabled: false, auditsPanelEnabled: true, onlineDetectionEnabled: true, - domBreakpointsEnabled: false + domBreakpointsEnabled: false, + resourceExportEnabled: false } WebInspector.Settings = function(sessionScope) diff --git a/WebCore/inspector/front-end/StylesSidebarPane.js b/WebCore/inspector/front-end/StylesSidebarPane.js index 1dddde7..6aff37d 100644 --- a/WebCore/inspector/front-end/StylesSidebarPane.js +++ b/WebCore/inspector/front-end/StylesSidebarPane.js @@ -64,6 +64,7 @@ WebInspector.StylesSidebarPane = function(computedStylePane) this.titleElement.appendChild(this.settingsSelectElement); this._computedStylePane = computedStylePane; + this.element.addEventListener("contextmenu", this._contextMenuEventFired.bind(this), true); } // Taken from http://www.w3.org/TR/CSS21/propidx.html. @@ -105,6 +106,17 @@ WebInspector.StylesSidebarPane.prototype = { this.settingsSelectElement[2].selected = true; }, + _contextMenuEventFired: function(event) + { + var href = event.target.enclosingNodeOrSelfWithClass("webkit-html-resource-link") || event.target.enclosingNodeOrSelfWithClass("webkit-html-external-link"); + if (href) { + var contextMenu = new WebInspector.ContextMenu(); + var filled = WebInspector.panels.elements.populateHrefContextMenu(contextMenu, event, href); + if (filled) + contextMenu.show(event); + } + }, + update: function(node, editedSection, forceUpdate) { var refresh = false; diff --git a/WebCore/inspector/front-end/WebKit.qrc b/WebCore/inspector/front-end/WebKit.qrc index f90a9fe..c222f0e 100644 --- a/WebCore/inspector/front-end/WebKit.qrc +++ b/WebCore/inspector/front-end/WebKit.qrc @@ -266,5 +266,6 @@ <file>Images/warningOrangeDot.png</file> <file>Images/warningsErrors.png</file> <file>Images/whiteConnectorPoint.png</file> + <file alias="DebuggerScript.js">../../bindings/v8/DebuggerScript.js</file> </qresource> </RCC> diff --git a/WebCore/inspector/front-end/inspector.css b/WebCore/inspector/front-end/inspector.css index 4319816..6d8571c 100644 --- a/WebCore/inspector/front-end/inspector.css +++ b/WebCore/inspector/front-end/inspector.css @@ -1693,6 +1693,10 @@ li.editing .swatch, li.editing .enabled-button, li.editing-sub-part .delete-but color: gray; } +.pane > .body .placard + .info { + border-top: 1px solid gray +} + .pane.expanded > .body, .pane.expanded > .growbar { display: block; } @@ -2835,7 +2839,8 @@ button.enable-toggle-status-bar-item.toggled-on .glyph { } .resources-category-documents, .resources-category-stylesheets, .resources-category-images, -.resources-category-scripts, .resources-category-xhr, .resources-category-fonts, .resources-category-other { +.resources-category-scripts, .resources-category-xhr, .resources-category-fonts, +.resources-category-websockets, .resources-category-other { display: none; } @@ -2845,6 +2850,7 @@ button.enable-toggle-status-bar-item.toggled-on .glyph { .filter-all .resources-category-scripts, .filter-scripts .resources-category-scripts, .filter-all .resources-category-xhr, .filter-xhr .resources-category-xhr, .filter-all .resources-category-fonts, .filter-fonts .resources-category-fonts, +.filter-all .resources-category-websockets, .filter-websockets .resources-category-websockets, .filter-all .resources-category-other, .filter-other .resources-category-other, .resource-sidebar-tree-item.selected { display: list-item; @@ -2920,6 +2926,15 @@ button.enable-toggle-status-bar-item.toggled-on .glyph { -webkit-border-image: url(Images/timelineHollowPillYellow.png) 6 7 6 7; } +/* FIXME: Create bar images for WebSocket. */ +.resources-category-websockets .resources-graph-bar { + -webkit-border-image: url(Images/timelinePillGray.png) 6 7 6 7; +} + +.resources-category-websockets.resource-cached .resources-graph-bar { + -webkit-border-image: url(Images/timelineHollowPillGray.png) 6 7 6 7; +} + #resource-views { position: absolute; top: 23px; diff --git a/WebCore/inspector/front-end/inspector.js b/WebCore/inspector/front-end/inspector.js index 840745f..f6fa06b 100644 --- a/WebCore/inspector/front-end/inspector.js +++ b/WebCore/inspector/front-end/inspector.js @@ -403,45 +403,44 @@ var WebInspector = { } }, - get hoveredDOMNode() + highlightDOMNode: function(nodeId) { - return this._hoveredDOMNode; - }, + if ("_hideDOMNodeHighlightTimeout" in this) { + clearTimeout(this._hideDOMNodeHighlightTimeout); + delete this._hideDOMNodeHighlightTimeout; + } - set hoveredDOMNode(x) - { - if (this._hoveredDOMNode === x) + if (this._highlightedDOMNodeId === nodeId) return; - this._hoveredDOMNode = x; - - if (this._hoveredDOMNode) - this._updateHoverHighlightSoon(this.showingDOMNodeHighlight ? 50 : 500); + this._highlightedDOMNodeId = nodeId; + if (nodeId) + InspectorBackend.highlightDOMNode(nodeId); else - this._updateHoverHighlight(); + InspectorBackend.hideDOMNodeHighlight(); }, - _updateHoverHighlightSoon: function(delay) + highlightDOMNodeForTwoSeconds: function(nodeId) { - if ("_updateHoverHighlightTimeout" in this) - clearTimeout(this._updateHoverHighlightTimeout); - this._updateHoverHighlightTimeout = setTimeout(this._updateHoverHighlight.bind(this), delay); + this.highlightDOMNode(nodeId); + this._hideDOMNodeHighlightTimeout = setTimeout(this.highlightDOMNode.bind(this, 0), 2000); }, - _updateHoverHighlight: function() + wireElementWithDOMNode: function(element, nodeId) { - if ("_updateHoverHighlightTimeout" in this) { - clearTimeout(this._updateHoverHighlightTimeout); - delete this._updateHoverHighlightTimeout; - } + element.addEventListener("click", this._updateFocusedNode.bind(this, nodeId), false); + element.addEventListener("mouseover", this.highlightDOMNode.bind(this, nodeId), false); + element.addEventListener("mouseout", this.highlightDOMNode.bind(this, 0), false); + }, - if (this._hoveredDOMNode) { - InspectorBackend.highlightDOMNode(this._hoveredDOMNode.id); - this.showingDOMNodeHighlight = true; - } else { - InspectorBackend.hideDOMNodeHighlight(); - this.showingDOMNodeHighlight = false; - } + _updateFocusedNode: function(nodeId) + { + var node = WebInspector.domAgent.nodeForId(nodeId); + if (!node) + return; + + this.currentPanel = this.panels.elements; + this.panels.elements.focusedDOMNode = node; } } @@ -517,6 +516,7 @@ WebInspector.doLoadedDone = function() scripts: new WebInspector.ResourceCategory("scripts", WebInspector.UIString("Scripts"), "rgb(255,121,0)"), xhr: new WebInspector.ResourceCategory("xhr", WebInspector.UIString("XHR"), "rgb(231,231,10)"), fonts: new WebInspector.ResourceCategory("fonts", WebInspector.UIString("Fonts"), "rgb(255,82,62)"), + websocket: new WebInspector.ResourceCategory("websockets", WebInspector.UIString("WebSocket"), "rgb(186,186,186)"), // FIXME: Decide the color. other: new WebInspector.ResourceCategory("other", WebInspector.UIString("Other"), "rgb(186,186,186)") }; @@ -582,6 +582,16 @@ WebInspector.doLoadedDone = function() this.extensionServer.initExtensions(); + function populateInspectorState(inspectorState) + { + WebInspector.monitoringXHREnabled = inspectorState.monitoringXHREnabled; + if (inspectorState.resourceTrackingEnabled) + WebInspector.panels.resources.resourceTrackingWasEnabled(); + else + WebInspector.panels.resources.resourceTrackingWasDisabled(); + } + InspectorBackend.getInspectorState(populateInspectorState); + InspectorBackend.populateScriptObjects(); // As a DOMAgent method, this needs to happen after the frontend has loaded and the agent is available. @@ -720,22 +730,10 @@ WebInspector.disconnectFromBackend = function() InspectorFrontendHost.disconnectFromBackend(); } -WebInspector.documentMouseOver = function(event) -{ - if (event.target.tagName !== "A") - return; - - const anchor = event.target; - if (!anchor.hasStyleClass("webkit-html-resource-link")) - return; - if (anchor.href && anchor.href.indexOf("/data:") != -1) - return; -} - WebInspector.documentClick = function(event) { var anchor = event.target.enclosingNodeOrSelfWithNodeName("a"); - if (!anchor) + if (!anchor || anchor.target === "_blank") return; // Prevent the link from navigating, since we don't do any navigation by following links normally. @@ -788,6 +786,16 @@ WebInspector.documentClick = function(event) followLink(); } +WebInspector.openResource = function(resourceURL, inResourcesPanel) +{ + var resource = WebInspector.resourceForURL(resourceURL); + if (inResourcesPanel && resource) { + WebInspector.panels.resources.showResource(resource); + WebInspector.showPanel("resources"); + } else + InspectorBackend.openInInspectedWindow(resource ? resource.url : resourceURL); +} + WebInspector._registerShortcuts = function() { var shortcut = WebInspector.KeyboardShortcut; @@ -1223,6 +1231,8 @@ WebInspector.updateResource = function(payload) resource.requestMethod = payload.requestMethod; resource.requestFormData = payload.requestFormData; resource.documentURL = payload.documentURL; + if (typeof payload.webSocketRequestKey3 !== "undefined") + resource.webSocketRequestKey3 = payload.webSocketRequestKey3; if (resource.mainResource) this.mainResource = resource; @@ -1247,6 +1257,8 @@ WebInspector.updateResource = function(payload) resource.connectionReused = payload.connectionReused; resource.timing = payload.timing; resource.cached = payload.cached; + if (typeof payload.webSocketChallengeResponse !== "undefined") + resource.webSocketChallengeResponse = payload.webSocketChallengeResponse; } if (payload.didTypeChange) { @@ -1370,16 +1382,6 @@ WebInspector.updateNetworkState = function(isNowOnline) this.panels.storage.updateNetworkState(isNowOnline); } -WebInspector.resourceTrackingWasEnabled = function() -{ - this.panels.resources.resourceTrackingWasEnabled(); -} - -WebInspector.resourceTrackingWasDisabled = function() -{ - this.panels.resources.resourceTrackingWasDisabled(); -} - WebInspector.searchingForNodeWasEnabled = function() { this.panels.elements.searchingForNodeWasEnabled(); @@ -1390,16 +1392,6 @@ WebInspector.searchingForNodeWasDisabled = function() this.panels.elements.searchingForNodeWasDisabled(); } -WebInspector.monitoringXHRWasEnabled = function() -{ - this.monitoringXHREnabled = true; -} - -WebInspector.monitoringXHRWasDisabled = function() -{ - this.monitoringXHREnabled = false; -} - WebInspector.attachDebuggerWhenShown = function() { this.panels.scripts.attachDebuggerWhenShown(); @@ -1447,7 +1439,7 @@ WebInspector.failedToParseScriptSource = function(sourceURL, source, startingLin WebInspector.pausedScript = function(details) { - this.panels.scripts.debuggerPaused(details.callFrames); + this.panels.scripts.debuggerPaused(details); InspectorFrontendHost.bringToFront(); } @@ -1474,7 +1466,7 @@ WebInspector.reset = function() this.resourceURLMap = {}; this.cookieDomains = {}; this.applicationCacheDomains = {}; - this.hoveredDOMNode = null; + this.highlightDOMNode(0); delete this.mainResource; @@ -1683,13 +1675,8 @@ WebInspector.drawLoadingPieChart = function(canvas, percent) { WebInspector.updateFocusedNode = function(nodeId) { - var node = WebInspector.domAgent.nodeForId(nodeId); - if (!node) - // FIXME: Should we deselect if null is passed in? - return; - - this.currentPanel = this.panels.elements; - this.panels.elements.focusedDOMNode = node; + this._updateFocusedNode(nodeId); + this.highlightDOMNodeForTwoSeconds(nodeId); } WebInspector.displayNameForURL = function(url) @@ -1804,7 +1791,6 @@ WebInspector.linkifyURLAsNode = function(url, linkText, classes, isExternal, too a.title = url; else if (typeof tooltipText !== "string" || tooltipText.length) a.title = tooltipText; - a.target = "_blank"; a.textContent = linkText; return a; @@ -1828,6 +1814,29 @@ WebInspector.linkifyResourceAsNode = function(url, preferredPanel, lineNumber, c return node; } +WebInspector.resourceURLForRelatedNode = function(node, url) +{ + if (!url || url.indexOf("://") > 0) + return url; + + for (var frameOwnerCandidate = node; frameOwnerCandidate; frameOwnerCandidate = frameOwnerCandidate.parentNode) { + if (frameOwnerCandidate.documentURL) { + var result = WebInspector.completeURL(frameOwnerCandidate.documentURL, url); + if (result) + return result; + break; + } + } + + // documentURL not found or has bad value + for (var resourceURL in WebInspector.resourceURLMap) { + var match = resourceURL.match(WebInspector.URLRegExp); + if (match && match[4] === url) + return resourceURL; + } + return url; +}, + WebInspector.completeURL = function(baseURL, href) { var match = baseURL.match(WebInspector.URLRegExp); @@ -1850,7 +1859,6 @@ WebInspector.addMainEventListeners = function(doc) doc.defaultView.addEventListener("focus", this.windowFocused.bind(this), false); doc.defaultView.addEventListener("blur", this.windowBlurred.bind(this), false); doc.addEventListener("click", this.documentClick.bind(this), true); - doc.addEventListener("mouseover", this.documentMouseOver.bind(this), true); } WebInspector._searchFieldManualFocus = function(event) @@ -2008,6 +2016,11 @@ WebInspector.UIString = function(string) return String.vsprintf(string, Array.prototype.slice.call(arguments, 1)); } +WebInspector.formatLocalized = function(format, substitutions, formatters, initialValue, append) +{ + return String.format(WebInspector.UIString(format), substitutions, formatters, initialValue, append); +} + WebInspector.isMac = function() { if (!("_isMac" in this)) diff --git a/WebCore/inspector/front-end/treeoutline.js b/WebCore/inspector/front-end/treeoutline.js index 5891401..c2a3fe7 100644 --- a/WebCore/inspector/front-end/treeoutline.js +++ b/WebCore/inspector/front-end/treeoutline.js @@ -651,6 +651,7 @@ TreeElement.treeElementToggled = function(event) else element.treeElement.expand(); } + event.stopPropagation(); } TreeElement.treeElementDoubleClicked = function(event) diff --git a/WebCore/inspector/front-end/utilities.js b/WebCore/inspector/front-end/utilities.js index e8adff6..5e41da6 100644 --- a/WebCore/inspector/front-end/utilities.js +++ b/WebCore/inspector/front-end/utilities.js @@ -984,3 +984,12 @@ function createSearchRegex(query) } return new RegExp(regex, "i"); } + +function offerFileForDownload(contents) +{ + var builder = new BlobBuilder(); + builder.append(contents); + var blob = builder.getBlob("application/octet-stream"); + var url = window.createBlobURL(blob); + window.open(url); +} |