From 07a51fb3103ac29cbc6e309e6506246d12a94f64 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 16 Jan 2012 17:17:36 -0800 Subject: [PATCH 01/90] Displaying lines and a basic cursor --- spec/atom/buffer-spec.coffee | 48 ++++----- spec/atom/editor-spec.coffee | 168 ++--------------------------- spec/fixtures/sample.js | 13 +++ spec/stdlib/template-spec.coffee | 1 + src/atom/buffer.coffee | 44 +++----- src/atom/cursor.coffee | 14 +++ src/atom/editor.coffee | 93 ++++------------ src/atom/root-view.coffee | 1 - src/stdlib/template/builder.coffee | 1 + static/atom.css | 9 +- 10 files changed, 103 insertions(+), 289 deletions(-) create mode 100644 spec/fixtures/sample.js create mode 100644 src/atom/cursor.coffee diff --git a/spec/atom/buffer-spec.coffee b/spec/atom/buffer-spec.coffee index 6b7a8f887..69bd03685 100644 --- a/spec/atom/buffer-spec.coffee +++ b/spec/atom/buffer-spec.coffee @@ -1,30 +1,42 @@ Buffer = require 'buffer' fs = require 'fs' -describe 'Buffer', -> +fdescribe 'Buffer', -> + [filePath, fileContents, buffer] = [] + + beforeEach -> + filePath = require.resolve('fixtures/sample.js') + fileContents = fs.read(filePath) + buffer = new Buffer(filePath) + describe 'constructor', -> - describe "when given a url", -> - describe "when a file exists for the url", -> + describe "when given a path", -> + describe "when a file exists for the path", -> it "loads the contents of that file", -> filePath = require.resolve 'fixtures/sample.txt' - buffer = new Buffer filePath + buffer = new Buffer(filePath) expect(buffer.getText()).toBe fs.read(filePath) - describe "when no file exists for the url", -> + describe "when no file exists for the path", -> it "creates an empty buffer", -> filePath = "does-not-exist.txt" expect(fs.exists(filePath)).toBeFalsy() - buffer = new Buffer filePath + buffer = new Buffer(filePath) expect(buffer.getText()).toBe "" - describe "when no url is given", -> + describe "when no path is given", -> it "creates an empty buffer", -> - buffer = new Buffer null + buffer = new Buffer expect(buffer.getText()).toBe "" + describe ".getLines()", -> + it "returns an array of lines in the text contents", -> + expect(buffer.getLines().length).toBe fileContents.split("\n").length + expect(buffer.getLines().join('\n')).toBe fileContents + describe ".save()", -> - describe "when the buffer has a url", -> + describe "when the buffer has a path", -> filePath = null beforeEach -> @@ -34,28 +46,14 @@ describe 'Buffer', -> afterEach -> fs.remove filePath - it "saves the contents of the buffer to the url", -> + it "saves the contents of the buffer to the path", -> buffer = new Buffer filePath buffer.setText 'Buffer contents!' buffer.save() expect(fs.read(filePath)).toEqual 'Buffer contents!' - describe "when the buffer no url", -> + describe "when the buffer no path", -> it "throw an exception", -> buffer = new Buffer expect(-> buffer.save()).toThrow() - describe ".getMode()", -> - describe "when given a url", -> - it "sets 'mode' based on the file extension", -> - buffer = new Buffer 'something.js' - expect(buffer.getMode().name).toBe 'javascript' - - buffer = new Buffer 'something' - expect(buffer.getMode().name).toBe 'text' - - describe "when no url is given", -> - it "sets 'mode' to text mode", -> - buffer = new Buffer null - expect(buffer.getMode().name).toBe 'text' - diff --git a/spec/atom/editor-spec.coffee b/spec/atom/editor-spec.coffee index 2b5f4455e..6aa769896 100644 --- a/spec/atom/editor-spec.coffee +++ b/spec/atom/editor-spec.coffee @@ -1,173 +1,21 @@ Buffer = require 'buffer' Editor = require 'editor' $ = require 'jquery' -ck = require 'coffeekup' fs = require 'fs' -describe "Editor", -> - mainDiv = null +fdescribe "Editor", -> + buffer = null editor = null - filePath = null - tempFilePath = null beforeEach -> - filePath = require.resolve 'fixtures/sample.txt' - tempFilePath = '/tmp/temp.txt' + buffer = new Buffer(require.resolve('fixtures/sample.js')) editor = Editor.build() - afterEach -> - fs.remove tempFilePath - editor.destroy() - - describe "initialize", -> - it "has a buffer", -> - expect(editor.buffer).toBeDefined() - - describe '.set/getPosition', -> - it "gets the cursor location / moves the cursor location", -> - editor.buffer.setText("012345") - expect(editor.getPosition()).toEqual {column: 6, row: 0} - editor.setPosition(column: 2, row: 0) - expect(editor.getPosition()).toEqual {column: 2, row: 0} - - describe 'destroy', -> - it 'destroys the ace editor', -> - spyOn(editor.aceEditor, 'destroy').andCallThrough() - editor.destroy() - expect(editor.aceEditor.destroy).toHaveBeenCalled() - - describe "setBuffer(buffer)", -> - it "sets the document on the aceSession", -> - buffer = new Buffer filePath - editor.setBuffer buffer - - fileContents = fs.read(filePath) - expect(editor.getAceSession().getValue()).toBe fileContents - - it "restores the ace edit session for a previously assigned buffer", -> - buffer = new Buffer filePath - editor.setBuffer buffer - - aceSession = editor.getAceSession() - - editor.setBuffer new Buffer(tempFilePath) - expect(editor.getAceSession()).not.toBe(aceSession) - + describe ".setBuffer", -> + it "creates a pre element for each line in the buffer", -> editor.setBuffer(buffer) - expect(editor.getAceSession()).toBe aceSession + expect(editor.lines.find('pre').length).toEqual(buffer.numLines()) - it "sets the language mode based on the file extension", -> - buffer = new Buffer "something.js" - editor.setBuffer buffer - - expect(editor.getAceSession().getMode().name).toBe 'javascript' - - describe "when the text is changed via the ace editor", -> - it "updates the buffer text", -> - buffer = new Buffer(filePath) - editor.setBuffer(buffer) - expect(buffer.getText()).not.toMatch /^.ooo/ - editor.getAceSession().insert {row: 0, column: 1}, 'ooo' - expect(buffer.getText()).toMatch /^.ooo/ - - describe ".save()", -> - it "is triggered by the 'save' event", -> - spyOn(editor, 'save') - editor.trigger('save') - expect(editor.save).toHaveBeenCalled() - - describe "when the current buffer has a url", -> - beforeEach -> - buffer = new Buffer(tempFilePath) - editor.setBuffer(buffer) - - it "saves the current buffer to disk", -> - editor.buffer.setText 'Edited buffer!' - expect(fs.exists(tempFilePath)).toBeFalsy() - - editor.save() - - expect(fs.exists(tempFilePath)).toBeTruthy() - expect(fs.read(tempFilePath)).toBe 'Edited buffer!' - - describe "when the current buffer has no url", -> - selectedFilePath = null - beforeEach -> - expect(editor.buffer.url).toBeUndefined() - editor.buffer.setText 'Save me to a new url' - spyOn(atom.native, 'savePanel').andCallFake -> selectedFilePath - - it "presents a 'save as' dialog", -> - editor.save() - expect(atom.native.savePanel).toHaveBeenCalled() - - describe "when a url is chosen", -> - it "saves the buffer to the chosen url", -> - selectedFilePath = '/tmp/temp.txt' - - editor.save() - - expect(fs.exists(selectedFilePath)).toBeTruthy() - expect(fs.read(selectedFilePath)).toBe 'Save me to a new url' - - describe "when dialog is cancelled", -> - it "does not save the buffer", -> - selectedFilePath = null - - editor.save() - - expect(fs.exists(selectedFilePath)).toBeFalsy() - - describe "key event handling", -> - handler = null - returnValue = null - - beforeEach -> - handler = - handleKeyEvent: jasmine.createSpy('handleKeyEvent').andCallFake -> - returnValue - - describe "when onCommandKey is called on the aceEditor (triggered by a keydown event on the textarea)", -> - event = null - - beforeEach -> - event = keydownEvent 'x' - spyOn(event, 'stopPropagation') - - describe "when no key event handler has been assigned", -> - beforeEach -> - expect(editor.keyEventHandler).toBeNull() - - it "handles the event without crashing", -> - editor.aceEditor.onCommandKey event, 0, event.which - - describe "when a key event handler has been assigned", -> - beforeEach -> - editor.keyEventHandler = handler - - it "asks the key event handler to handle the event", -> - editor.aceEditor.onCommandKey event, 0, event.which - expect(handler.handleKeyEvent).toHaveBeenCalled() - - describe "if the atom key event handler returns true, indicating that it did not handle the event", -> - beforeEach -> - returnValue = true - - it "does not stop the propagation of the event, allowing Ace to handle it as normal", -> - editor.aceEditor.onCommandKey event, 0, event.which - expect(event.stopPropagation).not.toHaveBeenCalled() - - describe "if the atom key event handler returns false, indicating that it handled the event", -> - beforeEach -> - returnValue = false - - it "stops propagation of the event, so Ace does not attempt to handle it", -> - editor.aceEditor.onCommandKey event, 0, event.which - expect(event.stopPropagation).toHaveBeenCalled() - - describe "when onTextInput is called on the aceEditor (triggered by an input event)", -> - it "does not call handleKeyEvent on the key event handler, because there is no event", -> - editor.keyEventHandler = handler - editor.aceEditor.onTextInput("x", false) - expect(handler.handleKeyEvent).not.toHaveBeenCalled() + it "sets the cursor to the beginning of the file", -> + expect(editor.getPosition()).toEqual(row: 0, col: 0) diff --git a/spec/fixtures/sample.js b/spec/fixtures/sample.js new file mode 100644 index 000000000..231ea9751 --- /dev/null +++ b/spec/fixtures/sample.js @@ -0,0 +1,13 @@ +var quicksort = function () { + var sort = function(items) { + if (items.length <= 1) return items; + var pivot = items.shift(), current, left = [], right = []; + while(items.length > 0) { + current = items.shift(); + current < pivot ? left.push(current) : right.push(current); + } + return sort(left).concat(pivot).concat(sort(right)); + }; + return sort(Array.apply(this, arguments)); +}; + diff --git a/spec/stdlib/template-spec.coffee b/spec/stdlib/template-spec.coffee index 35649b5bb..965d2b875 100644 --- a/spec/stdlib/template-spec.coffee +++ b/spec/stdlib/template-spec.coffee @@ -52,6 +52,7 @@ describe "Template", -> it "constructs and wires outlets for subviews", -> expect(view.subview).toExist() expect(view.subview.find('h2:contains(Subview)')).toExist() + expect(view.subview.parentView).toBe view it "does not overwrite outlets on the superview with outlets from the subviews", -> expect(view.header).toMatchSelector "h1" diff --git a/src/atom/buffer.coffee b/src/atom/buffer.coffee index b334a85c4..5b85a2967 100644 --- a/src/atom/buffer.coffee +++ b/src/atom/buffer.coffee @@ -3,45 +3,27 @@ fs = require 'fs' module.exports = class Buffer - aceDocument: null - url: null + lines: null - constructor: (@url) -> - text = if @url and fs.exists(@url) - fs.read(@url) + constructor: (@path) -> + if @path and fs.exists(@path) + @setText(fs.read(@path)) else - "" - - @aceDocument = new Document text + @setText('') getText: -> - @aceDocument.getValue() + @lines.join('\n') setText: (text) -> - @aceDocument.setValue text + @lines = text.split('\n') - getMode: -> - return @mode if @mode + getLines: -> + @lines - extension = if @url then @url.split('/').pop().split('.').pop() else null - modeName = switch extension - when 'js' then 'javascript' - when 'coffee' then 'coffee' - when 'rb', 'ru' then 'ruby' - when 'c', 'h', 'cpp' then 'c_cpp' - when 'html', 'htm' then 'html' - when 'css' then 'css' - - else 'text' - - @mode = new (require("ace/mode/#{modeName}").Mode) - @mode.name = modeName - @mode + numLines: -> + @getLines().length save: -> - if not @url then throw new Error("Tried to save buffer with no url") - fs.write @url, @getText() - - getLine: (row) -> - @aceDocument.getLine(row) + if not @path then throw new Error("Tried to save buffer with no url") + fs.write @path, @getText() diff --git a/src/atom/cursor.coffee b/src/atom/cursor.coffee new file mode 100644 index 000000000..28557ab22 --- /dev/null +++ b/src/atom/cursor.coffee @@ -0,0 +1,14 @@ +Template = require 'template' + +module.exports = +class Cursor extends Template + content: -> + @pre class: 'cursor', style: 'position: absolute;', ' ' + + viewProperties: + setPosition: (@position) -> + @css(@parentView.toPixelPosition(position)) + + getPosition: -> + @position + diff --git a/src/atom/editor.coffee b/src/atom/editor.coffee index 378de8835..24176621a 100644 --- a/src/atom/editor.coffee +++ b/src/atom/editor.coffee @@ -1,93 +1,44 @@ Template = require 'template' Buffer = require 'buffer' -{EditSession} = require 'ace/edit_session' -ace = require 'ace/ace' +Cursor = require 'cursor' $ = require 'jquery' module.exports = class Editor extends Template content: -> - @div class: 'editor' + @div class: 'editor', => + @div outlet: 'lines' + @subview 'cursor', Cursor.build() viewProperties: - aceEditor: null buffer: null - keyEventHandler: null initialize: () -> - @aceSessions = {} - @buildAceEditor() @setBuffer(new Buffer) - @on 'save', => @save() - - shutdown: -> - @destroy() - - destroy: -> - @aceEditor.destroy() setBuffer: (@buffer) -> - @aceEditor.setSession @getAceSessionForBuffer(buffer) + @lines.empty() + for line in @buffer.getLines() + @lines.append "
#{line}
" + @setPosition(row: 0, col: 0) - getAceSessionForBuffer: (buffer) -> - @aceSessions[@buffer.url] ?= new EditSession(@buffer.aceDocument, @buffer.getMode()) - - buildAceEditor: -> - @aceEditor = ace.edit this[0] - @aceEditor.setTheme(require "ace/theme/twilight") - @aceEditor.setKeyboardHandler - handleKeyboard: (data, hashId, keyString, keyCode, event) => - if event and @keyEventHandler and @keyEventHandler.handleKeyEvent(event) == false - { command: { exec: -> }} - else - null - - getAceSession: -> - @aceEditor.getSession() - - focus: -> - @aceEditor.focus() - - save: -> - if @buffer.url - @buffer.save() - else if url = atom.native.savePanel() - @buffer.url = url - @buffer.save() + setPosition: (position) -> + @cursor.setPosition(position) getPosition: -> - @getAceSession().getSelection().getCursor() + @cursor.getPosition() - setPosition: ({column, row}) -> - @aceEditor.navigateTo(row, column) + toPixelPosition: ({row, col}) -> + { top: row * @lineHeight(), left: col * @charWidth() } - selectToPosition: (position) -> - if @aceEditor.selection.isEmpty() - { row, column } = @getPosition() - @aceEditor.selection.setSelectionAnchor(row, column) - @aceEditor.moveCursorToPosition(position) + lineHeight: -> + @lines.css('line-height') - delete: -> - @getAceSession().remove(@aceEditor.getSelectionRange()) + charWidth: -> + return @cachedCharWidth if @cachedCharWidth + fragment = $('
x
') + @lines.append(fragment) + @cachedCharWidth = fragment.width() + fragment.remove() + @cachedCharWidth - getLineText: (row) -> - @buffer.getLine(row) - - getRow: -> - { row } = @getPosition() - row - - deleteChar: -> - @aceEditor.remove 'right' - - selectLine: -> - @aceEditor.selection.selectLine() - - deleteLine: -> - @aceEditor.removeLines() - - moveLeft: -> - @aceEditor.navigateLeft() - - moveUp: -> - @aceEditor.navigateUp() diff --git a/src/atom/root-view.coffee b/src/atom/root-view.coffee index 65c5543f1..7f4d2f2ac 100644 --- a/src/atom/root-view.coffee +++ b/src/atom/root-view.coffee @@ -23,7 +23,6 @@ class RootView extends Template globalKeymap: null initialize: ({url}) -> - new VimMode(@editor) @editor.keyEventHandler = atom.globalKeymap @createProject(url) diff --git a/src/stdlib/template/builder.coffee b/src/stdlib/template/builder.coffee index 2e50e678f..657f46bc8 100644 --- a/src/stdlib/template/builder.coffee +++ b/src/stdlib/template/builder.coffee @@ -48,6 +48,7 @@ class Builder @tag 'div', id: subviewId @postProcessingFns.push (view) -> view[outletName] = subview + subview.parentView = view view.find("div##{subviewId}").replaceWith(subview) elementIsVoid: (name) -> diff --git a/static/atom.css b/static/atom.css index 62d57e7a4..7774f6216 100644 --- a/static/atom.css +++ b/static/atom.css @@ -36,8 +36,15 @@ body { background-color: blue; } -.ace_editor { +.editor { font: 18px Inconsolata, Monaco, Courier !important; width: 100%; height: 100%; + background: #333; + color: white; +} + +.editor .cursor { + background: #9dff9d; + opacity: .3; } From 7d7b119927a70b6f2421019f18c11c1d6464ba81 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 16 Jan 2012 19:23:27 -0800 Subject: [PATCH 02/90] WIP --- spec/atom/buffer-spec.coffee | 2 +- spec/atom/editor-spec.coffee | 11 +++++++++- spec/spec-helper.coffee | 6 +++++- src/atom/cursor.coffee | 6 +++--- src/atom/editor.coffee | 41 +++++++++++++++++++++++++++++++----- src/atom/root-view.coffee | 1 + static/atom.css | 12 ----------- static/editor.css | 18 ++++++++++++++++ 8 files changed, 74 insertions(+), 23 deletions(-) create mode 100644 static/editor.css diff --git a/spec/atom/buffer-spec.coffee b/spec/atom/buffer-spec.coffee index 69bd03685..9cf2b1294 100644 --- a/spec/atom/buffer-spec.coffee +++ b/spec/atom/buffer-spec.coffee @@ -1,7 +1,7 @@ Buffer = require 'buffer' fs = require 'fs' -fdescribe 'Buffer', -> +describe 'Buffer', -> [filePath, fileContents, buffer] = [] beforeEach -> diff --git a/spec/atom/editor-spec.coffee b/spec/atom/editor-spec.coffee index 6aa769896..f7b466a7b 100644 --- a/spec/atom/editor-spec.coffee +++ b/spec/atom/editor-spec.coffee @@ -3,7 +3,7 @@ Editor = require 'editor' $ = require 'jquery' fs = require 'fs' -fdescribe "Editor", -> +describe "Editor", -> buffer = null editor = null @@ -19,3 +19,12 @@ fdescribe "Editor", -> it "sets the cursor to the beginning of the file", -> expect(editor.getPosition()).toEqual(row: 0, col: 0) + fdescribe ".setPosition({row, col})", -> + it "moves the cursor to cover the character at the given row and column", -> + editor.attachToDom() + editor.setBuffer(buffer) + editor.setPosition(row: 2, col: 2) + + expect(editor.cursor.position().top).toBe(2 * editor.lineHeight()) + expect(editor.cursor.position().left).toBe(2 * editor.charWidth()) + diff --git a/spec/spec-helper.coffee b/spec/spec-helper.coffee index cde8a86d8..ac45f95ca 100644 --- a/spec/spec-helper.coffee +++ b/spec/spec-helper.coffee @@ -7,7 +7,7 @@ BindingSet = require 'binding-set' afterEach -> (new Native).resetMainMenu() atom.globalKeymap.reset() - $('#jasmine-content').empty() + # $('#jasmine-content').empty() window.atom = new (require 'app') @@ -32,3 +32,7 @@ $.fn.resultOfTrigger = (type) -> $.fn.enableKeymap = -> @on 'keydown', (e) => atom.globalKeymap.handleKeyEvent(e) +$.fn.attachToDom = -> + console.log this + $('#jasmine-content').append(this) + diff --git a/src/atom/cursor.coffee b/src/atom/cursor.coffee index 28557ab22..11e36f916 100644 --- a/src/atom/cursor.coffee +++ b/src/atom/cursor.coffee @@ -6,9 +6,9 @@ class Cursor extends Template @pre class: 'cursor', style: 'position: absolute;', ' ' viewProperties: - setPosition: (@position) -> - @css(@parentView.toPixelPosition(position)) + setPosition: (@_position) -> + @css(@parentView.toPixelPosition(@_position)) getPosition: -> - @position + @_position diff --git a/src/atom/editor.coffee b/src/atom/editor.coffee index 24176621a..09409f75d 100644 --- a/src/atom/editor.coffee +++ b/src/atom/editor.coffee @@ -2,25 +2,51 @@ Template = require 'template' Buffer = require 'buffer' Cursor = require 'cursor' $ = require 'jquery' +_ = require 'underscore' module.exports = class Editor extends Template content: -> @div class: 'editor', => - @div outlet: 'lines' + # @link rel: 'stylesheet', href: "#{require.resolve('editor.css')}?#{(new Date).getTime()}" + @style @div outlet: 'lines' @subview 'cursor', Cursor.build() viewProperties: buffer: null initialize: () -> + $('head').append """ + + + """ + @setBuffer(new Buffer) setBuffer: (@buffer) -> @lines.empty() for line in @buffer.getLines() @lines.append "
#{line}
" - @setPosition(row: 0, col: 0) + _.defer => @setPosition(row: 3, col: 4) setPosition: (position) -> @cursor.setPosition(position) @@ -32,13 +58,18 @@ class Editor extends Template { top: row * @lineHeight(), left: col * @charWidth() } lineHeight: -> - @lines.css('line-height') + @calculateDimensions() unless @cachedLineHeight + @cachedLineHeight charWidth: -> - return @cachedCharWidth if @cachedCharWidth + @calculateDimensions() unless @cachedCharWidth + @cachedCharWidth + + calculateDimensions: -> fragment = $('
x
') @lines.append(fragment) @cachedCharWidth = fragment.width() + @cachedLineHeight = fragment.outerHeight() + console.log @cachedLineHeight fragment.remove() - @cachedCharWidth diff --git a/src/atom/root-view.coffee b/src/atom/root-view.coffee index 7f4d2f2ac..08d9ffe31 100644 --- a/src/atom/root-view.coffee +++ b/src/atom/root-view.coffee @@ -15,6 +15,7 @@ class RootView extends Template content: -> @div id: 'app-horizontal', => @link rel: 'stylesheet', href: "#{require.resolve('atom.css')}?#{(new Date).getTime()}" + @link rel: 'stylesheet', href: "#{require.resolve('editor.css')}?#{(new Date).getTime()}" @div id: 'app-vertical', outlet: 'vertical', => @div id: 'main', outlet: 'main', => @subview 'editor', Editor.build() diff --git a/static/atom.css b/static/atom.css index 7774f6216..9944544af 100644 --- a/static/atom.css +++ b/static/atom.css @@ -36,15 +36,3 @@ body { background-color: blue; } -.editor { - font: 18px Inconsolata, Monaco, Courier !important; - width: 100%; - height: 100%; - background: #333; - color: white; -} - -.editor .cursor { - background: #9dff9d; - opacity: .3; -} diff --git a/static/editor.css b/static/editor.css new file mode 100644 index 000000000..643c38457 --- /dev/null +++ b/static/editor.css @@ -0,0 +1,18 @@ +.editor { + font-family: Inconsolata, Monaco, Courier; + font: 18px Inconsolata, Monaco, Courier !important; + position: relative; + width: 100%; + height: 100%; + background: #333; + color: white; +} + +.editor pre { + margin: 0; +} + +.editor .cursor { + background: #9dff9d; + opacity: .3; +} From bde3a9f5f521d083f7d4bc0a134462f9c15888b0 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 16 Jan 2012 20:17:07 -0800 Subject: [PATCH 03/90] Use requireStylesheet for editor stylesheet. --- spec/spec-helper.coffee | 2 +- src/atom/editor.coffee | 26 +------------------------- 2 files changed, 2 insertions(+), 26 deletions(-) diff --git a/spec/spec-helper.coffee b/spec/spec-helper.coffee index 7cd40c0a3..d842c62ba 100644 --- a/spec/spec-helper.coffee +++ b/spec/spec-helper.coffee @@ -9,7 +9,7 @@ afterEach -> (new Native).resetMainMenu() atom.globalKeymap.reset() # $('#jasmine-content').empty() - $('head style[path]').remove() + # $('head style[path]').remove() window.atom = new (require 'app') diff --git a/src/atom/editor.coffee b/src/atom/editor.coffee index 09409f75d..287f49f40 100644 --- a/src/atom/editor.coffee +++ b/src/atom/editor.coffee @@ -8,7 +8,6 @@ module.exports = class Editor extends Template content: -> @div class: 'editor', => - # @link rel: 'stylesheet', href: "#{require.resolve('editor.css')}?#{(new Date).getTime()}" @style @div outlet: 'lines' @subview 'cursor', Cursor.build() @@ -16,30 +15,7 @@ class Editor extends Template buffer: null initialize: () -> - $('head').append """ - - - """ - + requireStylesheet 'editor.css' @setBuffer(new Buffer) setBuffer: (@buffer) -> From 5198a88cce2d70395d0d1322b9f971c0ad349f18 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 16 Jan 2012 22:11:38 -0800 Subject: [PATCH 04/90] Half-baked: Have a lines reasonably looking cursor working. A bunch of tests are failing but you can load a buffer and display its lines. --- index.html | 1 + spec/atom/editor-spec.coffee | 2 +- spec/spec-helper.coffee | 3 +- src/atom/editor.coffee | 4 +-- src/atom/root-view.coffee | 2 -- src/atom/window.coffee | 4 +++ static/atom.css | 1 - static/editor.css | 1 - static/reset.css | 65 ++++++++++++++++++++++++++++++++++++ 9 files changed, 73 insertions(+), 10 deletions(-) create mode 100755 static/reset.css diff --git a/index.html b/index.html index f29fa4ddc..51b1524b3 100644 --- a/index.html +++ b/index.html @@ -1,5 +1,6 @@ + + + +
+
+
+
+ +
+
+
+
+
+
+
+
+ + diff --git a/frameworks/WebCore.framework/Versions/A/Resources/inspector/inspector.js b/frameworks/WebCore.framework/Versions/A/Resources/inspector/inspector.js new file mode 100644 index 000000000..0094b6d09 --- /dev/null +++ b/frameworks/WebCore.framework/Versions/A/Resources/inspector/inspector.js @@ -0,0 +1,70987 @@ +/* utilities.js */ + +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Contains diff method based on Javascript Diff Algorithm By John Resig + * http://ejohn.org/files/jsdiff.js (released under the MIT license). + */ + +/** + * @param {string=} direction + */ +Node.prototype.rangeOfWord = function(offset, stopCharacters, stayWithinNode, direction) +{ + var startNode; + var startOffset = 0; + var endNode; + var endOffset = 0; + + if (!stayWithinNode) + stayWithinNode = this; + + if (!direction || direction === "backward" || direction === "both") { + var node = this; + while (node) { + if (node === stayWithinNode) { + if (!startNode) + startNode = stayWithinNode; + break; + } + + if (node.nodeType === Node.TEXT_NODE) { + var start = (node === this ? (offset - 1) : (node.nodeValue.length - 1)); + for (var i = start; i >= 0; --i) { + if (stopCharacters.indexOf(node.nodeValue[i]) !== -1) { + startNode = node; + startOffset = i + 1; + break; + } + } + } + + if (startNode) + break; + + node = node.traversePreviousNode(stayWithinNode); + } + + if (!startNode) { + startNode = stayWithinNode; + startOffset = 0; + } + } else { + startNode = this; + startOffset = offset; + } + + if (!direction || direction === "forward" || direction === "both") { + node = this; + while (node) { + if (node === stayWithinNode) { + if (!endNode) + endNode = stayWithinNode; + break; + } + + if (node.nodeType === Node.TEXT_NODE) { + var start = (node === this ? offset : 0); + for (var i = start; i < node.nodeValue.length; ++i) { + if (stopCharacters.indexOf(node.nodeValue[i]) !== -1) { + endNode = node; + endOffset = i; + break; + } + } + } + + if (endNode) + break; + + node = node.traverseNextNode(stayWithinNode); + } + + if (!endNode) { + endNode = stayWithinNode; + endOffset = stayWithinNode.nodeType === Node.TEXT_NODE ? stayWithinNode.nodeValue.length : stayWithinNode.childNodes.length; + } + } else { + endNode = this; + endOffset = offset; + } + + var result = this.ownerDocument.createRange(); + result.setStart(startNode, startOffset); + result.setEnd(endNode, endOffset); + + return result; +} + +Node.prototype.traverseNextTextNode = function(stayWithin) +{ + var node = this.traverseNextNode(stayWithin); + if (!node) + return; + + while (node && node.nodeType !== Node.TEXT_NODE) + node = node.traverseNextNode(stayWithin); + + return node; +} + +Node.prototype.rangeBoundaryForOffset = function(offset) +{ + var node = this.traverseNextTextNode(this); + while (node && offset > node.nodeValue.length) { + offset -= node.nodeValue.length; + node = node.traverseNextTextNode(this); + } + if (!node) + return { container: this, offset: 0 }; + return { container: node, offset: offset }; +} + +Element.prototype.removeStyleClass = function(className) +{ + this.classList.remove(className); +} + +Element.prototype.removeMatchingStyleClasses = function(classNameRegex) +{ + var regex = new RegExp("(^|\\s+)" + classNameRegex + "($|\\s+)"); + if (regex.test(this.className)) + this.className = this.className.replace(regex, " "); +} + +Element.prototype.addStyleClass = function(className) +{ + this.classList.add(className); +} + +Element.prototype.hasStyleClass = function(className) +{ + return this.classList.contains(className); +} + +Element.prototype.positionAt = function(x, y) +{ + this.style.left = x + "px"; + this.style.top = y + "px"; +} + +Element.prototype.pruneEmptyTextNodes = function() +{ + var sibling = this.firstChild; + while (sibling) { + var nextSibling = sibling.nextSibling; + if (sibling.nodeType === this.TEXT_NODE && sibling.nodeValue === "") + this.removeChild(sibling); + sibling = nextSibling; + } +} + +Element.prototype.isScrolledToBottom = function() +{ + // This code works only for 0-width border + return this.scrollTop + this.clientHeight === this.scrollHeight; +} + +Node.prototype.enclosingNodeOrSelfWithNodeNameInArray = function(nameArray) +{ + for (var node = this; node && node !== this.ownerDocument; node = node.parentNode) + for (var i = 0; i < nameArray.length; ++i) + if (node.nodeName.toLowerCase() === nameArray[i].toLowerCase()) + return node; + return null; +} + +Node.prototype.enclosingNodeOrSelfWithNodeName = function(nodeName) +{ + return this.enclosingNodeOrSelfWithNodeNameInArray([nodeName]); +} + +Node.prototype.enclosingNodeOrSelfWithClass = function(className) +{ + for (var node = this; node && node !== this.ownerDocument; node = node.parentNode) + if (node.nodeType === Node.ELEMENT_NODE && node.hasStyleClass(className)) + return node; + return null; +} + +Node.prototype.enclosingNodeWithClass = function(className) +{ + if (!this.parentNode) + return null; + return this.parentNode.enclosingNodeOrSelfWithClass(className); +} + +Element.prototype.query = function(query) +{ + return this.ownerDocument.evaluate(query, this, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; +} + +Element.prototype.removeChildren = function() +{ + if (this.firstChild) + this.textContent = ""; +} + +Element.prototype.isInsertionCaretInside = function() +{ + var selection = window.getSelection(); + if (!selection.rangeCount || !selection.isCollapsed) + return false; + var selectionRange = selection.getRangeAt(0); + return selectionRange.startContainer.isSelfOrDescendant(this); +} + +/** + * @param {string=} className + */ +Element.prototype.createChild = function(elementName, className) +{ + var element = this.ownerDocument.createElement(elementName); + if (className) + element.className = className; + this.appendChild(element); + return element; +} + +DocumentFragment.prototype.createChild = Element.prototype.createChild; + +/** + * @return {number} + */ +Element.prototype.totalOffsetLeft = function() +{ + var total = 0; + for (var element = this; element; element = element.offsetParent) { + total += element.offsetLeft + if (this !== element) + total += element.clientLeft - element.scrollLeft; + } + + return total; +} + +/** + * @return {number} + */ +Element.prototype.totalOffsetTop = function() +{ + var total = 0; + for (var element = this; element; element = element.offsetParent) { + total += element.offsetTop + if (this !== element) + total += element.clientTop - element.scrollTop; + } + + return total; +} + +/** + * @constructor + * @param {number=} x + * @param {number=} y + * @param {number=} width + * @param {number=} height + */ +function AnchorBox(x, y, width, height) +{ + this.x = x || 0; + this.y = y || 0; + this.width = width || 0; + this.height = height || 0; +} + +/** + * @param {Window} targetWindow + * @return {AnchorBox} + */ +Element.prototype.offsetRelativeToWindow = function(targetWindow) +{ + var elementOffset = new AnchorBox(); + var curElement = this; + var curWindow = this.ownerDocument.defaultView; + while (curWindow && curElement) { + elementOffset.x += curElement.totalOffsetLeft(); + elementOffset.y += curElement.totalOffsetTop(); + if (curWindow === targetWindow) + break; + + curElement = curWindow.frameElement; + curWindow = curWindow.parent; + } + + return elementOffset; +} + +/** + * @param {Window} targetWindow + * @return {AnchorBox} + */ +Element.prototype.boxInWindow = function(targetWindow) +{ + targetWindow = targetWindow || this.ownerDocument.defaultView; + + var anchorBox = this.offsetRelativeToWindow(window); + anchorBox.width = this.offsetWidth; + anchorBox.height = this.offsetHeight; + + return anchorBox; +} + +/** + * @param {string} text + */ +Element.prototype.setTextAndTitle = function(text) +{ + this.textContent = text; + this.title = text; +} + +KeyboardEvent.prototype.__defineGetter__("data", function() +{ + // Emulate "data" attribute from DOM 3 TextInput event. + // See http://www.w3.org/TR/DOM-Level-3-Events/#events-Events-TextEvent-data + switch (this.type) { + case "keypress": + if (!this.ctrlKey && !this.metaKey) + return String.fromCharCode(this.charCode); + else + return ""; + case "keydown": + case "keyup": + if (!this.ctrlKey && !this.metaKey && !this.altKey) + return String.fromCharCode(this.which); + else + return ""; + } +}); + +Text.prototype.select = function(start, end) +{ + start = start || 0; + end = end || this.textContent.length; + + if (start < 0) + start = end + start; + + var selection = this.ownerDocument.defaultView.getSelection(); + selection.removeAllRanges(); + var range = this.ownerDocument.createRange(); + range.setStart(this, start); + range.setEnd(this, end); + selection.addRange(range); + return this; +} + +Element.prototype.selectionLeftOffset = function() +{ + // Calculate selection offset relative to the current element. + + var selection = window.getSelection(); + if (!selection.containsNode(this, true)) + return null; + + var leftOffset = selection.anchorOffset; + var node = selection.anchorNode; + + while (node !== this) { + while (node.previousSibling) { + node = node.previousSibling; + leftOffset += node.textContent.length; + } + node = node.parentNode; + } + + return leftOffset; +} + +String.prototype.hasSubstring = function(string, caseInsensitive) +{ + if (!caseInsensitive) + return this.indexOf(string) !== -1; + return this.match(new RegExp(string.escapeForRegExp(), "i")); +} + +String.prototype.findAll = function(string) +{ + var matches = []; + var i = this.indexOf(string); + while (i !== -1) { + matches.push(i); + i = this.indexOf(string, i + string.length); + } + return matches; +} + +String.prototype.lineEndings = function() +{ + if (!this._lineEndings) { + this._lineEndings = this.findAll("\n"); + this._lineEndings.push(this.length); + } + return this._lineEndings; +} + +String.prototype.asParsedURL = function() +{ + // RegExp groups: + // 1 - scheme + // 2 - hostname + // 3 - ?port + // 4 - ?path + // 5 - ?fragment + var match = this.match(/^([^:]+):\/\/([^\/:]*)(?::([\d]+))?(?:(\/[^#]*)(?:#(.*))?)?$/i); + if (!match) + return null; + var result = {}; + result.scheme = match[1].toLowerCase(); + result.host = match[2]; + result.port = match[3]; + result.path = match[4] || "/"; + result.fragment = match[5]; + + result.lastPathComponent = ""; + if (result.path) { + // First cut the query params. + var path = result.path; + var indexOfQuery = path.indexOf("?"); + if (indexOfQuery !== -1) + path = path.substring(0, indexOfQuery); + + // Then take last path component. + var lastSlashIndex = path.lastIndexOf("/"); + if (lastSlashIndex !== -1) { + result.firstPathComponents = path.substring(0, lastSlashIndex + 1); + result.lastPathComponent = path.substring(lastSlashIndex + 1); + } + } + return result; +} + +String.prototype.escapeCharacters = function(chars) +{ + var foundChar = false; + for (var i = 0; i < chars.length; ++i) { + if (this.indexOf(chars.charAt(i)) !== -1) { + foundChar = true; + break; + } + } + + if (!foundChar) + return this; + + var result = ""; + for (var i = 0; i < this.length; ++i) { + if (chars.indexOf(this.charAt(i)) !== -1) + result += "\\"; + result += this.charAt(i); + } + + return result; +} + +String.prototype.escapeForRegExp = function() +{ + return this.escapeCharacters("^[]{}()\\.$*+?|"); +} + +String.prototype.escapeHTML = function() +{ + return this.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); //" doublequotes just for editor +} + +String.prototype.collapseWhitespace = function() +{ + return this.replace(/[\s\xA0]+/g, " "); +} + +String.prototype.trimMiddle = function(maxLength) +{ + if (this.length <= maxLength) + return this; + var leftHalf = maxLength >> 1; + var rightHalf = maxLength - leftHalf - 1; + return this.substr(0, leftHalf) + "\u2026" + this.substr(this.length - rightHalf, rightHalf); +} + +String.prototype.trimEnd = function(maxLength) +{ + if (this.length <= maxLength) + return this; + return this.substr(0, maxLength - 1) + "\u2026"; +} + +String.prototype.trimURL = function(baseURLDomain) +{ + var result = this.replace(/^(https|http|file):\/\//i, ""); + if (baseURLDomain) + result = result.replace(new RegExp("^" + baseURLDomain.escapeForRegExp(), "i"), ""); + return result; +} + +String.prototype.removeURLFragment = function() +{ + var fragmentIndex = this.indexOf("#"); + if (fragmentIndex == -1) + fragmentIndex = this.length; + return this.substring(0, fragmentIndex); +} + +Node.prototype.isAncestor = function(node) +{ + if (!node) + return false; + + var currentNode = node.parentNode; + while (currentNode) { + if (this === currentNode) + return true; + currentNode = currentNode.parentNode; + } + return false; +} + +Node.prototype.isDescendant = function(descendant) +{ + return !!descendant && descendant.isAncestor(this); +} + +Node.prototype.isSelfOrAncestor = function(node) +{ + return !!node && (node === this || this.isAncestor(node)); +} + +Node.prototype.isSelfOrDescendant = function(node) +{ + return !!node && (node === this || this.isDescendant(node)); +} + +Node.prototype.traverseNextNode = function(stayWithin) +{ + var node = this.firstChild; + if (node) + return node; + + if (stayWithin && this === stayWithin) + return null; + + node = this.nextSibling; + if (node) + return node; + + node = this; + while (node && !node.nextSibling && (!stayWithin || !node.parentNode || node.parentNode !== stayWithin)) + node = node.parentNode; + if (!node) + return null; + + return node.nextSibling; +} + +Node.prototype.traversePreviousNode = function(stayWithin) +{ + if (stayWithin && this === stayWithin) + return null; + var node = this.previousSibling; + while (node && node.lastChild) + node = node.lastChild; + if (node) + return node; + return this.parentNode; +} + +Number.constrain = function(num, min, max) +{ + if (num < min) + num = min; + else if (num > max) + num = max; + return num; +} + +Date.prototype.toISO8601Compact = function() +{ + function leadZero(x) + { + return x > 9 ? '' + x : '0' + x + } + return this.getFullYear() + + leadZero(this.getMonth() + 1) + + leadZero(this.getDate()) + 'T' + + leadZero(this.getHours()) + + leadZero(this.getMinutes()) + + leadZero(this.getSeconds()); +} + +HTMLTextAreaElement.prototype.moveCursorToEnd = function() +{ + var length = this.value.length; + this.setSelectionRange(length, length); +} + +Object.defineProperty(Array.prototype, "remove", +{ + /** + * @this {Array.<*>} + */ + value: function(value, onlyFirst) + { + if (onlyFirst) { + var index = this.indexOf(value); + if (index !== -1) + this.splice(index, 1); + return; + } + + var length = this.length; + for (var i = 0; i < length; ++i) { + if (this[i] === value) + this.splice(i, 1); + } + } +}); + +Object.defineProperty(Array.prototype, "keySet", +{ + /** + * @this {Array.<*>} + */ + value: function() + { + var keys = {}; + for (var i = 0; i < this.length; ++i) + keys[this[i]] = true; + return keys; + } +}); + +Object.defineProperty(Array.prototype, "upperBound", +{ + /** + * @this {Array.} + */ + value: function(value) + { + var first = 0; + var count = this.length; + while (count > 0) { + var step = count >> 1; + var middle = first + step; + if (value >= this[middle]) { + first = middle + 1; + count -= step + 1; + } else + count = step; + } + return first; + } +}); + +Array.diff = function(left, right) +{ + var o = left; + var n = right; + + var ns = {}; + var os = {}; + + for (var i = 0; i < n.length; i++) { + if (ns[n[i]] == null) + ns[n[i]] = { rows: [], o: null }; + ns[n[i]].rows.push(i); + } + + for (var i = 0; i < o.length; i++) { + if (os[o[i]] == null) + os[o[i]] = { rows: [], n: null }; + os[o[i]].rows.push(i); + } + + for (var i in ns) { + if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) { + n[ns[i].rows[0]] = { text: n[ns[i].rows[0]], row: os[i].rows[0] }; + o[os[i].rows[0]] = { text: o[os[i].rows[0]], row: ns[i].rows[0] }; + } + } + + for (var i = 0; i < n.length - 1; i++) { + if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null && n[i + 1] == o[n[i].row + 1]) { + n[i + 1] = { text: n[i + 1], row: n[i].row + 1 }; + o[n[i].row + 1] = { text: o[n[i].row + 1], row: i + 1 }; + } + } + + for (var i = n.length - 1; i > 0; i--) { + if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null && + n[i - 1] == o[n[i].row - 1]) { + n[i - 1] = { text: n[i - 1], row: n[i].row - 1 }; + o[n[i].row - 1] = { text: o[n[i].row - 1], row: i - 1 }; + } + } + + return { left: o, right: n }; +} + +Array.convert = function(list) +{ + // Cast array-like object to an array. + return Array.prototype.slice.call(list); +} + +/** + * @param {string} format + * @param {...*} var_arg + */ +String.sprintf = function(format, var_arg) +{ + return String.vsprintf(format, Array.prototype.slice.call(arguments, 1)); +} + +String.tokenizeFormatString = function(format) +{ + var tokens = []; + var substitutionIndex = 0; + + function addStringToken(str) + { + tokens.push({ type: "string", value: str }); + } + + function addSpecifierToken(specifier, precision, substitutionIndex) + { + tokens.push({ type: "specifier", specifier: specifier, precision: precision, substitutionIndex: substitutionIndex }); + } + + var index = 0; + for (var precentIndex = format.indexOf("%", index); precentIndex !== -1; precentIndex = format.indexOf("%", index)) { + addStringToken(format.substring(index, precentIndex)); + index = precentIndex + 1; + + if (format[index] === "%") { + addStringToken("%"); + ++index; + continue; + } + + if (!isNaN(format[index])) { + // The first character is a number, it might be a substitution index. + var number = parseInt(format.substring(index), 10); + while (!isNaN(format[index])) + ++index; + // If the number is greater than zero and ends with a "$", + // then this is a substitution index. + if (number > 0 && format[index] === "$") { + substitutionIndex = (number - 1); + ++index; + } + } + + var precision = -1; + if (format[index] === ".") { + // This is a precision specifier. If no digit follows the ".", + // then the precision should be zero. + ++index; + precision = parseInt(format.substring(index), 10); + if (isNaN(precision)) + precision = 0; + while (!isNaN(format[index])) + ++index; + } + + addSpecifierToken(format[index], precision, substitutionIndex); + + ++substitutionIndex; + ++index; + } + + addStringToken(format.substring(index)); + + return tokens; +} + +String.standardFormatters = { + d: function(substitution) + { + return !isNaN(substitution) ? substitution : 0; + }, + + f: function(substitution, token) + { + if (substitution && token.precision > -1) + substitution = substitution.toFixed(token.precision); + return !isNaN(substitution) ? substitution : (token.precision > -1 ? Number(0).toFixed(token.precision) : 0); + }, + + s: function(substitution) + { + return substitution; + } +} + +String.vsprintf = function(format, substitutions) +{ + return String.format(format, substitutions, String.standardFormatters, "", function(a, b) { return a + b; }).formattedResult; +} + +String.format = function(format, substitutions, formatters, initialValue, append) +{ + if (!format || !substitutions || !substitutions.length) + return { formattedResult: append(initialValue, format), unusedSubstitutions: substitutions }; + + function prettyFunctionName() + { + return "String.format(\"" + format + "\", \"" + substitutions.join("\", \"") + "\")"; + } + + function warn(msg) + { + console.warn(prettyFunctionName() + ": " + msg); + } + + function error(msg) + { + console.error(prettyFunctionName() + ": " + msg); + } + + var result = initialValue; + var tokens = String.tokenizeFormatString(format); + var usedSubstitutionIndexes = {}; + + for (var i = 0; i < tokens.length; ++i) { + var token = tokens[i]; + + if (token.type === "string") { + result = append(result, token.value); + continue; + } + + if (token.type !== "specifier") { + error("Unknown token type \"" + token.type + "\" found."); + continue; + } + + if (token.substitutionIndex >= substitutions.length) { + // If there are not enough substitutions for the current substitutionIndex + // just output the format specifier literally and move on. + error("not enough substitution arguments. Had " + substitutions.length + " but needed " + (token.substitutionIndex + 1) + ", so substitution was skipped."); + result = append(result, "%" + (token.precision > -1 ? token.precision : "") + token.specifier); + continue; + } + + usedSubstitutionIndexes[token.substitutionIndex] = true; + + if (!(token.specifier in formatters)) { + // Encountered an unsupported format character, treat as a string. + warn("unsupported format character \u201C" + token.specifier + "\u201D. Treating as a string."); + result = append(result, substitutions[token.substitutionIndex]); + continue; + } + + result = append(result, formatters[token.specifier](substitutions[token.substitutionIndex], token)); + } + + var unusedSubstitutions = []; + for (var i = 0; i < substitutions.length; ++i) { + if (i in usedSubstitutionIndexes) + continue; + unusedSubstitutions.push(substitutions[i]); + } + + return { formattedResult: result, unusedSubstitutions: unusedSubstitutions }; +} + +function isEnterKey(event) { + // Check if in IME. + return event.keyCode !== 229 && event.keyIdentifier === "Enter"; +} + +/** + * @param {Element} element + * @param {number} offset + * @param {number} length + * @param {Array.=} domChanges + */ +function highlightSearchResult(element, offset, length, domChanges) +{ + var result = highlightSearchResults(element, [{offset: offset, length: length }], domChanges); + return result.length ? result[0] : null; +} + +/** + * @param {Element} element + * @param {Array.} resultRanges + * @param {Array.=} changes + */ +function highlightSearchResults(element, resultRanges, changes) +{ + return highlightRangesWithStyleClass(element, resultRanges, "webkit-search-result", changes); + +} + +/** + * @param {Element} element + * @param {Array.} resultRanges + * @param {string} styleClass + * @param {Array.=} changes + */ +function highlightRangesWithStyleClass(element, resultRanges, styleClass, changes) +{ + changes = changes || []; + var highlightNodes = []; + var lineText = element.textContent; + var ownerDocument = element.ownerDocument; + var textNodeSnapshot = ownerDocument.evaluate(".//text()", element, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + + var snapshotLength = textNodeSnapshot.snapshotLength; + if (snapshotLength === 0) + return highlightNodes; + + var nodeRanges = []; + var rangeEndOffset = 0; + for (var i = 0; i < snapshotLength; ++i) { + var range = {}; + range.offset = rangeEndOffset; + range.length = textNodeSnapshot.snapshotItem(i).textContent.length; + rangeEndOffset = range.offset + range.length; + nodeRanges.push(range); + } + + var startIndex = 0; + for (var i = 0; i < resultRanges.length; ++i) { + var startOffset = resultRanges[i].offset; + var endOffset = startOffset + resultRanges[i].length; + + while (startIndex < snapshotLength && nodeRanges[startIndex].offset + nodeRanges[startIndex].length <= startOffset) + startIndex++; + var endIndex = startIndex; + while (endIndex < snapshotLength && nodeRanges[endIndex].offset + nodeRanges[endIndex].length < endOffset) + endIndex++; + if (endIndex === snapshotLength) + break; + + var highlightNode = ownerDocument.createElement("span"); + highlightNode.className = styleClass; + highlightNode.textContent = lineText.substring(startOffset, endOffset); + + var lastTextNode = textNodeSnapshot.snapshotItem(endIndex); + var lastText = lastTextNode.textContent; + lastTextNode.textContent = lastText.substring(endOffset - nodeRanges[endIndex].offset); + changes.push({ node: lastTextNode, type: "changed", oldText: lastText, newText: lastTextNode.textContent }); + + if (startIndex === endIndex) { + lastTextNode.parentElement.insertBefore(highlightNode, lastTextNode); + changes.push({ node: highlightNode, type: "added", nextSibling: lastTextNode, parent: lastTextNode.parentElement }); + highlightNodes.push(highlightNode); + + var prefixNode = ownerDocument.createTextNode(lastText.substring(0, startOffset - nodeRanges[startIndex].offset)); + lastTextNode.parentElement.insertBefore(prefixNode, highlightNode); + changes.push({ node: prefixNode, type: "added", nextSibling: highlightNode, parent: lastTextNode.parentElement }); + } else { + var firstTextNode = textNodeSnapshot.snapshotItem(startIndex); + var firstText = firstTextNode.textContent; + var anchorElement = firstTextNode.nextSibling; + + firstTextNode.parentElement.insertBefore(highlightNode, anchorElement); + changes.push({ node: highlightNode, type: "added", nextSibling: anchorElement, parent: firstTextNode.parentElement }); + highlightNodes.push(highlightNode); + + firstTextNode.textContent = firstText.substring(0, startOffset - nodeRanges[startIndex].offset); + changes.push({ node: firstTextNode, type: "changed", oldText: firstText, newText: firstTextNode.textContent }); + + for (var j = startIndex + 1; j < endIndex; j++) { + var textNode = textNodeSnapshot.snapshotItem(j); + var text = textNode.textContent; + textNode.textContent = ""; + changes.push({ node: textNode, type: "changed", oldText: text, newText: textNode.textContent }); + } + } + startIndex = endIndex; + nodeRanges[startIndex].offset = endOffset; + nodeRanges[startIndex].length = lastTextNode.textContent.length; + + } + return highlightNodes; +} + +function applyDomChanges(domChanges) +{ + for (var i = 0, size = domChanges.length; i < size; ++i) { + var entry = domChanges[i]; + switch (entry.type) { + case "added": + entry.parent.insertBefore(entry.node, entry.nextSibling); + break; + case "changed": + entry.node.textContent = entry.newText; + break; + } + } +} + +function revertDomChanges(domChanges) +{ + for (var i = domChanges.length - 1; i >= 0; --i) { + var entry = domChanges[i]; + switch (entry.type) { + case "added": + if (entry.node.parentElement) + entry.node.parentElement.removeChild(entry.node); + break; + case "changed": + entry.node.textContent = entry.oldText; + break; + } + } +} + +/** + * @param {string} query + * @param {boolean} caseSensitive + * @param {boolean} isRegex + * @return {RegExp} + */ +function createSearchRegex(query, caseSensitive, isRegex) +{ + var regexFlags = caseSensitive ? "g" : "gi"; + var regexObject; + + if (isRegex) { + try { + regexObject = new RegExp(query, regexFlags); + } catch (e) { + // Silent catch. + } + } + + if (!regexObject) + regexObject = createPlainTextSearchRegex(query, regexFlags); + + return regexObject; +} + +/** + * @param {string} query + * @param {string=} flags + * @return {RegExp} + */ +function createPlainTextSearchRegex(query, flags) +{ + // This should be kept the same as the one in ContentSearchUtils.cpp. + var regexSpecialCharacters = "[](){}+-*.,?\\^$|"; + var regex = ""; + for (var i = 0; i < query.length; ++i) { + var c = query.charAt(i); + if (regexSpecialCharacters.indexOf(c) != -1) + regex += "\\"; + regex += c; + } + return new RegExp(regex, flags || ""); +} + +/** + * @param {RegExp} regex + * @param {string} content + * @return {number} + */ +function countRegexMatches(regex, content) +{ + var text = content; + var result = 0; + var match; + while (text && (match = regex.exec(text))) { + if (match[0].length > 0) + ++result; + text = text.substring(match.index + 1); + } + return result; +} + +/** + * @param {number} value + * @param {number} symbolsCount + * @return {string} + */ +function numberToStringWithSpacesPadding(value, symbolsCount) +{ + var numberString = value.toString(); + var paddingLength = Math.max(0, symbolsCount - numberString.length); + var paddingString = Array(paddingLength + 1).join("\u00a0"); + return paddingString + numberString; +} + +/** + * @constructor + */ +function TextDiff() +{ + this.added = []; + this.removed = []; + this.changed = []; +} + +/** + * @param {string} baseContent + * @param {string} newContent + * @return {TextDiff} + */ +TextDiff.compute = function(baseContent, newContent) +{ + var oldLines = baseContent.split(/\r?\n/); + var newLines = newContent.split(/\r?\n/); + + var diff = Array.diff(oldLines, newLines); + + var diffData = new TextDiff(); + + var offset = 0; + var right = diff.right; + for (var i = 0; i < right.length; ++i) { + if (typeof right[i] === "string") { + if (right.length > i + 1 && right[i + 1].row === i + 1 - offset) + diffData.changed.push(i); + else { + diffData.added.push(i); + offset++; + } + } else + offset = i - right[i].row; + } + return diffData; +} + +/** + * @constructor + */ +var Map = function() +{ + this._map = {}; +} + +Map._lastObjectIdentifier = 0; + +Map.prototype = { + /** + * @param {Object} key + */ + put: function(key, value) + { + var objectIdentifier = key.__identifier; + if (!objectIdentifier) { + objectIdentifier = ++Map._lastObjectIdentifier; + key.__identifier = objectIdentifier; + } + this._map[objectIdentifier] = value; + }, + + /** + * @param {Object} key + */ + remove: function(key) + { + delete this._map[key.__identifier]; + }, + + keys: function() + { + var result = []; + for (var key in this._map) + result.push(key); + return result; + }, + + /** + * @param {Object} key + */ + get: function(key) + { + return this._map[key.__identifier]; + }, +} +/* BinarySearch.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @param {*} object + * @param {Array.<*>} array + * @param {function(*, *)} comparator + */ +function binarySearch(object, array, comparator) +{ + var first = 0; + var last = array.length - 1; + + while (first <= last) { + var mid = (first + last) >> 1; + var c = comparator(object, array[mid]); + if (c > 0) + first = mid + 1; + else if (c < 0) + last = mid - 1; + else + return mid; + } + + // Return the nearest lesser index, "-1" means "0, "-2" means "1", etc. + return -(first + 1); +} + +Object.defineProperty(Array.prototype, "binaryIndexOf", { value: function(value, comparator) +{ + var result = binarySearch(value, this, comparator); + return result >= 0 ? result : -1; +}}); + +/** + * @param {*} anObject + * @param {Array.<*>} aList + * @param {function(*, *)} aFunction + */ +function insertionIndexForObjectInListSortedByFunction(anObject, aList, aFunction) +{ + var index = binarySearch(anObject, aList, aFunction); + if (index < 0) + // See binarySearch implementation. + return -index - 1; + else { + // Return the first occurance of an item in the list. + while (index > 0 && aFunction(anObject, aList[index - 1]) === 0) + index--; + return index; + } +} +/* treeoutline.js */ + + /* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + */ +function TreeOutline(listNode) +{ + /** + * @type {Array.} + */ + this.children = []; + this.selectedTreeElement = null; + this._childrenListNode = listNode; + this._childrenListNode.removeChildren(); + this.expandTreeElementsWhenArrowing = false; + this.root = true; + this.hasChildren = false; + this.expanded = true; + this.selected = false; + this.treeOutline = this; + this.comparator = null; + this.searchable = false; + this.searchInputElement = null; + + this._childrenListNode.tabIndex = 0; + this._childrenListNode.addEventListener("keydown", this._treeKeyDown.bind(this), true); + this._childrenListNode.addEventListener("keypress", this._treeKeyPress.bind(this), true); + + this._treeElementsMap = new Map(); + this._expandedStateMap = new Map(); +} + +TreeOutline.prototype.appendChild = function(child) +{ + var insertionIndex; + if (this.treeOutline.comparator) + insertionIndex = insertionIndexForObjectInListSortedByFunction(child, this.children, this.treeOutline.comparator); + else + insertionIndex = this.children.length; + this.insertChild(child, insertionIndex); +} + +TreeOutline.prototype.insertChild = function(child, index) +{ + if (!child) + throw("child can't be undefined or null"); + + var previousChild = (index > 0 ? this.children[index - 1] : null); + if (previousChild) { + previousChild.nextSibling = child; + child.previousSibling = previousChild; + } else { + child.previousSibling = null; + } + + var nextChild = this.children[index]; + if (nextChild) { + nextChild.previousSibling = child; + child.nextSibling = nextChild; + } else { + child.nextSibling = null; + } + + this.children.splice(index, 0, child); + this.hasChildren = true; + child.parent = this; + child.treeOutline = this.treeOutline; + child.treeOutline._rememberTreeElement(child); + + var current = child.children[0]; + while (current) { + current.treeOutline = this.treeOutline; + current.treeOutline._rememberTreeElement(current); + current = current.traverseNextTreeElement(false, child, true); + } + + if (child.hasChildren && typeof(child.treeOutline._expandedStateMap.get(child.representedObject)) !== "undefined") + child.expanded = child.treeOutline._expandedStateMap.get(child.representedObject); + + if (!this._childrenListNode) { + this._childrenListNode = this.treeOutline._childrenListNode.ownerDocument.createElement("ol"); + this._childrenListNode.parentTreeElement = this; + this._childrenListNode.classList.add("children"); + if (this.hidden) + this._childrenListNode.classList.add("hidden"); + } + + child._attach(); + + if (this.treeOutline.onadd) + this.treeOutline.onadd(child); +} + +TreeOutline.prototype.removeChildAtIndex = function(childIndex) +{ + if (childIndex < 0 || childIndex >= this.children.length) + throw("childIndex out of range"); + + var child = this.children[childIndex]; + this.children.splice(childIndex, 1); + + var parent = child.parent; + if (child.deselect()) { + if (child.previousSibling) + child.previousSibling.select(); + else if (child.nextSibling) + child.nextSibling.select(); + else + parent.select(); + } + + if (child.previousSibling) + child.previousSibling.nextSibling = child.nextSibling; + if (child.nextSibling) + child.nextSibling.previousSibling = child.previousSibling; + + if (child.treeOutline) { + child.treeOutline._forgetTreeElement(child); + child.treeOutline._forgetChildrenRecursive(child); + } + + child._detach(); + child.treeOutline = null; + child.parent = null; + child.nextSibling = null; + child.previousSibling = null; +} + +TreeOutline.prototype.removeChild = function(child) +{ + if (!child) + throw("child can't be undefined or null"); + + var childIndex = this.children.indexOf(child); + if (childIndex === -1) + throw("child not found in this node's children"); + + this.removeChildAtIndex.call(this, childIndex); +} + +TreeOutline.prototype.removeChildren = function() +{ + for (var i = 0; i < this.children.length; ++i) { + var child = this.children[i]; + child.deselect(); + + if (child.treeOutline) { + child.treeOutline._forgetTreeElement(child); + child.treeOutline._forgetChildrenRecursive(child); + } + + child._detach(); + child.treeOutline = null; + child.parent = null; + child.nextSibling = null; + child.previousSibling = null; + } + + this.children = []; +} + +TreeOutline.prototype.removeChildrenRecursive = function() +{ + var childrenToRemove = this.children; + + var child = this.children[0]; + while (child) { + if (child.children.length) + childrenToRemove = childrenToRemove.concat(child.children); + child = child.traverseNextTreeElement(false, this, true); + } + + for (var i = 0; i < childrenToRemove.length; ++i) { + child = childrenToRemove[i]; + child.deselect(); + if (child.treeOutline) + child.treeOutline._forgetTreeElement(child); + child._detach(); + child.children = []; + child.treeOutline = null; + child.parent = null; + child.nextSibling = null; + child.previousSibling = null; + } + + this.children = []; +} + +TreeOutline.prototype._rememberTreeElement = function(element) +{ + if (!this._treeElementsMap.get(element.representedObject)) + this._treeElementsMap.put(element.representedObject, []); + + // check if the element is already known + var elements = this._treeElementsMap.get(element.representedObject); + if (elements.indexOf(element) !== -1) + return; + + // add the element + elements.push(element); +} + +TreeOutline.prototype._forgetTreeElement = function(element) +{ + if (this._treeElementsMap.get(element.representedObject)) + this._treeElementsMap.get(element.representedObject).remove(element, true); +} + +TreeOutline.prototype._forgetChildrenRecursive = function(parentElement) +{ + var child = parentElement.children[0]; + while (child) { + this._forgetTreeElement(child); + child = child.traverseNextTreeElement(false, this, true); + } +} + +TreeOutline.prototype.getCachedTreeElement = function(representedObject) +{ + if (!representedObject) + return null; + + var elements = this._treeElementsMap.get(representedObject); + if (elements && elements.length) + return elements[0]; + return null; +} + +TreeOutline.prototype.findTreeElement = function(representedObject, isAncestor, getParent) +{ + if (!representedObject) + return null; + + var cachedElement = this.getCachedTreeElement(representedObject); + if (cachedElement) + return cachedElement; + + // The representedObject isn't known, so we start at the top of the tree and work down to find the first + // tree element that represents representedObject or one of its ancestors. + var item; + var found = false; + for (var i = 0; i < this.children.length; ++i) { + item = this.children[i]; + if (item.representedObject === representedObject || isAncestor(item.representedObject, representedObject)) { + found = true; + break; + } + } + + if (!found) + return null; + + // Make sure the item that we found is connected to the root of the tree. + // Build up a list of representedObject's ancestors that aren't already in our tree. + var ancestors = []; + var currentObject = representedObject; + while (currentObject) { + ancestors.unshift(currentObject); + if (currentObject === item.representedObject) + break; + currentObject = getParent(currentObject); + } + + // For each of those ancestors we populate them to fill in the tree. + for (var i = 0; i < ancestors.length; ++i) { + // Make sure we don't call findTreeElement with the same representedObject + // again, to prevent infinite recursion. + if (ancestors[i] === representedObject) + continue; + // FIXME: we could do something faster than findTreeElement since we will know the next + // ancestor exists in the tree. + item = this.findTreeElement(ancestors[i], isAncestor, getParent); + if (item) + item.onpopulate(); + } + + return this.getCachedTreeElement(representedObject); +} + +TreeOutline.prototype._treeElementDidChange = function(treeElement) +{ + if (treeElement.treeOutline !== this) + return; + + if (this.onchange) + this.onchange(treeElement); +} + +TreeOutline.prototype.treeElementFromPoint = function(x, y) +{ + var node = this._childrenListNode.ownerDocument.elementFromPoint(x, y); + if (!node) + return null; + + var listNode = node.enclosingNodeOrSelfWithNodeNameInArray(["ol", "li"]); + if (listNode) + return listNode.parentTreeElement || listNode.treeElement; + return null; +} + +TreeOutline.prototype._treeKeyPress = function(event) +{ + if (!this.searchable) + return; + + var searchText = String.fromCharCode(event.charCode); + // Ignore whitespace. + if (searchText.trim() !== searchText) + return; + + this._startSearch(searchText); + event.preventDefault(); + event.stopPropagation(); +} + +TreeOutline.prototype._treeKeyDown = function(event) +{ + if (event.target !== this._childrenListNode) + return; + + if (!this.selectedTreeElement || event.shiftKey || event.metaKey || event.ctrlKey) + return; + + var handled = false; + var nextSelectedElement; + if (event.keyIdentifier === "Up" && !event.altKey) { + nextSelectedElement = this.selectedTreeElement.traversePreviousTreeElement(true); + while (nextSelectedElement && !nextSelectedElement.selectable) + nextSelectedElement = nextSelectedElement.traversePreviousTreeElement(!this.expandTreeElementsWhenArrowing); + handled = nextSelectedElement ? true : false; + } else if (event.keyIdentifier === "Down" && !event.altKey) { + nextSelectedElement = this.selectedTreeElement.traverseNextTreeElement(true); + while (nextSelectedElement && !nextSelectedElement.selectable) + nextSelectedElement = nextSelectedElement.traverseNextTreeElement(!this.expandTreeElementsWhenArrowing); + handled = nextSelectedElement ? true : false; + } else if (event.keyIdentifier === "Left") { + if (this.selectedTreeElement.expanded) { + if (event.altKey) + this.selectedTreeElement.collapseRecursively(); + else + this.selectedTreeElement.collapse(); + handled = true; + } else if (this.selectedTreeElement.parent && !this.selectedTreeElement.parent.root) { + handled = true; + if (this.selectedTreeElement.parent.selectable) { + nextSelectedElement = this.selectedTreeElement.parent; + while (nextSelectedElement && !nextSelectedElement.selectable) + nextSelectedElement = nextSelectedElement.parent; + handled = nextSelectedElement ? true : false; + } else if (this.selectedTreeElement.parent) + this.selectedTreeElement.parent.collapse(); + } + } else if (event.keyIdentifier === "Right") { + if (!this.selectedTreeElement.revealed()) { + this.selectedTreeElement.reveal(); + handled = true; + } else if (this.selectedTreeElement.hasChildren) { + handled = true; + if (this.selectedTreeElement.expanded) { + nextSelectedElement = this.selectedTreeElement.children[0]; + while (nextSelectedElement && !nextSelectedElement.selectable) + nextSelectedElement = nextSelectedElement.nextSibling; + handled = nextSelectedElement ? true : false; + } else { + if (event.altKey) + this.selectedTreeElement.expandRecursively(); + else + this.selectedTreeElement.expand(); + } + } + } else if (event.keyCode === 8 /* Backspace */ || event.keyCode === 46 /* Delete */) { + if (this.selectedTreeElement.ondelete) + handled = this.selectedTreeElement.ondelete(); + } else if (isEnterKey(event)) { + if (this.selectedTreeElement.onenter) + handled = this.selectedTreeElement.onenter(); + } + + if (nextSelectedElement) { + nextSelectedElement.reveal(); + nextSelectedElement.select(false, true); + } + + if (handled) { + event.preventDefault(); + event.stopPropagation(); + } +} + +TreeOutline.prototype.expand = function() +{ + // this is the root, do nothing +} + +TreeOutline.prototype.collapse = function() +{ + // this is the root, do nothing +} + +TreeOutline.prototype.revealed = function() +{ + return true; +} + +TreeOutline.prototype.reveal = function() +{ + // this is the root, do nothing +} + +TreeOutline.prototype.select = function() +{ + // this is the root, do nothing +} + +/** + * @param {boolean=} omitFocus + */ +TreeOutline.prototype.revealAndSelect = function(omitFocus) +{ + // this is the root, do nothing +} + +/** + * @param {string} searchText + */ +TreeOutline.prototype._startSearch = function(searchText) +{ + if (!this.searchInputElement || !this.searchable) + return; + + this._searching = true; + + if (this.searchStarted) + this.searchStarted(); + + this.searchInputElement.value = searchText; + + function focusSearchInput() + { + this.searchInputElement.focus(); + } + window.setTimeout(focusSearchInput.bind(this), 0); + this._searchTextChanged(); + this._boundSearchTextChanged = this._searchTextChanged.bind(this); + this.searchInputElement.addEventListener("paste", this._boundSearchTextChanged); + this.searchInputElement.addEventListener("cut", this._boundSearchTextChanged); + this.searchInputElement.addEventListener("keypress", this._boundSearchTextChanged); + this._boundSearchInputKeyDown = this._searchInputKeyDown.bind(this); + this.searchInputElement.addEventListener("keydown", this._boundSearchInputKeyDown); + this._boundSearchInputBlur = this._searchInputBlur.bind(this); + this.searchInputElement.addEventListener("blur", this._boundSearchInputBlur); +} + +TreeOutline.prototype._searchTextChanged = function() +{ + function updateSearch() + { + var nextSelectedElement = this._nextSearchMatch(this.searchInputElement.value, this.selectedTreeElement, false); + if (!nextSelectedElement) + nextSelectedElement = this._nextSearchMatch(this.searchInputElement.value, this.children[0], false); + this._showSearchMatchElement(nextSelectedElement); + } + + window.setTimeout(updateSearch.bind(this), 0); +} + +TreeOutline.prototype._showSearchMatchElement = function(treeElement) +{ + this._currentSearchMatchElement = treeElement; + if (treeElement) { + this._childrenListNode.classList.add("search-match-found"); + this._childrenListNode.classList.remove("search-match-not-found"); + treeElement.revealAndSelect(true); + } else { + this._childrenListNode.classList.remove("search-match-found"); + this._childrenListNode.classList.add("search-match-not-found"); + } +} + +TreeOutline.prototype._searchInputKeyDown = function(event) +{ + if (event.shiftKey || event.metaKey || event.ctrlKey || event.altKey) + return; + + var handled = false; + var nextSelectedElement; + if (event.keyIdentifier === "Down") { + nextSelectedElement = this._nextSearchMatch(this.searchInputElement.value, this.selectedTreeElement, true); + handled = true; + } else if (event.keyIdentifier === "Up") { + nextSelectedElement = this._previousSearchMatch(this.searchInputElement.value, this.selectedTreeElement); + handled = true; + } else if (event.keyCode === 27 /* Esc */) { + this._searchFinished(); + handled = true; + } else if (isEnterKey(event)) { + var lastSearchMatchElement = this._currentSearchMatchElement; + this._searchFinished(); + if (lastSearchMatchElement && lastSearchMatchElement.onenter) + lastSearchMatchElement.onenter(); + handled = true; + } + + if (nextSelectedElement) + this._showSearchMatchElement(nextSelectedElement); + + if (handled) { + event.preventDefault(); + event.stopPropagation(); + } else + window.setTimeout(this._boundSearchTextChanged, 0); +} + +/** + * @param {string} searchText + * @param {TreeElement} startTreeElement + * @param {boolean} skipStartTreeElement + */ +TreeOutline.prototype._nextSearchMatch = function(searchText, startTreeElement, skipStartTreeElement) +{ + var currentTreeElement = startTreeElement; + var skipCurrentTreeElement = skipStartTreeElement; + while (currentTreeElement && (skipCurrentTreeElement || !currentTreeElement.matchesSearchText || !currentTreeElement.matchesSearchText(searchText))) { + currentTreeElement = currentTreeElement.traverseNextTreeElement(true, null, true); + skipCurrentTreeElement = false; + } + + return currentTreeElement; +} + +/** + * @param {string} searchText + * @param {TreeElement=} startTreeElement + */ +TreeOutline.prototype._previousSearchMatch = function(searchText, startTreeElement) +{ + var currentTreeElement = startTreeElement; + var skipCurrentTreeElement = true; + while (currentTreeElement && (skipCurrentTreeElement || !currentTreeElement.matchesSearchText || !currentTreeElement.matchesSearchText(searchText))) { + currentTreeElement = currentTreeElement.traversePreviousTreeElement(true, true); + skipCurrentTreeElement = false; + } + + return currentTreeElement; +} + +TreeOutline.prototype._searchInputBlur = function(event) +{ + this._searchFinished(); +} + +TreeOutline.prototype._searchFinished = function() +{ + if (!this._searching) + return; + + delete this._searching; + this._childrenListNode.classList.remove("search-match-found"); + this._childrenListNode.classList.remove("search-match-not-found"); + delete this._currentSearchMatchElement; + + this.searchInputElement.value = ""; + this.searchInputElement.removeEventListener("paste", this._boundSearchTextChanged); + this.searchInputElement.removeEventListener("cut", this._boundSearchTextChanged); + delete this._boundSearchTextChanged; + + this.searchInputElement.removeEventListener("keydown", this._boundSearchInputKeyDown); + delete this._boundSearchInputKeyDown; + + this.searchInputElement.removeEventListener("blur", this._boundSearchInputBlur); + delete this._boundSearchInputBlur; + + if (this.searchFinished) + this.searchFinished(); + + this.treeOutline._childrenListNode.focus(); +} + +TreeOutline.prototype.stopSearch = function() +{ + this._searchFinished(); +} + +/** + * @constructor + * @param {Object=} representedObject + * @param {boolean=} hasChildren + */ +function TreeElement(title, representedObject, hasChildren) +{ + this._title = title; + this.representedObject = (representedObject || {}); + + this._hidden = false; + this._selectable = true; + this.expanded = false; + this.selected = false; + this.hasChildren = hasChildren; + this.children = []; + this.treeOutline = null; + this.parent = null; + this.previousSibling = null; + this.nextSibling = null; + this._listItemNode = null; +} + +TreeElement.prototype = { + arrowToggleWidth: 10, + + get selectable() { + if (this._hidden) + return false; + return this._selectable; + }, + + set selectable(x) { + this._selectable = x; + }, + + get listItemElement() { + return this._listItemNode; + }, + + get childrenListElement() { + return this._childrenListNode; + }, + + get title() { + return this._title; + }, + + set title(x) { + this._title = x; + this._setListItemNodeContent(); + this.didChange(); + }, + + get tooltip() { + return this._tooltip; + }, + + set tooltip(x) { + this._tooltip = x; + if (this._listItemNode) + this._listItemNode.title = x ? x : ""; + this.didChange(); + }, + + get hasChildren() { + return this._hasChildren; + }, + + set hasChildren(x) { + if (this._hasChildren === x) + return; + + this._hasChildren = x; + + if (!this._listItemNode) + return; + + if (x) + this._listItemNode.classList.add("parent"); + else { + this._listItemNode.classList.remove("parent"); + this.collapse(); + } + + this.didChange(); + }, + + get hidden() { + return this._hidden; + }, + + set hidden(x) { + if (this._hidden === x) + return; + + this._hidden = x; + + if (x) { + if (this._listItemNode) + this._listItemNode.classList.add("hidden"); + if (this._childrenListNode) + this._childrenListNode.classList.add("hidden"); + } else { + if (this._listItemNode) + this._listItemNode.classList.remove("hidden"); + if (this._childrenListNode) + this._childrenListNode.classList.remove("hidden"); + } + }, + + get shouldRefreshChildren() { + return this._shouldRefreshChildren; + }, + + set shouldRefreshChildren(x) { + this._shouldRefreshChildren = x; + if (x && this.expanded) + this.expand(); + }, + + _fireDidChange: function() + { + delete this._didChangeTimeoutIdentifier; + + if (this.treeOutline) + this.treeOutline._treeElementDidChange(this); + }, + + didChange: function() + { + if (!this.treeOutline) + return; + + // Prevent telling the TreeOutline multiple times in a row by delaying it with a timeout. + if (!this._didChangeTimeoutIdentifier) + this._didChangeTimeoutIdentifier = setTimeout(this._fireDidChange.bind(this), 0); + }, + + _setListItemNodeContent: function() + { + if (!this._listItemNode) + return; + + if (typeof this._title === "string") + this._listItemNode.textContent = this._title; + else { + this._listItemNode.removeChildren(); + if (this._title) + this._listItemNode.appendChild(this._title); + } + } +} + +TreeElement.prototype.appendChild = TreeOutline.prototype.appendChild; +TreeElement.prototype.insertChild = TreeOutline.prototype.insertChild; +TreeElement.prototype.removeChild = TreeOutline.prototype.removeChild; +TreeElement.prototype.removeChildAtIndex = TreeOutline.prototype.removeChildAtIndex; +TreeElement.prototype.removeChildren = TreeOutline.prototype.removeChildren; +TreeElement.prototype.removeChildrenRecursive = TreeOutline.prototype.removeChildrenRecursive; + +TreeElement.prototype._attach = function() +{ + if (!this._listItemNode || this.parent._shouldRefreshChildren) { + if (this._listItemNode && this._listItemNode.parentNode) + this._listItemNode.parentNode.removeChild(this._listItemNode); + + this._listItemNode = this.treeOutline._childrenListNode.ownerDocument.createElement("li"); + this._listItemNode.treeElement = this; + this._setListItemNodeContent(); + this._listItemNode.title = this._tooltip ? this._tooltip : ""; + + if (this.hidden) + this._listItemNode.classList.add("hidden"); + if (this.hasChildren) + this._listItemNode.classList.add("parent"); + if (this.expanded) + this._listItemNode.classList.add("expanded"); + if (this.selected) + this._listItemNode.classList.add("selected"); + + this._listItemNode.addEventListener("mousedown", TreeElement.treeElementMouseDown, false); + this._listItemNode.addEventListener("click", TreeElement.treeElementToggled, false); + this._listItemNode.addEventListener("dblclick", TreeElement.treeElementDoubleClicked, false); + + if (this.onattach) + this.onattach(this); + } + + var nextSibling = null; + if (this.nextSibling && this.nextSibling._listItemNode && this.nextSibling._listItemNode.parentNode === this.parent._childrenListNode) + nextSibling = this.nextSibling._listItemNode; + this.parent._childrenListNode.insertBefore(this._listItemNode, nextSibling); + if (this._childrenListNode) + this.parent._childrenListNode.insertBefore(this._childrenListNode, this._listItemNode.nextSibling); + if (this.selected) + this.select(); + if (this.expanded) + this.expand(); +} + +TreeElement.prototype._detach = function() +{ + if (this._listItemNode && this._listItemNode.parentNode) + this._listItemNode.parentNode.removeChild(this._listItemNode); + if (this._childrenListNode && this._childrenListNode.parentNode) + this._childrenListNode.parentNode.removeChild(this._childrenListNode); +} + +TreeElement.treeElementMouseDown = function(event) +{ + var element = event.currentTarget; + if (!element || !element.treeElement || !element.treeElement.selectable) + return; + + if (element.treeElement.isEventWithinDisclosureTriangle(event)) + return; + + element.treeElement.selectOnMouseDown(event); +} + +TreeElement.treeElementToggled = function(event) +{ + var element = event.currentTarget; + if (!element || !element.treeElement) + return; + + var toggleOnClick = element.treeElement.toggleOnClick && !element.treeElement.selectable; + var isInTriangle = element.treeElement.isEventWithinDisclosureTriangle(event); + if (!toggleOnClick && !isInTriangle) + return; + + if (element.treeElement.expanded) { + if (event.altKey) + element.treeElement.collapseRecursively(); + else + element.treeElement.collapse(); + } else { + if (event.altKey) + element.treeElement.expandRecursively(); + else + element.treeElement.expand(); + } + event.stopPropagation(); +} + +TreeElement.treeElementDoubleClicked = function(event) +{ + var element = event.currentTarget; + if (!element || !element.treeElement) + return; + + if (element.treeElement.ondblclick) + element.treeElement.ondblclick.call(element.treeElement, event); + else if (element.treeElement.hasChildren && !element.treeElement.expanded) + element.treeElement.expand(); +} + +TreeElement.prototype.collapse = function() +{ + if (this._listItemNode) + this._listItemNode.classList.remove("expanded"); + if (this._childrenListNode) + this._childrenListNode.classList.remove("expanded"); + + this.expanded = false; + + if (this.treeOutline) + this.treeOutline._expandedStateMap.put(this.representedObject, false); + + if (this.oncollapse) + this.oncollapse(this); +} + +TreeElement.prototype.collapseRecursively = function() +{ + var item = this; + while (item) { + if (item.expanded) + item.collapse(); + item = item.traverseNextTreeElement(false, this, true); + } +} + +TreeElement.prototype.expand = function() +{ + if (!this.hasChildren || (this.expanded && !this._shouldRefreshChildren && this._childrenListNode)) + return; + + // Set this before onpopulate. Since onpopulate can add elements and call onadd, this makes + // sure the expanded flag is true before calling those functions. This prevents the possibility + // of an infinite loop if onpopulate or onadd were to call expand. + + this.expanded = true; + if (this.treeOutline) + this.treeOutline._expandedStateMap.put(this.representedObject, true); + + if (this.treeOutline && (!this._childrenListNode || this._shouldRefreshChildren)) { + if (this._childrenListNode && this._childrenListNode.parentNode) + this._childrenListNode.parentNode.removeChild(this._childrenListNode); + + this._childrenListNode = this.treeOutline._childrenListNode.ownerDocument.createElement("ol"); + this._childrenListNode.parentTreeElement = this; + this._childrenListNode.classList.add("children"); + + if (this.hidden) + this._childrenListNode.classList.add("hidden"); + + this.onpopulate(); + + for (var i = 0; i < this.children.length; ++i) + this.children[i]._attach(); + + delete this._shouldRefreshChildren; + } + + if (this._listItemNode) { + this._listItemNode.classList.add("expanded"); + if (this._childrenListNode && this._childrenListNode.parentNode != this._listItemNode.parentNode) + this.parent._childrenListNode.insertBefore(this._childrenListNode, this._listItemNode.nextSibling); + } + + if (this._childrenListNode) + this._childrenListNode.classList.add("expanded"); + + if (this.onexpand) + this.onexpand(this); +} + +TreeElement.prototype.expandRecursively = function(maxDepth) +{ + var item = this; + var info = {}; + var depth = 0; + + // The Inspector uses TreeOutlines to represents object properties, so recursive expansion + // in some case can be infinite, since JavaScript objects can hold circular references. + // So default to a recursion cap of 3 levels, since that gives fairly good results. + if (typeof maxDepth === "undefined" || typeof maxDepth === "null") + maxDepth = 3; + + while (item) { + if (depth < maxDepth) + item.expand(); + item = item.traverseNextTreeElement(false, this, (depth >= maxDepth), info); + depth += info.depthChange; + } +} + +TreeElement.prototype.hasAncestor = function(ancestor) { + if (!ancestor) + return false; + + var currentNode = this.parent; + while (currentNode) { + if (ancestor === currentNode) + return true; + currentNode = currentNode.parent; + } + + return false; +} + +TreeElement.prototype.reveal = function() +{ + var currentAncestor = this.parent; + while (currentAncestor && !currentAncestor.root) { + if (!currentAncestor.expanded) + currentAncestor.expand(); + currentAncestor = currentAncestor.parent; + } + + if (this.onreveal) + this.onreveal(this); +} + +TreeElement.prototype.revealed = function() +{ + var currentAncestor = this.parent; + while (currentAncestor && !currentAncestor.root) { + if (!currentAncestor.expanded) + return false; + currentAncestor = currentAncestor.parent; + } + + return true; +} + +TreeElement.prototype.selectOnMouseDown = function(event) +{ + this.select(false, true); +} + +/** + * @param {boolean=} omitFocus + * @param {boolean=} selectedByUser + */ +TreeElement.prototype.select = function(omitFocus, selectedByUser) +{ + if (!this.treeOutline || !this.selectable || this.selected) + return; + + if (this.treeOutline.selectedTreeElement) + this.treeOutline.selectedTreeElement.deselect(); + + this.selected = true; + + if(!omitFocus) + this.treeOutline._childrenListNode.focus(); + + // Focusing on another node may detach "this" from tree. + if (!this.treeOutline) + return; + this.treeOutline.selectedTreeElement = this; + if (this._listItemNode) + this._listItemNode.classList.add("selected"); + + if (this.onselect) + this.onselect(this, selectedByUser); +} + +/** + * @param {boolean=} omitFocus + */ +TreeElement.prototype.revealAndSelect = function(omitFocus) +{ + this.reveal(); + this.select(omitFocus); +} + +/** + * @param {boolean=} supressOnDeselect + */ +TreeElement.prototype.deselect = function(supressOnDeselect) +{ + if (!this.treeOutline || this.treeOutline.selectedTreeElement !== this || !this.selected) + return false; + + this.selected = false; + this.treeOutline.selectedTreeElement = null; + if (this._listItemNode) + this._listItemNode.classList.remove("selected"); + + if (this.ondeselect && !supressOnDeselect) + this.ondeselect(this); + return true; +} + +TreeElement.prototype.onpopulate = function() +{ + // Overriden by subclasses. +} + +/** + * @param {boolean} skipUnrevealed + * @param {(TreeOutline|TreeElement)=} stayWithin + * @param {boolean=} dontPopulate + * @param {Object=} info + * @return {TreeElement} + */ +TreeElement.prototype.traverseNextTreeElement = function(skipUnrevealed, stayWithin, dontPopulate, info) +{ + if (!dontPopulate && this.hasChildren) + this.onpopulate(); + + if (info) + info.depthChange = 0; + + var element = skipUnrevealed ? (this.revealed() ? this.children[0] : null) : this.children[0]; + if (element && (!skipUnrevealed || (skipUnrevealed && this.expanded))) { + if (info) + info.depthChange = 1; + return element; + } + + if (this === stayWithin) + return null; + + element = skipUnrevealed ? (this.revealed() ? this.nextSibling : null) : this.nextSibling; + if (element) + return element; + + element = this; + while (element && !element.root && !(skipUnrevealed ? (element.revealed() ? element.nextSibling : null) : element.nextSibling) && element.parent !== stayWithin) { + if (info) + info.depthChange -= 1; + element = element.parent; + } + + if (!element) + return null; + + return (skipUnrevealed ? (element.revealed() ? element.nextSibling : null) : element.nextSibling); +} + +/** + * @param {boolean} skipUnrevealed + * @param {boolean=} dontPopulate + * @return {TreeElement} + */ +TreeElement.prototype.traversePreviousTreeElement = function(skipUnrevealed, dontPopulate) +{ + var element = skipUnrevealed ? (this.revealed() ? this.previousSibling : null) : this.previousSibling; + if (!dontPopulate && element && element.hasChildren) + element.onpopulate(); + + while (element && (skipUnrevealed ? (element.revealed() && element.expanded ? element.children[element.children.length - 1] : null) : element.children[element.children.length - 1])) { + if (!dontPopulate && element.hasChildren) + element.onpopulate(); + element = (skipUnrevealed ? (element.revealed() && element.expanded ? element.children[element.children.length - 1] : null) : element.children[element.children.length - 1]); + } + + if (element) + return element; + + if (!this.parent || this.parent.root) + return null; + + return this.parent; +} + +TreeElement.prototype.isEventWithinDisclosureTriangle = function(event) +{ + // FIXME: We should not use getComputedStyle(). For that we need to get rid of using ::before for disclosure triangle. (http://webk.it/74446) + var computedLeftPadding = window.getComputedStyle(this._listItemNode).getPropertyCSSValue("padding-left").getFloatValue(CSSPrimitiveValue.CSS_PX); + var left = this._listItemNode.totalOffsetLeft() + computedLeftPadding; + return event.pageX >= left && event.pageX <= left + this.arrowToggleWidth && this.hasChildren; +} +/* inspector.js */ + +/* + * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2007 Matt Lilek (pewtermoose@gmail.com). + * Copyright (C) 2009 Joseph Pecoraro + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +var WebInspector = { + _createPanels: function() + { + this.panels = {}; + WebInspector.inspectorView = new WebInspector.InspectorView(); + var parentElement = document.getElementById("main"); + WebInspector.inspectorView.show(parentElement); + WebInspector.inspectorView.addEventListener(WebInspector.InspectorView.Events.PanelSelected, this._panelSelected, this); + + if (WebInspector.WorkerManager.isWorkerFrontend()) { + this.panels.scripts = new WebInspector.ScriptsPanel(this.debuggerPresentationModel); + this.panels.console = new WebInspector.ConsolePanel(); + return; + } + var hiddenPanels = (InspectorFrontendHost.hiddenPanels() || "").split(','); + if (hiddenPanels.indexOf("elements") === -1) + this.panels.elements = new WebInspector.ElementsPanel(); + if (hiddenPanels.indexOf("resources") === -1) + this.panels.resources = new WebInspector.ResourcesPanel(); + if (hiddenPanels.indexOf("network") === -1) + this.panels.network = new WebInspector.NetworkPanel(); + if (hiddenPanels.indexOf("scripts") === -1) + this.panels.scripts = new WebInspector.ScriptsPanel(this.debuggerPresentationModel); + if (hiddenPanels.indexOf("timeline") === -1) + this.panels.timeline = new WebInspector.TimelinePanel(); + if (hiddenPanels.indexOf("profiles") === -1) + this.panels.profiles = new WebInspector.ProfilesPanel(); + if (hiddenPanels.indexOf("audits") === -1) + this.panels.audits = new WebInspector.AuditsPanel(); + if (hiddenPanels.indexOf("console") === -1) + this.panels.console = new WebInspector.ConsolePanel(); + }, + + _panelSelected: function() + { + this._toggleConsoleButton.disabled = WebInspector.inspectorView.currentPanel() === WebInspector.panels.console; + }, + + _createGlobalStatusBarItems: function() + { + this._dockToggleButton = new WebInspector.StatusBarButton(this._dockButtonTitle(), "dock-status-bar-item"); + this._dockToggleButton.addEventListener("click", this._toggleAttach.bind(this), false); + this._dockToggleButton.toggled = !this.attached; + + this._settingsButton = new WebInspector.StatusBarButton(WebInspector.UIString("Settings"), "settings-status-bar-item"); + this._settingsButton.addEventListener("click", this._toggleSettings.bind(this), false); + + var anchoredStatusBar = document.getElementById("anchored-status-bar-items"); + anchoredStatusBar.appendChild(this._dockToggleButton.element); + + this._toggleConsoleButton = new WebInspector.StatusBarButton(WebInspector.UIString("Show console."), "console-status-bar-item"); + this._toggleConsoleButton.addEventListener("click", this._toggleConsoleButtonClicked.bind(this), false); + anchoredStatusBar.appendChild(this._toggleConsoleButton.element); + + if (this.panels.elements) + anchoredStatusBar.appendChild(this.panels.elements.nodeSearchButton.element); + anchoredStatusBar.appendChild(this._settingsButton.element); + }, + + _dockButtonTitle: function() + { + return this.attached ? WebInspector.UIString("Undock into separate window.") : WebInspector.UIString("Dock to main window."); + }, + + _toggleAttach: function() + { + if (!this._attached) { + InspectorFrontendHost.requestAttachWindow(); + WebInspector.userMetrics.WindowDocked.record(); + } else { + InspectorFrontendHost.requestDetachWindow(); + WebInspector.userMetrics.WindowUndocked.record(); + } + }, + + _toggleConsoleButtonClicked: function() + { + if (this._toggleConsoleButton.disabled) + return; + + this._toggleConsoleButton.toggled = !this._toggleConsoleButton.toggled; + + var animationType = window.event && window.event.shiftKey ? WebInspector.Drawer.AnimationType.Slow : WebInspector.Drawer.AnimationType.Normal; + if (this._toggleConsoleButton.toggled) { + this._toggleConsoleButton.title = WebInspector.UIString("Hide console."); + this.drawer.show(this.consoleView, animationType); + this._consoleWasShown = true; + } else { + this._toggleConsoleButton.title = WebInspector.UIString("Show console."); + this.drawer.hide(animationType); + delete this._consoleWasShown; + } + }, + + _escPressed: function() + { + // If drawer was open with some view other than console then just close it. + if (!this._consoleWasShown && WebInspector.drawer.visible) + this.drawer.hide(WebInspector.Drawer.AnimationType.Immediately); + else + this._toggleConsoleButtonClicked(); + }, + + /** + * @param {WebInspector.View} view + */ + showViewInDrawer: function(view) + { + this._toggleConsoleButton.title = WebInspector.UIString("Hide console."); + this._toggleConsoleButton.toggled = false; + this.drawer.show(view, WebInspector.Drawer.AnimationType.Immediately); + }, + + _toggleSettings: function() + { + this._settingsButton.toggled = !this._settingsButton.toggled; + if (this._settingsButton.toggled) + this._showSettingsScreen(); + else + this._hideSettingsScreen(); + }, + + _showSettingsScreen: function() + { + function onhide() + { + this._settingsButton.toggled = false; + delete this._settingsScreen; + } + + if (!this._settingsScreen) { + this._settingsScreen = new WebInspector.SettingsScreen(); + this._settingsScreen.show(onhide.bind(this)); + } + }, + + _hideSettingsScreen: function() + { + if (this._settingsScreen) + this._settingsScreen.hide(); + }, + + get attached() + { + return this._attached; + }, + + set attached(x) + { + if (this._attached === x) + return; + + this._attached = x; + + if (this._dockToggleButton) { + this._dockToggleButton.title = this._dockButtonTitle(); + this._dockToggleButton.toggled = !x; + } + + if (x) + document.body.removeStyleClass("detached"); + else + document.body.addStyleClass("detached"); + + this._setCompactMode(x && !WebInspector.settings.dockToRight.get()); + }, + + isCompactMode: function() + { + return this.attached && !WebInspector.settings.dockToRight.get(); + }, + + _setCompactMode: function(x) + { + var body = document.body; + if (x) + body.addStyleClass("compact"); + else + body.removeStyleClass("compact"); + + // This may be called before doLoadedDone, hence the bulk of inspector objects may + // not be created yet. + if (WebInspector.toolbar) + WebInspector.toolbar.compact = x; + + if (WebInspector.searchController) + WebInspector.searchController.updateSearchLabel(); + + if (WebInspector.drawer) + WebInspector.drawer.resize(); + }, + + _updateErrorAndWarningCounts: function() + { + var errorWarningElement = document.getElementById("error-warning-count"); + if (!errorWarningElement) + return; + + var errors = WebInspector.console.errors; + var warnings = WebInspector.console.warnings; + if (!errors && !warnings) { + errorWarningElement.addStyleClass("hidden"); + return; + } + + errorWarningElement.removeStyleClass("hidden"); + + errorWarningElement.removeChildren(); + + if (errors) { + var errorImageElement = document.createElement("img"); + errorImageElement.id = "error-count-img"; + errorWarningElement.appendChild(errorImageElement); + var errorElement = document.createElement("span"); + errorElement.id = "error-count"; + errorElement.textContent = errors; + errorWarningElement.appendChild(errorElement); + } + + if (warnings) { + var warningsImageElement = document.createElement("img"); + warningsImageElement.id = "warning-count-img"; + errorWarningElement.appendChild(warningsImageElement); + var warningsElement = document.createElement("span"); + warningsElement.id = "warning-count"; + warningsElement.textContent = warnings; + errorWarningElement.appendChild(warningsElement); + } + + if (errors) { + if (warnings) { + if (errors == 1) { + if (warnings == 1) + errorWarningElement.title = WebInspector.UIString("%d error, %d warning", errors, warnings); + else + errorWarningElement.title = WebInspector.UIString("%d error, %d warnings", errors, warnings); + } else if (warnings == 1) + errorWarningElement.title = WebInspector.UIString("%d errors, %d warning", errors, warnings); + else + errorWarningElement.title = WebInspector.UIString("%d errors, %d warnings", errors, warnings); + } else if (errors == 1) + errorWarningElement.title = WebInspector.UIString("%d error", errors); + else + errorWarningElement.title = WebInspector.UIString("%d errors", errors); + } else if (warnings == 1) + errorWarningElement.title = WebInspector.UIString("%d warning", warnings); + else if (warnings) + errorWarningElement.title = WebInspector.UIString("%d warnings", warnings); + else + errorWarningElement.title = null; + }, + + networkResourceById: function(id) + { + return this.panels.network.resourceById(id); + }, + + get inspectedPageDomain() + { + var parsedURL = WebInspector.inspectedPageURL && WebInspector.inspectedPageURL.asParsedURL(); + return parsedURL ? parsedURL.host : ""; + }, + + _initializeCapability: function(name, callback, error, result) + { + Capabilities[name] = result; + if (callback) + callback(); + } +} + +WebInspector.Events = { + InspectorClosing: "InspectorClosing" +} + +{(function parseQueryParameters() +{ + WebInspector.queryParamsObject = {}; + var queryParams = window.location.search; + if (!queryParams) + return; + var params = queryParams.substring(1).split("&"); + for (var i = 0; i < params.length; ++i) { + var pair = params[i].split("="); + WebInspector.queryParamsObject[pair[0]] = pair[1]; + } +})();} + +WebInspector.loaded = function() +{ + InspectorBackend.loadFromJSONIfNeeded(); + if ("page" in WebInspector.queryParamsObject) { + var page = WebInspector.queryParamsObject.page; + var host = "host" in WebInspector.queryParamsObject ? WebInspector.queryParamsObject.host : window.location.host; + WebInspector.socket = new WebSocket("ws://" + host + "/devtools/page/" + page); + WebInspector.socket.onmessage = function(message) { InspectorBackend.dispatch(message.data); } + WebInspector.socket.onerror = function(error) { console.error(error); } + WebInspector.socket.onopen = function() { + InspectorFrontendHost.sendMessageToBackend = WebInspector.socket.send.bind(WebInspector.socket); + WebInspector.doLoadedDone(); + } + return; + } + WebInspector.doLoadedDone(); +} + +WebInspector.doLoadedDone = function() +{ + // Install styles and themes + WebInspector.installPortStyles(); + if (WebInspector.socket) + document.body.addStyleClass("remote"); + + if (WebInspector.queryParamsObject.toolbarColor && WebInspector.queryParamsObject.textColor) + WebInspector.setToolbarColors(WebInspector.queryParamsObject.toolbarColor, WebInspector.queryParamsObject.textColor); + + InspectorFrontendHost.loaded(); + WebInspector.WorkerManager.loaded(); + + DebuggerAgent.causesRecompilation(WebInspector._initializeCapability.bind(WebInspector, "debuggerCausesRecompilation", null)); + DebuggerAgent.supportsNativeBreakpoints(WebInspector._initializeCapability.bind(WebInspector, "nativeInstrumentationEnabled", null)); + ProfilerAgent.causesRecompilation(WebInspector._initializeCapability.bind(WebInspector, "profilerCausesRecompilation", null)); + ProfilerAgent.isSampling(WebInspector._initializeCapability.bind(WebInspector, "samplingCPUProfiler", null)); + ProfilerAgent.hasHeapProfiler(WebInspector._initializeCapability.bind(WebInspector, "heapProfilerPresent", WebInspector._doLoadedDoneWithCapabilities.bind(WebInspector))); +} + +WebInspector._doLoadedDoneWithCapabilities = function() +{ + WebInspector.shortcutsScreen = new WebInspector.ShortcutsScreen(); + this._registerShortcuts(); + + // set order of some sections explicitly + WebInspector.shortcutsScreen.section(WebInspector.UIString("Console")); + WebInspector.shortcutsScreen.section(WebInspector.UIString("Elements Panel")); + + this.console = new WebInspector.ConsoleModel(); + this.console.addEventListener(WebInspector.ConsoleModel.Events.ConsoleCleared, this._updateErrorAndWarningCounts, this); + this.console.addEventListener(WebInspector.ConsoleModel.Events.MessageAdded, this._updateErrorAndWarningCounts, this); + this.console.addEventListener(WebInspector.ConsoleModel.Events.RepeatCountUpdated, this._updateErrorAndWarningCounts, this); + + this.debuggerModel = new WebInspector.DebuggerModel(); + this.debuggerPresentationModel = new WebInspector.DebuggerPresentationModel(); + + this.drawer = new WebInspector.Drawer(); + this.consoleView = new WebInspector.ConsoleView(WebInspector.WorkerManager.isWorkerFrontend()); + + this.networkManager = new WebInspector.NetworkManager(); + this.resourceTreeModel = new WebInspector.ResourceTreeModel(this.networkManager); + this.networkLog = new WebInspector.NetworkLog(); + this.domAgent = new WebInspector.DOMAgent(); + new WebInspector.JavaScriptContextManager(this.resourceTreeModel, this.consoleView); + + InspectorBackend.registerInspectorDispatcher(this); + + this.cssModel = new WebInspector.CSSStyleModel(); + this.timelineManager = new WebInspector.TimelineManager(); + InspectorBackend.registerDatabaseDispatcher(new WebInspector.DatabaseDispatcher()); + InspectorBackend.registerDOMStorageDispatcher(new WebInspector.DOMStorageDispatcher()); + + this.searchController = new WebInspector.SearchController(); + this.advancedSearchController = new WebInspector.AdvancedSearchController(); + + if (Capabilities.nativeInstrumentationEnabled) + this.domBreakpointsSidebarPane = new WebInspector.DOMBreakpointsSidebarPane(); + + this._createPanels(); + this._createGlobalStatusBarItems(); + + + this.toolbar = new WebInspector.Toolbar(); + WebInspector._installDockToRight(); + + for (var panelName in this.panels) + this.addPanel(this.panels[panelName]); + + this.addMainEventListeners(document); + + window.addEventListener("resize", this.windowResize.bind(this), true); + + var errorWarningCount = document.getElementById("error-warning-count"); + errorWarningCount.addEventListener("click", this.showConsole.bind(this), false); + this._updateErrorAndWarningCounts(); + + var autoselectPanel = WebInspector.UIString("a panel chosen automatically"); + var openAnchorLocationSetting = WebInspector.settings.createSetting("openLinkHandler", autoselectPanel); + this.openAnchorLocationRegistry = new WebInspector.HandlerRegistry(openAnchorLocationSetting); + this.openAnchorLocationRegistry.registerHandler(autoselectPanel, function() { return false; }); + + this.extensionServer.initExtensions(); + + this.console.enableAgent(); + + function showInitialPanel() + { + if (!WebInspector.inspectorView.currentPanel()) + WebInspector.showPanel(WebInspector.settings.lastActivePanel.get()); + } + + InspectorAgent.enable(showInitialPanel); + DatabaseAgent.enable(); + DOMStorageAgent.enable(); + + WebInspector.CSSCompletions.requestCSSNameCompletions(); + WebInspector.WorkerManager.loadCompleted(); + InspectorFrontendAPI.loadCompleted(); +} + +WebInspector._installDockToRight = function() +{ + // Re-use Settings infrastructure for the dock-to-right settings UI + WebInspector.settings.dockToRight.set(WebInspector.queryParamsObject.dockSide === "right"); + + if (WebInspector.settings.dockToRight.get()) + document.body.addStyleClass("dock-to-right"); + + if (WebInspector.attached) + WebInspector._setCompactMode(!WebInspector.settings.dockToRight.get()); + + WebInspector.settings.dockToRight.addChangeListener(listener.bind(this)); + + function listener(event) + { + var value = WebInspector.settings.dockToRight.get(); + if (value) { + InspectorFrontendHost.requestSetDockSide("right"); + document.body.addStyleClass("dock-to-right"); + } else { + InspectorFrontendHost.requestSetDockSide("bottom"); + document.body.removeStyleClass("dock-to-right"); + } + if (WebInspector.attached) + WebInspector._setCompactMode(!value); + } +} + +WebInspector.addPanel = function(panel) +{ + WebInspector.inspectorView.addPanel(panel); +} + +var windowLoaded = function() +{ + var localizedStringsURL = InspectorFrontendHost.localizedStringsURL(); + if (localizedStringsURL) { + var localizedStringsScriptElement = document.createElement("script"); + localizedStringsScriptElement.addEventListener("load", WebInspector.loaded.bind(WebInspector), false); + localizedStringsScriptElement.type = "text/javascript"; + localizedStringsScriptElement.src = localizedStringsURL; + document.head.appendChild(localizedStringsScriptElement); + } else + WebInspector.loaded(); + + WebInspector.setAttachedWindow(WebInspector.queryParamsObject.docked === "true"); + + window.removeEventListener("DOMContentLoaded", windowLoaded, false); + delete windowLoaded; +}; + +window.addEventListener("DOMContentLoaded", windowLoaded, false); + +// We'd like to enforce asynchronous interaction between the inspector controller and the frontend. +// It is needed to prevent re-entering the backend code. +// Also, native dispatches do not guarantee setTimeouts to be serialized, so we +// enforce serialization using 'messagesToDispatch' queue. It is also important that JSC debugger +// tests require that each command was dispatch within individual timeout callback, so we don't batch them. + +var messagesToDispatch = []; + +WebInspector.dispatchQueueIsEmpty = function() { + return messagesToDispatch.length == 0; +} + +WebInspector.dispatch = function(message) { + messagesToDispatch.push(message); + setTimeout(function() { + InspectorBackend.dispatch(messagesToDispatch.shift()); + }, 0); +} + +WebInspector.dispatchMessageFromBackend = function(messageObject) +{ + WebInspector.dispatch(messageObject); +} + +WebInspector.windowResize = function(event) +{ + WebInspector.inspectorView.doResize(); + WebInspector.drawer.resize(); + WebInspector.toolbar.resize(); +} + +WebInspector.setAttachedWindow = function(attached) +{ + this.attached = attached; +} + +WebInspector.close = function(event) +{ + if (this._isClosing) + return; + this._isClosing = true; + this.notifications.dispatchEventToListeners(WebInspector.Events.InspectorClosing); + InspectorFrontendHost.closeWindow(); +} + +WebInspector.documentClick = function(event) +{ + var anchor = event.target.enclosingNodeOrSelfWithNodeName("a"); + if (!anchor || anchor.target === "_blank") + return; + + // Prevent the link from navigating, since we don't do any navigation by following links normally. + event.preventDefault(); + event.stopPropagation(); + + function followLink() + { + if (WebInspector._showAnchorLocation(anchor)) + return; + + const profileMatch = WebInspector.ProfileType.URLRegExp.exec(anchor.href); + if (profileMatch) { + WebInspector.showProfileForURL(anchor.href); + return; + } + + var parsedURL = anchor.href.asParsedURL(); + if (parsedURL && parsedURL.scheme === "webkit-link-action") { + if (parsedURL.host === "show-panel") { + var panel = parsedURL.path.substring(1); + if (WebInspector.panels[panel]) + WebInspector.showPanel(panel); + } + return; + } + + WebInspector.showPanel("resources"); + } + + if (WebInspector.followLinkTimeout) + clearTimeout(WebInspector.followLinkTimeout); + + if (anchor.preventFollowOnDoubleClick) { + // Start a timeout if this is the first click, if the timeout is canceled + // before it fires, then a double clicked happened or another link was clicked. + if (event.detail === 1) + WebInspector.followLinkTimeout = setTimeout(followLink, 333); + return; + } + + followLink(); +} + +WebInspector.openResource = function(resourceURL, inResourcesPanel) +{ + var resource = WebInspector.resourceForURL(resourceURL); + if (inResourcesPanel && resource) { + WebInspector.panels.resources.showResource(resource); + WebInspector.showPanel("resources"); + } else + InspectorFrontendHost.openInNewTab(resourceURL); +} + +WebInspector.openRequestInNetworkPanel = function(resource) +{ + WebInspector.showPanel("network"); + WebInspector.panels.network.revealAndHighlightResource(resource); +} + +WebInspector._registerShortcuts = function() +{ + var shortcut = WebInspector.KeyboardShortcut; + var section = WebInspector.shortcutsScreen.section(WebInspector.UIString("All Panels")); + var keys = [ + shortcut.shortcutToString("]", shortcut.Modifiers.CtrlOrMeta), + shortcut.shortcutToString("[", shortcut.Modifiers.CtrlOrMeta) + ]; + section.addRelatedKeys(keys, WebInspector.UIString("Next/previous panel")); + section.addKey(shortcut.shortcutToString(shortcut.Keys.Esc), WebInspector.UIString("Toggle console")); + section.addKey(shortcut.shortcutToString("f", shortcut.Modifiers.CtrlOrMeta), WebInspector.UIString("Search")); + + var advancedSearchShortcut = WebInspector.AdvancedSearchController.createShortcut(); + section.addKey(advancedSearchShortcut.name, WebInspector.UIString("Search across all scripts")); + + if (WebInspector.isMac()) { + keys = [ + shortcut.shortcutToString("g", shortcut.Modifiers.Meta), + shortcut.shortcutToString("g", shortcut.Modifiers.Meta | shortcut.Modifiers.Shift) + ]; + section.addRelatedKeys(keys, WebInspector.UIString("Find next/previous")); + } + + var goToShortcut = WebInspector.GoToLineDialog.createShortcut(); + section.addKey(goToShortcut.name, WebInspector.UIString("Go to Line")); +} + +WebInspector.documentKeyDown = function(event) +{ + const helpKey = WebInspector.isMac() ? "U+003F" : "U+00BF"; // "?" for both platforms + + if (event.keyIdentifier === "F1" || + (event.keyIdentifier === helpKey && event.shiftKey && (!WebInspector.isInEditMode(event) || event.metaKey))) { + WebInspector.shortcutsScreen.show(); + event.stopPropagation(); + event.preventDefault(); + return; + } + + if (WebInspector.currentFocusElement() && WebInspector.currentFocusElement().handleKeyEvent) { + WebInspector.currentFocusElement().handleKeyEvent(event); + if (event.handled) { + event.preventDefault(); + return; + } + } + + if (WebInspector.inspectorView.currentPanel()) { + WebInspector.inspectorView.currentPanel().handleShortcut(event); + if (event.handled) { + event.preventDefault(); + return; + } + } + + WebInspector.searchController.handleShortcut(event); + WebInspector.advancedSearchController.handleShortcut(event); + if (event.handled) { + event.preventDefault(); + return; + } + + var isMac = WebInspector.isMac(); + switch (event.keyIdentifier) { + case "U+001B": // Escape key + if (event.target.hasStyleClass("text-prompt") || !WebInspector.isInEditMode(event)) { + event.preventDefault(); + this._escPressed(); + } + break; + case "U+0052": // R key + if (WebInspector.isInEditMode(event)) + return; + if ((event.metaKey && isMac) || (event.ctrlKey && !isMac)) { + PageAgent.reload(event.shiftKey); + event.preventDefault(); + } + break; + case "F5": + if (!isMac && !WebInspector.isInEditMode(event)) { + PageAgent.reload(event.ctrlKey || event.shiftKey); + event.preventDefault(); + } + break; + } +} + +WebInspector.documentCanCopy = function(event) +{ + if (WebInspector.inspectorView.currentPanel() && WebInspector.inspectorView.currentPanel().handleCopyEvent) + event.preventDefault(); +} + +WebInspector.documentCopy = function(event) +{ + if (WebInspector.inspectorView.currentPanel() && WebInspector.inspectorView.currentPanel().handleCopyEvent) + WebInspector.inspectorView.currentPanel().handleCopyEvent(event); +} + +WebInspector.contextMenuEventFired = function(event) +{ + if (event.handled || event.target.hasStyleClass("popup-glasspane")) + event.preventDefault(); +} + +WebInspector.toggleSearchingForNode = function() +{ + if (this.panels.elements) { + this.showPanel("elements"); + this.panels.elements.toggleSearchingForNode(); + } +} + +WebInspector.showConsole = function() +{ + if (WebInspector._toggleConsoleButton && !WebInspector._toggleConsoleButton.toggled) + WebInspector._toggleConsoleButtonClicked(); +} + +WebInspector.showPanel = function(panel) +{ + if (!(panel in this.panels)) { + if (WebInspector.WorkerManager.isWorkerFrontend()) + panel = "scripts"; + else + panel = "elements"; + } + WebInspector.inspectorView.setCurrentPanel(this.panels[panel]); +} + +WebInspector.bringToFront = function() +{ + InspectorFrontendHost.bringToFront(); +} + +WebInspector.didCreateWorker = function() +{ + var workersPane = WebInspector.panels.scripts.sidebarPanes.workers; + if (workersPane) + workersPane.addWorker.apply(workersPane, arguments); +} + +WebInspector.didDestroyWorker = function() +{ + var workersPane = WebInspector.panels.scripts.sidebarPanes.workers; + if (workersPane) + workersPane.removeWorker.apply(workersPane, arguments); +} + +/** + * @param {string=} messageLevel + * @param {boolean=} showConsole + */ +WebInspector.log = function(message, messageLevel, showConsole) +{ + // remember 'this' for setInterval() callback + var self = this; + + // return indication if we can actually log a message + function isLogAvailable() + { + return WebInspector.ConsoleMessage && WebInspector.RemoteObject && self.console; + } + + // flush the queue of pending messages + function flushQueue() + { + var queued = WebInspector.log.queued; + if (!queued) + return; + + for (var i = 0; i < queued.length; ++i) + logMessage(queued[i]); + + delete WebInspector.log.queued; + } + + // flush the queue if it console is available + // - this function is run on an interval + function flushQueueIfAvailable() + { + if (!isLogAvailable()) + return; + + clearInterval(WebInspector.log.interval); + delete WebInspector.log.interval; + + flushQueue(); + } + + // actually log the message + function logMessage(message) + { + // post the message + var msg = WebInspector.ConsoleMessage.create( + WebInspector.ConsoleMessage.MessageSource.Other, + messageLevel || WebInspector.ConsoleMessage.MessageLevel.Debug, + message); + + self.console.addMessage(msg); + if (showConsole) + WebInspector.showConsole(); + } + + // if we can't log the message, queue it + if (!isLogAvailable()) { + if (!WebInspector.log.queued) + WebInspector.log.queued = []; + + WebInspector.log.queued.push(message); + + if (!WebInspector.log.interval) + WebInspector.log.interval = setInterval(flushQueueIfAvailable, 1000); + + return; + } + + // flush the pending queue if any + flushQueue(); + + // log the message + logMessage(message); +} + +WebInspector.inspect = function(payload, hints) +{ + var object = WebInspector.RemoteObject.fromPayload(payload); + if (object.subtype === "node") { + // Request node from backend and focus it. + WebInspector.inspectorView.setCurrentPanel(WebInspector.panels.elements); + object.pushNodeToFrontend(WebInspector.updateFocusedNode.bind(WebInspector), object.release.bind(object)); + return; + } + + if (hints.databaseId) { + WebInspector.inspectorView.setCurrentPanel(WebInspector.panels.resources); + WebInspector.panels.resources.selectDatabase(hints.databaseId); + } else if (hints.domStorageId) { + WebInspector.inspectorView.setCurrentPanel(WebInspector.panels.resources); + WebInspector.panels.resources.selectDOMStorage(hints.domStorageId); + } + + object.release(); +} + +WebInspector.updateFocusedNode = function(nodeId) +{ + this.panels.elements.revealAndSelectNode(nodeId); +} + +WebInspector.populateResourceContextMenu = function(contextMenu, url, preferredLineNumber) +{ + var registry = WebInspector.openAnchorLocationRegistry; + // Skip 0th handler, as it's 'Use default panel' one. + for (var i = 1; i < registry.handlerNames.length; ++i) { + var handler = registry.handlerNames[i]; + contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Open using %s" : "Open Using %s", handler), + registry.dispatchToHandler.bind(registry, handler, { url: url, preferredLineNumber: preferredLineNumber })); + } +} + +WebInspector._showAnchorLocation = function(anchor) +{ + if (WebInspector.openAnchorLocationRegistry.dispatch({ url: anchor.href, lineNumber: anchor.lineNumber})) + return true; + var preferedPanel = this.panels[anchor.preferredPanel || "resources"]; + if (WebInspector._showAnchorLocationInPanel(anchor, preferedPanel)) + return true; + if (preferedPanel !== this.panels.resources && WebInspector._showAnchorLocationInPanel(anchor, this.panels.resources)) + return true; + return false; +} + +WebInspector._showAnchorLocationInPanel = function(anchor, panel) +{ + if (!panel.canShowAnchorLocation(anchor)) + return false; + + // FIXME: support webkit-html-external-link links here. + if (anchor.hasStyleClass("webkit-html-external-link")) { + anchor.removeStyleClass("webkit-html-external-link"); + anchor.addStyleClass("webkit-html-resource-link"); + } + + this.showPanelForAnchorNavigation(panel); + panel.showAnchorLocation(anchor); + return true; +} + +WebInspector.showPanelForAnchorNavigation = function(panel) +{ + WebInspector.searchController.disableSearchUntilExplicitAction(); + WebInspector.inspectorView.setCurrentPanel(panel); +} + +WebInspector.showProfileForURL = function(url) +{ + WebInspector.showPanel("profiles"); + WebInspector.panels.profiles.showProfileForURL(url); +} + +WebInspector.evaluateInConsole = function(expression) +{ + this.showConsole(); + this.consoleView.evaluateUsingTextPrompt(expression); +} + +WebInspector.addMainEventListeners = function(doc) +{ + doc.addEventListener("keydown", this.documentKeyDown.bind(this), false); + doc.addEventListener("beforecopy", this.documentCanCopy.bind(this), true); + doc.addEventListener("copy", this.documentCopy.bind(this), true); + doc.addEventListener("contextmenu", this.contextMenuEventFired.bind(this), true); + doc.addEventListener("click", this.documentClick.bind(this), true); +} + +WebInspector.frontendReused = function() +{ + this.resourceTreeModel.frontendReused(); +} + +WebInspector._toolbarItemClicked = function(event) +{ + var toolbarItem = event.currentTarget; + WebInspector.inspectorView.setCurrentPanel(toolbarItem.panel); +} +/* UIUtils.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2007 Matt Lilek (pewtermoose@gmail.com). + * Copyright (C) 2009 Joseph Pecoraro + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.elementDragStart = function(element, dividerDrag, elementDragEnd, event, cursor) +{ + if (WebInspector._elementDraggingEventListener || WebInspector._elementEndDraggingEventListener) + WebInspector.elementDragEnd(event); + + if (element) { + // Install glass pane + if (WebInspector._elementDraggingGlassPane) + WebInspector._elementDraggingGlassPane.parentElement.removeChild(WebInspector._elementDraggingGlassPane); + + var glassPane = document.createElement("div"); + glassPane.style.cssText = "position:absolute;top:0;bottom:0;left:0;right:0;opacity:0;z-index:1"; + glassPane.id = "glass-pane-for-drag"; + element.ownerDocument.body.appendChild(glassPane); + WebInspector._elementDraggingGlassPane = glassPane; + } + + WebInspector._elementDraggingEventListener = dividerDrag; + WebInspector._elementEndDraggingEventListener = elementDragEnd; + + var targetDocument = event.target.ownerDocument; + targetDocument.addEventListener("mousemove", dividerDrag, true); + targetDocument.addEventListener("mouseup", elementDragEnd, true); + + targetDocument.body.style.cursor = cursor; + + event.preventDefault(); +} + +WebInspector.elementDragEnd = function(event) +{ + var targetDocument = event.target.ownerDocument; + targetDocument.removeEventListener("mousemove", WebInspector._elementDraggingEventListener, true); + targetDocument.removeEventListener("mouseup", WebInspector._elementEndDraggingEventListener, true); + + targetDocument.body.style.removeProperty("cursor"); + + if (WebInspector._elementDraggingGlassPane) + WebInspector._elementDraggingGlassPane.parentElement.removeChild(WebInspector._elementDraggingGlassPane); + + delete WebInspector._elementDraggingGlassPane; + delete WebInspector._elementDraggingEventListener; + delete WebInspector._elementEndDraggingEventListener; + + event.preventDefault(); +} + +WebInspector.animateStyle = function(animations, duration, callback) +{ + var interval; + var complete = 0; + var hasCompleted = false; + + const intervalDuration = (1000 / 30); // 30 frames per second. + const animationsLength = animations.length; + const propertyUnit = {opacity: ""}; + const defaultUnit = "px"; + + function cubicInOut(t, b, c, d) + { + if ((t/=d/2) < 1) return c/2*t*t*t + b; + return c/2*((t-=2)*t*t + 2) + b; + } + + // Pre-process animations. + for (var i = 0; i < animationsLength; ++i) { + var animation = animations[i]; + var element = null, start = null, end = null, key = null; + for (key in animation) { + if (key === "element") + element = animation[key]; + else if (key === "start") + start = animation[key]; + else if (key === "end") + end = animation[key]; + } + + if (!element || !end) + continue; + + if (!start) { + var computedStyle = element.ownerDocument.defaultView.getComputedStyle(element); + start = {}; + for (key in end) + start[key] = parseInt(computedStyle.getPropertyValue(key), 10); + animation.start = start; + } else + for (key in start) + element.style.setProperty(key, start[key] + (key in propertyUnit ? propertyUnit[key] : defaultUnit)); + } + + function animateLoop() + { + if (hasCompleted) + return; + + // Advance forward. + complete += intervalDuration; + var next = complete + intervalDuration; + + // Make style changes. + for (var i = 0; i < animationsLength; ++i) { + var animation = animations[i]; + var element = animation.element; + var start = animation.start; + var end = animation.end; + if (!element || !end) + continue; + + var style = element.style; + for (key in end) { + var endValue = end[key]; + if (next < duration) { + var startValue = start[key]; + var newValue = cubicInOut(complete, startValue, endValue - startValue, duration); + style.setProperty(key, newValue + (key in propertyUnit ? propertyUnit[key] : defaultUnit)); + } else + style.setProperty(key, endValue + (key in propertyUnit ? propertyUnit[key] : defaultUnit)); + } + } + + // End condition. + if (complete >= duration) { + hasCompleted = true; + clearInterval(interval); + if (callback) + callback(); + } + } + + function forceComplete() + { + if (hasCompleted) + return; + + complete = duration; + animateLoop(); + } + + function cancel() + { + hasCompleted = true; + clearInterval(interval); + } + + interval = setInterval(animateLoop, intervalDuration); + return { + cancel: cancel, + forceComplete: forceComplete + }; +} + +WebInspector.isBeingEdited = function(element) +{ + return element.__editing; +} + +WebInspector.markBeingEdited = function(element, value) +{ + if (value) { + if (element.__editing) + return false; + element.__editing = true; + WebInspector.__editingCount = (WebInspector.__editingCount || 0) + 1; + } else { + if (!element.__editing) + return false; + delete element.__editing; + --WebInspector.__editingCount; + } + return true; +} + +WebInspector.isInEditMode = function(event) +{ + if (WebInspector.__editingCount > 0) + return true; + if (event.target.nodeName === "INPUT") + return true; + if (event.target.enclosingNodeOrSelfWithClass("text-prompt")) + return true; + return false; +} + +/** + * @constructor + * @param {function(Element,string,string,*,string)} commitHandler + * @param {function(Element,*)} cancelHandler + * @param {*=} context + */ +WebInspector.EditingConfig = function(commitHandler, cancelHandler, context) +{ + this.commitHandler = commitHandler; + this.cancelHandler = cancelHandler + this.context = context; + + /** + * Handles the "paste" event, return values are the same as those for customFinishHandler + * @type {function(Element)|undefined} + */ + this.pasteHandler; + + /** + * Whether the edited element is multiline + * @type {boolean|undefined} + */ + this.multiline; + + /** + * Custom finish handler for the editing session (invoked on keydown) + * @type {function(Element,*)|undefined} + */ + this.customFinishHandler; +} + +WebInspector.EditingConfig.prototype = { + setPasteHandler: function(pasteHandler) + { + this.pasteHandler = pasteHandler; + }, + + setMultiline: function(multiline) + { + this.multiline = multiline; + }, + + setCustomFinishHandler: function(customFinishHandler) + { + this.customFinishHandler = customFinishHandler; + } +} + +/** + * @param {Element} element + * @param {WebInspector.EditingConfig=} config + */ +WebInspector.startEditing = function(element, config) +{ + if (!WebInspector.markBeingEdited(element, true)) + return; + + config = config || new WebInspector.EditingConfig(function() {}, function() {}); + var committedCallback = config.commitHandler; + var cancelledCallback = config.cancelHandler; + var pasteCallback = config.pasteHandler; + var context = config.context; + var oldText = getContent(element); + var moveDirection = ""; + + element.addStyleClass("editing"); + + var oldTabIndex = element.tabIndex; + if (element.tabIndex < 0) + element.tabIndex = 0; + + function blurEventListener() { + editingCommitted.call(element); + } + + function getContent(element) { + if (element.tagName === "INPUT" && element.type === "text") + return element.value; + else + return element.textContent; + } + + /** @this {Element} */ + function cleanUpAfterEditing() + { + WebInspector.markBeingEdited(element, false); + + this.removeStyleClass("editing"); + this.tabIndex = oldTabIndex; + this.scrollTop = 0; + this.scrollLeft = 0; + + element.removeEventListener("blur", blurEventListener, false); + element.removeEventListener("keydown", keyDownEventListener, true); + if (pasteCallback) + element.removeEventListener("paste", pasteEventListener, true); + + WebInspector.restoreFocusFromElement(element); + } + + /** @this {Element} */ + function editingCancelled() + { + if (this.tagName === "INPUT" && this.type === "text") + this.value = oldText; + else + this.textContent = oldText; + + cleanUpAfterEditing.call(this); + + cancelledCallback(this, context); + } + + /** @this {Element} */ + function editingCommitted() + { + cleanUpAfterEditing.call(this); + + committedCallback(this, getContent(this), oldText, context, moveDirection); + } + + function defaultFinishHandler(event) + { + var isMetaOrCtrl = WebInspector.isMac() ? + event.metaKey && !event.shiftKey && !event.ctrlKey && !event.altKey : + event.ctrlKey && !event.shiftKey && !event.metaKey && !event.altKey; + if (isEnterKey(event) && (event.isMetaOrCtrlForTest || !config.multiline || isMetaOrCtrl)) + return "commit"; + else if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code || event.keyIdentifier === "U+001B") + return "cancel"; + else if (event.keyIdentifier === "U+0009") // Tab key + return "move-" + (event.shiftKey ? "backward" : "forward"); + } + + function handleEditingResult(result, event) + { + if (result === "commit") { + editingCommitted.call(element); + event.preventDefault(); + event.stopPropagation(); + } else if (result === "cancel") { + editingCancelled.call(element); + event.preventDefault(); + event.stopPropagation(); + } else if (result && result.indexOf("move-") === 0) { + moveDirection = result.substring(5); + if (event.keyIdentifier !== "U+0009") + blurEventListener(); + } + } + + function pasteEventListener(event) + { + var result = pasteCallback(event); + handleEditingResult(result, event); + } + + function keyDownEventListener(event) + { + var handler = config.customFinishHandler || defaultFinishHandler; + var result = handler(event); + handleEditingResult(result, event); + } + + element.addEventListener("blur", blurEventListener, false); + element.addEventListener("keydown", keyDownEventListener, true); + if (pasteCallback) + element.addEventListener("paste", pasteEventListener, true); + + WebInspector.setCurrentFocusElement(element); + return { + cancel: editingCancelled.bind(element), + commit: editingCommitted.bind(element) + }; +} + +/** + * @param {boolean=} higherResolution + */ +Number.secondsToString = function(seconds, higherResolution) +{ + if (seconds === 0) + return "0"; + + var ms = seconds * 1000; + if (higherResolution && ms < 1000) + return WebInspector.UIString("%.3fms", ms); + else if (ms < 1000) + return WebInspector.UIString("%.0fms", ms); + + if (seconds < 60) + return WebInspector.UIString("%.2fs", seconds); + + var minutes = seconds / 60; + if (minutes < 60) + return WebInspector.UIString("%.1fmin", minutes); + + var hours = minutes / 60; + if (hours < 24) + return WebInspector.UIString("%.1fhrs", hours); + + var days = hours / 24; + return WebInspector.UIString("%.1f days", days); +} + +/** + * @param {boolean=} higherResolution + */ +Number.bytesToString = function(bytes, higherResolution) +{ + if (typeof higherResolution === "undefined") + higherResolution = true; + + if (bytes < 1024) + return WebInspector.UIString("%.0fB", bytes); + + var kilobytes = bytes / 1024; + if (higherResolution && kilobytes < 1024) + return WebInspector.UIString("%.2fKB", kilobytes); + else if (kilobytes < 1024) + return WebInspector.UIString("%.0fKB", kilobytes); + + var megabytes = kilobytes / 1024; + if (higherResolution) + return WebInspector.UIString("%.2fMB", megabytes); + else + return WebInspector.UIString("%.0fMB", megabytes); +} + +WebInspector._missingLocalizedStrings = {}; + +/** + * @param {string} string + * @param {...*} vararg + */ +WebInspector.UIString = function(string, vararg) +{ + if (Preferences.localizeUI) { + if (window.localizedStrings && string in window.localizedStrings) + string = window.localizedStrings[string]; + else { + if (!(string in WebInspector._missingLocalizedStrings)) { + console.warn("Localized string \"" + string + "\" not found."); + WebInspector._missingLocalizedStrings[string] = true; + } + + if (Preferences.showMissingLocalizedStrings) + string += " (not localized)"; + } + } + return String.vsprintf(string, Array.prototype.slice.call(arguments, 1)); +} + +WebInspector.useLowerCaseMenuTitles = function() +{ + return WebInspector.platform() === "windows" && Preferences.useLowerCaseMenuTitlesOnWindows; +} + +WebInspector.formatLocalized = function(format, substitutions, formatters, initialValue, append) +{ + return String.format(WebInspector.UIString(format), substitutions, formatters, initialValue, append); +} + +WebInspector.openLinkExternallyLabel = function() +{ + return WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Open link in new tab" : "Open Link in New Tab"); +} + +WebInspector.openInNetworkPanelLabel = function() +{ + return WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Open in network panel" : "Open in Network Panel"); +} + +WebInspector.copyLinkAddressLabel = function() +{ + return WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy link address" : "Copy Link Address"); +} + +WebInspector.platform = function() +{ + if (!WebInspector._platform) + WebInspector._platform = InspectorFrontendHost.platform(); + return WebInspector._platform; +} + +WebInspector.isMac = function() +{ + if (typeof WebInspector._isMac === "undefined") + WebInspector._isMac = WebInspector.platform() === "mac"; + + return WebInspector._isMac; +} + +WebInspector.PlatformFlavor = { + WindowsVista: "windows-vista", + MacTiger: "mac-tiger", + MacLeopard: "mac-leopard", + MacSnowLeopard: "mac-snowleopard" +} + +WebInspector.platformFlavor = function() +{ + function detectFlavor() + { + const userAgent = navigator.userAgent; + + if (WebInspector.platform() === "windows") { + var match = userAgent.match(/Windows NT (\d+)\.(?:\d+)/); + if (match && match[1] >= 6) + return WebInspector.PlatformFlavor.WindowsVista; + return null; + } else if (WebInspector.platform() === "mac") { + var match = userAgent.match(/Mac OS X\s*(?:(\d+)_(\d+))?/); + if (!match || match[1] != 10) + return WebInspector.PlatformFlavor.MacSnowLeopard; + switch (Number(match[2])) { + case 4: + return WebInspector.PlatformFlavor.MacTiger; + case 5: + return WebInspector.PlatformFlavor.MacLeopard; + case 6: + default: + return WebInspector.PlatformFlavor.MacSnowLeopard; + } + } + } + + if (!WebInspector._platformFlavor) + WebInspector._platformFlavor = detectFlavor(); + + return WebInspector._platformFlavor; +} + +WebInspector.port = function() +{ + if (!WebInspector._port) + WebInspector._port = InspectorFrontendHost.port(); + + return WebInspector._port; +} + +WebInspector.installPortStyles = function() +{ + var platform = WebInspector.platform(); + document.body.addStyleClass("platform-" + platform); + var flavor = WebInspector.platformFlavor(); + if (flavor) + document.body.addStyleClass("platform-" + flavor); + var port = WebInspector.port(); + document.body.addStyleClass("port-" + port); +} + +WebInspector._windowFocused = function(event) +{ + if (event.target.document.nodeType === Node.DOCUMENT_NODE) + document.body.removeStyleClass("inactive"); +} + +WebInspector._windowBlurred = function(event) +{ + if (event.target.document.nodeType === Node.DOCUMENT_NODE) + document.body.addStyleClass("inactive"); +} + +WebInspector.previousFocusElement = function() +{ + return WebInspector._previousFocusElement; +} + +WebInspector.currentFocusElement = function() +{ + return WebInspector._currentFocusElement; +} + +WebInspector._focusChanged = function(event) +{ + WebInspector.setCurrentFocusElement(event.target); +} + +WebInspector._textInputTypes = ["text", "search", "tel", "url", "email", "password"].keySet(); +WebInspector._isTextEditingElement = function(element) +{ + if (element instanceof HTMLInputElement) + return element.type in WebInspector._textInputTypes; + + if (element instanceof HTMLTextAreaElement) + return true; + + return false; +} + +WebInspector.setCurrentFocusElement = function(x) +{ + if (WebInspector._currentFocusElement !== x) + WebInspector._previousFocusElement = WebInspector._currentFocusElement; + WebInspector._currentFocusElement = x; + + if (WebInspector._currentFocusElement) { + WebInspector._currentFocusElement.focus(); + + // Make a caret selection inside the new element if there isn't a range selection and there isn't already a caret selection inside. + // This is needed (at least) to remove caret from console when focus is moved to some element in the panel. + // The code below should not be applied to text fields and text areas, hence _isTextEditingElement check. + var selection = window.getSelection(); + if (!WebInspector._isTextEditingElement(WebInspector._currentFocusElement) && selection.isCollapsed && !WebInspector._currentFocusElement.isInsertionCaretInside()) { + var selectionRange = WebInspector._currentFocusElement.ownerDocument.createRange(); + selectionRange.setStart(WebInspector._currentFocusElement, 0); + selectionRange.setEnd(WebInspector._currentFocusElement, 0); + + selection.removeAllRanges(); + selection.addRange(selectionRange); + } + } else if (WebInspector._previousFocusElement) + WebInspector._previousFocusElement.blur(); +} + +WebInspector.restoreFocusFromElement = function(element) +{ + if (element && element.isSelfOrAncestor(WebInspector.currentFocusElement())) + WebInspector.setCurrentFocusElement(WebInspector.previousFocusElement()); +} + +WebInspector.setToolbarColors = function(backgroundColor, color) +{ + if (!WebInspector._themeStyleElement) { + WebInspector._themeStyleElement = document.createElement("style"); + document.head.appendChild(WebInspector._themeStyleElement); + } + WebInspector._themeStyleElement.textContent = + "#toolbar {\ + background-image: none !important;\ + background-color: " + backgroundColor + " !important;\ + }\ + \ + .toolbar-label {\ + color: " + color + " !important;\ + text-shadow: none;\ + }"; +} + +WebInspector.resetToolbarColors = function() +{ + if (WebInspector._themeStyleElement) + WebInspector._themeStyleElement.textContent = ""; +} + +/** + * @param {WebInspector.ContextMenu} contextMenu + * @param {Node} contextNode + * @param {Event} event + */ +WebInspector.populateHrefContextMenu = function(contextMenu, contextNode, event) +{ + var anchorElement = event.target.enclosingNodeOrSelfWithClass("webkit-html-resource-link") || event.target.enclosingNodeOrSelfWithClass("webkit-html-external-link"); + if (!anchorElement) + return false; + + var resourceURL = WebInspector.resourceURLForRelatedNode(contextNode, anchorElement.href); + if (!resourceURL) + return false; + + // Add resource-related actions. + contextMenu.appendItem(WebInspector.openLinkExternallyLabel(), WebInspector.openResource.bind(WebInspector, resourceURL, false)); + if (WebInspector.resourceForURL(resourceURL)) + contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Open link in Resources panel" : "Open Link in Resources Panel"), WebInspector.openResource.bind(null, resourceURL, true)); + contextMenu.appendItem(WebInspector.copyLinkAddressLabel(), InspectorFrontendHost.copyText.bind(InspectorFrontendHost, resourceURL)); + return true; +} + +;(function() { + +function windowLoaded() +{ + window.addEventListener("focus", WebInspector._windowFocused, false); + window.addEventListener("blur", WebInspector._windowBlurred, false); + document.addEventListener("focus", WebInspector._focusChanged.bind(this), true); + window.removeEventListener("DOMContentLoaded", windowLoaded, false); +} + +window.addEventListener("DOMContentLoaded", windowLoaded, false); + +})(); +/* InspectorBackend.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + */ +function InspectorBackendClass() +{ + this._lastCallbackId = 1; + this._pendingResponsesCount = 0; + this._callbacks = {}; + this._domainDispatchers = {}; + this._eventArgs = {}; + this._replyArgs = {}; + + this.dumpInspectorTimeStats = false; + this.dumpInspectorProtocolMessages = false; + this._initialized = false; +} + +InspectorBackendClass.prototype = { + _wrap: function(callback, method) + { + var callbackId = this._lastCallbackId++; + if (!callback) + callback = function() {}; + + this._callbacks[callbackId] = callback; + callback.methodName = method; + if (this.dumpInspectorTimeStats) + callback.sendRequestTime = Date.now(); + + return callbackId; + }, + + registerCommand: function(method, signature, replyArgs) + { + var domainAndMethod = method.split("."); + var agentName = domainAndMethod[0] + "Agent"; + if (!window[agentName]) + window[agentName] = {}; + + window[agentName][domainAndMethod[1]] = this._sendMessageToBackend.bind(this, method, signature); + window[agentName][domainAndMethod[1]]["invoke"] = this._invoke.bind(this, method, signature); + this._replyArgs[method] = replyArgs; + + this._initialized = true; + }, + + registerEvent: function(eventName, params) + { + this._eventArgs[eventName] = params; + + this._initialized = true; + }, + + _invoke: function(method, signature, args, callback) + { + this._wrapCallbackAndSendMessageObject(method, args, callback); + }, + + _sendMessageToBackend: function(method, signature, vararg) + { + var args = Array.prototype.slice.call(arguments, 2); + var callback = (args.length && typeof args[args.length - 1] === "function") ? args.pop() : null; + + var params = {}; + var hasParams = false; + for (var i = 0; i < signature.length; ++i) { + var param = signature[i]; + var paramName = param["name"]; + var typeName = param["type"]; + var optionalFlag = param["optional"]; + + if (!args.length && !optionalFlag) { + console.error("Protocol Error: Invalid number of arguments for method '" + method + "' call. It must have the following arguments '" + JSON.stringify(signature) + "'."); + return; + } + + var value = args.shift(); + if (optionalFlag && typeof value === "undefined") { + continue; + } + + if (typeof value !== typeName) { + console.error("Protocol Error: Invalid type of argument '" + paramName + "' for method '" + method + "' call. It must be '" + typeName + "' but it is '" + typeof value + "'."); + return; + } + + params[paramName] = value; + hasParams = true; + } + + if (args.length === 1 && !callback) { + if (typeof args[0] !== "undefined") { + console.error("Protocol Error: Optional callback argument for method '" + method + "' call must be a function but its type is '" + typeof args[0] + "'."); + return; + } + } + + this._wrapCallbackAndSendMessageObject(method, hasParams ? params : null, callback); + }, + + _wrapCallbackAndSendMessageObject: function(method, params, callback) + { + var messageObject = {}; + messageObject.method = method; + if (params) + messageObject.params = params; + messageObject.id = this._wrap(callback, method); + + if (this.dumpInspectorProtocolMessages) + console.log("frontend: " + JSON.stringify(messageObject)); + + ++this._pendingResponsesCount; + this.sendMessageObjectToBackend(messageObject); + }, + + sendMessageObjectToBackend: function(messageObject) + { + var message = JSON.stringify(messageObject); + InspectorFrontendHost.sendMessageToBackend(message); + }, + + registerDomainDispatcher: function(domain, dispatcher) + { + this._domainDispatchers[domain] = dispatcher; + }, + + dispatch: function(message) + { + if (this.dumpInspectorProtocolMessages) + console.log("backend: " + ((typeof message === "string") ? message : JSON.stringify(message))); + + var messageObject = (typeof message === "string") ? JSON.parse(message) : message; + + if ("id" in messageObject) { // just a response for some request + if (messageObject.error) { + messageObject.error.__proto__ = { + getDescription: function() + { + switch(this.code) { + case -32700: return "Parse error"; + case -32600: return "Invalid Request"; + case -32601: return "Method not found"; + case -32602: return "Invalid params"; + case -32603: return "Internal error";; + case -32000: return "Server error"; + } + }, + + toString: function() + { + var description ="Unknown error code"; + return this.getDescription() + "(" + this.code + "): " + this.message + "." + (this.data ? " " + this.data.join(" ") : ""); + }, + + getMessage: function() + { + return this.message; + } + } + + if (messageObject.error.code !== -32000) + this.reportProtocolError(messageObject); + } + + var callback = this._callbacks[messageObject.id]; + if (callback) { + var argumentsArray = []; + if (messageObject.result) { + var paramNames = this._replyArgs[callback.methodName]; + if (paramNames) { + for (var i = 0; i < paramNames.length; ++i) + argumentsArray.push(messageObject.result[paramNames[i]]); + } + } + + var processingStartTime; + if (this.dumpInspectorTimeStats && callback.methodName) + processingStartTime = Date.now(); + + argumentsArray.unshift(messageObject.error); + callback.apply(null, argumentsArray); + --this._pendingResponsesCount; + delete this._callbacks[messageObject.id]; + + if (this.dumpInspectorTimeStats && callback.methodName) + console.log("time-stats: " + callback.methodName + " = " + (processingStartTime - callback.sendRequestTime) + " + " + (Date.now() - processingStartTime)); + } + + if (this._scripts && !this._pendingResponsesCount) + this.runAfterPendingDispatches(); + + return; + } else { + var method = messageObject.method.split("."); + var domainName = method[0]; + var functionName = method[1]; + if (!(domainName in this._domainDispatchers)) { + console.error("Protocol Error: the message is for non-existing domain '" + domainName + "'"); + return; + } + var dispatcher = this._domainDispatchers[domainName]; + if (!(functionName in dispatcher)) { + console.error("Protocol Error: Attempted to dispatch an unimplemented method '" + messageObject.method + "'"); + return; + } + + if (!this._eventArgs[messageObject.method]) { + console.error("Protocol Error: Attempted to dispatch an unspecified method '" + messageObject.method + "'"); + return; + } + + var params = []; + if (messageObject.params) { + var paramNames = this._eventArgs[messageObject.method]; + for (var i = 0; i < paramNames.length; ++i) + params.push(messageObject.params[paramNames[i]]); + } + + var processingStartTime; + if (this.dumpInspectorTimeStats) + processingStartTime = Date.now(); + + dispatcher[functionName].apply(dispatcher, params); + + if (this.dumpInspectorTimeStats) + console.log("time-stats: " + messageObject.method + " = " + (Date.now() - processingStartTime)); + } + }, + + reportProtocolError: function(messageObject) + { + console.error("Request with id = " + messageObject.id + " failed. " + messageObject.error); + }, + + /** + * @param {string=} script + */ + runAfterPendingDispatches: function(script) + { + if (!this._scripts) + this._scripts = []; + + if (script) + this._scripts.push(script); + + if (!this._pendingResponsesCount) { + var scripts = this._scripts; + this._scripts = [] + for (var id = 0; id < scripts.length; ++id) + scripts[id].call(this); + } + }, + + loadFromJSONIfNeeded: function() + { + if (this._initialized) + return; + + var xhr = new XMLHttpRequest(); + xhr.open("GET", "../Inspector.json", false); + xhr.send(null); + + var schema = JSON.parse(xhr.responseText); + var jsTypes = { integer: "number", array: "object" }; + var rawTypes = {}; + + var domains = schema["domains"]; + for (var i = 0; i < domains.length; ++i) { + var domain = domains[i]; + for (var j = 0; domain.types && j < domain.types.length; ++j) { + var type = domain.types[j]; + rawTypes[domain.domain + "." + type.id] = jsTypes[type.type] || type.type; + } + } + + var result = []; + for (var i = 0; i < domains.length; ++i) { + var domain = domains[i]; + + var commands = domain["commands"] || []; + for (var j = 0; j < commands.length; ++j) { + var command = commands[j]; + var parameters = command["parameters"]; + var paramsText = []; + for (var k = 0; parameters && k < parameters.length; ++k) { + var parameter = parameters[k]; + + var type; + if (parameter.type) + type = jsTypes[parameter.type] || parameter.type; + else { + var ref = parameter["$ref"]; + if (ref.indexOf(".") !== -1) + type = rawTypes[ref]; + else + type = rawTypes[domain.domain + "." + ref]; + } + + var text = "{\"name\": \"" + parameter.name + "\", \"type\": \"" + type + "\", \"optional\": " + (parameter.optional ? "true" : "false") + "}"; + paramsText.push(text); + } + + var returnsText = []; + var returns = command["returns"] || []; + for (var k = 0; k < returns.length; ++k) { + var parameter = returns[k]; + returnsText.push("\"" + parameter.name + "\""); + } + result.push("InspectorBackend.registerCommand(\"" + domain.domain + "." + command.name + "\", [" + paramsText.join(", ") + "], [" + returnsText.join(", ") + "]);"); + } + + for (var j = 0; domain.events && j < domain.events.length; ++j) { + var event = domain.events[j]; + var paramsText = []; + for (var k = 0; event.parameters && k < event.parameters.length; ++k) { + var parameter = event.parameters[k]; + paramsText.push("\"" + parameter.name + "\""); + } + result.push("InspectorBackend.registerEvent(\"" + domain.domain + "." + event.name + "\", [" + paramsText.join(", ") + "]);"); + } + + result.push("InspectorBackend.register" + domain.domain + "Dispatcher = InspectorBackend.registerDomainDispatcher.bind(InspectorBackend, \"" + domain.domain + "\");"); + } + eval(result.join("\n")); + } +} + +InspectorBackend = new InspectorBackendClass(); +/* InspectorBackendStub.js */ + +// File is generated by WebCore/WebCore/inspector/CodeGeneratorInspector.py + +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + + +// Inspector. +InspectorBackend.registerInspectorDispatcher = InspectorBackend.registerDomainDispatcher.bind(InspectorBackend, "Inspector"); +InspectorBackend.registerEvent("Inspector.evaluateForTestInFrontend", ["testCallId", "script"]); +InspectorBackend.registerEvent("Inspector.inspect", ["object", "hints"]); +InspectorBackend.registerEvent("Inspector.didCreateWorker", ["id", "url", "isShared"]); +InspectorBackend.registerEvent("Inspector.didDestroyWorker", ["id"]); +InspectorBackend.registerCommand("Inspector.enable", [], []); +InspectorBackend.registerCommand("Inspector.disable", [], []); + +// Memory. +InspectorBackend.registerMemoryDispatcher = InspectorBackend.registerDomainDispatcher.bind(InspectorBackend, "Memory"); +InspectorBackend.registerCommand("Memory.getDOMNodeCount", [], ["domGroups", "strings"]); + +// Page. +InspectorBackend.registerPageDispatcher = InspectorBackend.registerDomainDispatcher.bind(InspectorBackend, "Page"); +InspectorBackend.registerEvent("Page.domContentEventFired", ["timestamp"]); +InspectorBackend.registerEvent("Page.loadEventFired", ["timestamp"]); +InspectorBackend.registerEvent("Page.frameNavigated", ["frame"]); +InspectorBackend.registerEvent("Page.frameDetached", ["frameId"]); +InspectorBackend.registerCommand("Page.enable", [], []); +InspectorBackend.registerCommand("Page.disable", [], []); +InspectorBackend.registerCommand("Page.addScriptToEvaluateOnLoad", [{"name": "scriptSource", "type": "string", "optional": false}], ["identifier"]); +InspectorBackend.registerCommand("Page.removeScriptToEvaluateOnLoad", [{"name": "identifier", "type": "string", "optional": false}], []); +InspectorBackend.registerCommand("Page.reload", [{"name": "ignoreCache", "type": "boolean", "optional": true}, {"name": "scriptToEvaluateOnLoad", "type": "string", "optional": true}], []); +InspectorBackend.registerCommand("Page.navigate", [{"name": "url", "type": "string", "optional": false}], []); +InspectorBackend.registerCommand("Page.getCookies", [], ["cookies", "cookiesString"]); +InspectorBackend.registerCommand("Page.deleteCookie", [{"name": "cookieName", "type": "string", "optional": false}, {"name": "domain", "type": "string", "optional": false}], []); +InspectorBackend.registerCommand("Page.getResourceTree", [], ["frameTree"]); +InspectorBackend.registerCommand("Page.getResourceContent", [{"name": "frameId", "type": "string", "optional": false}, {"name": "url", "type": "string", "optional": false}], ["content", "base64Encoded"]); +InspectorBackend.registerCommand("Page.searchInResource", [{"name": "frameId", "type": "string", "optional": false}, {"name": "url", "type": "string", "optional": false}, {"name": "query", "type": "string", "optional": false}, {"name": "caseSensitive", "type": "boolean", "optional": true}, {"name": "isRegex", "type": "boolean", "optional": true}], ["result"]); +InspectorBackend.registerCommand("Page.searchInResources", [{"name": "text", "type": "string", "optional": false}, {"name": "caseSensitive", "type": "boolean", "optional": true}, {"name": "isRegex", "type": "boolean", "optional": true}], ["result"]); +InspectorBackend.registerCommand("Page.setDocumentContent", [{"name": "frameId", "type": "string", "optional": false}, {"name": "html", "type": "string", "optional": false}], []); +InspectorBackend.registerCommand("Page.setScreenSizeOverride", [{"name": "width", "type": "number", "optional": false}, {"name": "height", "type": "number", "optional": false}], []); + +// Runtime. +InspectorBackend.registerCommand("Runtime.evaluate", [{"name": "expression", "type": "string", "optional": false}, {"name": "objectGroup", "type": "string", "optional": true}, {"name": "includeCommandLineAPI", "type": "boolean", "optional": true}, {"name": "doNotPauseOnExceptions", "type": "boolean", "optional": true}, {"name": "frameId", "type": "string", "optional": true}, {"name": "returnByValue", "type": "boolean", "optional": true}], ["result", "wasThrown"]); +InspectorBackend.registerCommand("Runtime.callFunctionOn", [{"name": "objectId", "type": "string", "optional": false}, {"name": "functionDeclaration", "type": "string", "optional": false}, {"name": "arguments", "type": "object", "optional": true}, {"name": "returnByValue", "type": "boolean", "optional": true}], ["result", "wasThrown"]); +InspectorBackend.registerCommand("Runtime.getProperties", [{"name": "objectId", "type": "string", "optional": false}, {"name": "ownProperties", "type": "boolean", "optional": true}], ["result"]); +InspectorBackend.registerCommand("Runtime.releaseObject", [{"name": "objectId", "type": "string", "optional": false}], []); +InspectorBackend.registerCommand("Runtime.releaseObjectGroup", [{"name": "objectGroup", "type": "string", "optional": false}], []); +InspectorBackend.registerCommand("Runtime.run", [], []); + +// Console. +InspectorBackend.registerConsoleDispatcher = InspectorBackend.registerDomainDispatcher.bind(InspectorBackend, "Console"); +InspectorBackend.registerEvent("Console.messageAdded", ["message"]); +InspectorBackend.registerEvent("Console.messageRepeatCountUpdated", ["count"]); +InspectorBackend.registerEvent("Console.messagesCleared", []); +InspectorBackend.registerCommand("Console.enable", [], []); +InspectorBackend.registerCommand("Console.disable", [], []); +InspectorBackend.registerCommand("Console.clearMessages", [], []); +InspectorBackend.registerCommand("Console.setMonitoringXHREnabled", [{"name": "enabled", "type": "boolean", "optional": false}], []); +InspectorBackend.registerCommand("Console.addInspectedNode", [{"name": "nodeId", "type": "number", "optional": false}], []); + +// Network. +InspectorBackend.registerNetworkDispatcher = InspectorBackend.registerDomainDispatcher.bind(InspectorBackend, "Network"); +InspectorBackend.registerEvent("Network.requestWillBeSent", ["requestId", "frameId", "loaderId", "documentURL", "request", "timestamp", "initiator", "stackTrace", "redirectResponse"]); +InspectorBackend.registerEvent("Network.requestServedFromCache", ["requestId"]); +InspectorBackend.registerEvent("Network.responseReceived", ["requestId", "frameId", "loaderId", "timestamp", "type", "response"]); +InspectorBackend.registerEvent("Network.dataReceived", ["requestId", "timestamp", "dataLength", "encodedDataLength"]); +InspectorBackend.registerEvent("Network.loadingFinished", ["requestId", "timestamp"]); +InspectorBackend.registerEvent("Network.loadingFailed", ["requestId", "timestamp", "errorText", "canceled"]); +InspectorBackend.registerEvent("Network.requestServedFromMemoryCache", ["requestId", "frameId", "loaderId", "documentURL", "timestamp", "initiator", "resource"]); +InspectorBackend.registerEvent("Network.webSocketWillSendHandshakeRequest", ["requestId", "timestamp", "request"]); +InspectorBackend.registerEvent("Network.webSocketHandshakeResponseReceived", ["requestId", "timestamp", "response"]); +InspectorBackend.registerEvent("Network.webSocketCreated", ["requestId", "url"]); +InspectorBackend.registerEvent("Network.webSocketClosed", ["requestId", "timestamp"]); +InspectorBackend.registerCommand("Network.enable", [], []); +InspectorBackend.registerCommand("Network.disable", [], []); +InspectorBackend.registerCommand("Network.setUserAgentOverride", [{"name": "userAgent", "type": "string", "optional": false}], []); +InspectorBackend.registerCommand("Network.setExtraHTTPHeaders", [{"name": "headers", "type": "object", "optional": false}], []); +InspectorBackend.registerCommand("Network.getResponseBody", [{"name": "requestId", "type": "string", "optional": false}], ["body", "base64Encoded"]); +InspectorBackend.registerCommand("Network.canClearBrowserCache", [], ["result"]); +InspectorBackend.registerCommand("Network.clearBrowserCache", [], []); +InspectorBackend.registerCommand("Network.canClearBrowserCookies", [], ["result"]); +InspectorBackend.registerCommand("Network.clearBrowserCookies", [], []); +InspectorBackend.registerCommand("Network.setCacheDisabled", [{"name": "cacheDisabled", "type": "boolean", "optional": false}], []); + +// Database. +InspectorBackend.registerDatabaseDispatcher = InspectorBackend.registerDomainDispatcher.bind(InspectorBackend, "Database"); +InspectorBackend.registerEvent("Database.addDatabase", ["database"]); +InspectorBackend.registerEvent("Database.sqlTransactionSucceeded", ["transactionId", "columnNames", "values"]); +InspectorBackend.registerEvent("Database.sqlTransactionFailed", ["transactionId", "sqlError"]); +InspectorBackend.registerCommand("Database.enable", [], []); +InspectorBackend.registerCommand("Database.disable", [], []); +InspectorBackend.registerCommand("Database.getDatabaseTableNames", [{"name": "databaseId", "type": "number", "optional": false}], ["tableNames"]); +InspectorBackend.registerCommand("Database.executeSQL", [{"name": "databaseId", "type": "number", "optional": false}, {"name": "query", "type": "string", "optional": false}], ["success", "transactionId"]); + +// IndexedDB. +InspectorBackend.registerIndexedDBDispatcher = InspectorBackend.registerDomainDispatcher.bind(InspectorBackend, "IndexedDB"); +InspectorBackend.registerEvent("IndexedDB.databaseNamesLoaded", ["requestId", "securityOriginWithDatabaseNames"]); +InspectorBackend.registerCommand("IndexedDB.enable", [], []); +InspectorBackend.registerCommand("IndexedDB.disable", [], []); +InspectorBackend.registerCommand("IndexedDB.requestDatabaseNamesForFrame", [{"name": "requestId", "type": "number", "optional": false}, {"name": "frameId", "type": "string", "optional": false}], []); + +// DOMStorage. +InspectorBackend.registerDOMStorageDispatcher = InspectorBackend.registerDomainDispatcher.bind(InspectorBackend, "DOMStorage"); +InspectorBackend.registerEvent("DOMStorage.addDOMStorage", ["storage"]); +InspectorBackend.registerEvent("DOMStorage.updateDOMStorage", ["storageId"]); +InspectorBackend.registerCommand("DOMStorage.enable", [], []); +InspectorBackend.registerCommand("DOMStorage.disable", [], []); +InspectorBackend.registerCommand("DOMStorage.getDOMStorageEntries", [{"name": "storageId", "type": "number", "optional": false}], ["entries"]); +InspectorBackend.registerCommand("DOMStorage.setDOMStorageItem", [{"name": "storageId", "type": "number", "optional": false}, {"name": "key", "type": "string", "optional": false}, {"name": "value", "type": "string", "optional": false}], ["success"]); +InspectorBackend.registerCommand("DOMStorage.removeDOMStorageItem", [{"name": "storageId", "type": "number", "optional": false}, {"name": "key", "type": "string", "optional": false}], ["success"]); + +// ApplicationCache. +InspectorBackend.registerApplicationCacheDispatcher = InspectorBackend.registerDomainDispatcher.bind(InspectorBackend, "ApplicationCache"); +InspectorBackend.registerEvent("ApplicationCache.applicationCacheStatusUpdated", ["frameId", "manifestURL", "status"]); +InspectorBackend.registerEvent("ApplicationCache.networkStateUpdated", ["isNowOnline"]); +InspectorBackend.registerCommand("ApplicationCache.getFramesWithManifests", [], ["frameIds"]); +InspectorBackend.registerCommand("ApplicationCache.enable", [], []); +InspectorBackend.registerCommand("ApplicationCache.getManifestForFrame", [{"name": "frameId", "type": "string", "optional": false}], ["manifestURL"]); +InspectorBackend.registerCommand("ApplicationCache.getApplicationCacheForFrame", [{"name": "frameId", "type": "string", "optional": false}], ["applicationCache"]); + +// FileSystem. +InspectorBackend.registerFileSystemDispatcher = InspectorBackend.registerDomainDispatcher.bind(InspectorBackend, "FileSystem"); +InspectorBackend.registerCommand("FileSystem.enable", [], []); +InspectorBackend.registerCommand("FileSystem.disable", [], []); + +// DOM. +InspectorBackend.registerDOMDispatcher = InspectorBackend.registerDomainDispatcher.bind(InspectorBackend, "DOM"); +InspectorBackend.registerEvent("DOM.documentUpdated", []); +InspectorBackend.registerEvent("DOM.setChildNodes", ["parentId", "nodes"]); +InspectorBackend.registerEvent("DOM.attributeModified", ["nodeId", "name", "value"]); +InspectorBackend.registerEvent("DOM.attributeRemoved", ["nodeId", "name"]); +InspectorBackend.registerEvent("DOM.inlineStyleInvalidated", ["nodeIds"]); +InspectorBackend.registerEvent("DOM.characterDataModified", ["nodeId", "characterData"]); +InspectorBackend.registerEvent("DOM.childNodeCountUpdated", ["nodeId", "childNodeCount"]); +InspectorBackend.registerEvent("DOM.childNodeInserted", ["parentNodeId", "previousNodeId", "node"]); +InspectorBackend.registerEvent("DOM.childNodeRemoved", ["parentNodeId", "nodeId"]); +InspectorBackend.registerCommand("DOM.getDocument", [], ["root"]); +InspectorBackend.registerCommand("DOM.requestChildNodes", [{"name": "nodeId", "type": "number", "optional": false}], []); +InspectorBackend.registerCommand("DOM.querySelector", [{"name": "nodeId", "type": "number", "optional": false}, {"name": "selector", "type": "string", "optional": false}], ["nodeId"]); +InspectorBackend.registerCommand("DOM.querySelectorAll", [{"name": "nodeId", "type": "number", "optional": false}, {"name": "selector", "type": "string", "optional": false}], ["nodeIds"]); +InspectorBackend.registerCommand("DOM.setNodeName", [{"name": "nodeId", "type": "number", "optional": false}, {"name": "name", "type": "string", "optional": false}], ["nodeId"]); +InspectorBackend.registerCommand("DOM.setNodeValue", [{"name": "nodeId", "type": "number", "optional": false}, {"name": "value", "type": "string", "optional": false}], []); +InspectorBackend.registerCommand("DOM.removeNode", [{"name": "nodeId", "type": "number", "optional": false}], []); +InspectorBackend.registerCommand("DOM.setAttributeValue", [{"name": "nodeId", "type": "number", "optional": false}, {"name": "name", "type": "string", "optional": false}, {"name": "value", "type": "string", "optional": false}], []); +InspectorBackend.registerCommand("DOM.setAttributesAsText", [{"name": "nodeId", "type": "number", "optional": false}, {"name": "text", "type": "string", "optional": false}, {"name": "name", "type": "string", "optional": true}], []); +InspectorBackend.registerCommand("DOM.removeAttribute", [{"name": "nodeId", "type": "number", "optional": false}, {"name": "name", "type": "string", "optional": false}], []); +InspectorBackend.registerCommand("DOM.getEventListenersForNode", [{"name": "nodeId", "type": "number", "optional": false}], ["listeners"]); +InspectorBackend.registerCommand("DOM.getOuterHTML", [{"name": "nodeId", "type": "number", "optional": false}], ["outerHTML"]); +InspectorBackend.registerCommand("DOM.setOuterHTML", [{"name": "nodeId", "type": "number", "optional": false}, {"name": "outerHTML", "type": "string", "optional": false}], []); +InspectorBackend.registerCommand("DOM.performSearch", [{"name": "query", "type": "string", "optional": false}], ["searchId", "resultCount"]); +InspectorBackend.registerCommand("DOM.getSearchResults", [{"name": "searchId", "type": "string", "optional": false}, {"name": "fromIndex", "type": "number", "optional": false}, {"name": "toIndex", "type": "number", "optional": false}], ["nodeIds"]); +InspectorBackend.registerCommand("DOM.discardSearchResults", [{"name": "searchId", "type": "string", "optional": false}], []); +InspectorBackend.registerCommand("DOM.requestNode", [{"name": "objectId", "type": "string", "optional": false}], ["nodeId"]); +InspectorBackend.registerCommand("DOM.setInspectModeEnabled", [{"name": "enabled", "type": "boolean", "optional": false}, {"name": "highlightConfig", "type": "object", "optional": true}], []); +InspectorBackend.registerCommand("DOM.highlightRect", [{"name": "x", "type": "number", "optional": false}, {"name": "y", "type": "number", "optional": false}, {"name": "width", "type": "number", "optional": false}, {"name": "height", "type": "number", "optional": false}, {"name": "color", "type": "object", "optional": true}, {"name": "outlineColor", "type": "object", "optional": true}], []); +InspectorBackend.registerCommand("DOM.highlightNode", [{"name": "nodeId", "type": "number", "optional": false}, {"name": "highlightConfig", "type": "object", "optional": false}], []); +InspectorBackend.registerCommand("DOM.hideHighlight", [], []); +InspectorBackend.registerCommand("DOM.highlightFrame", [{"name": "frameId", "type": "string", "optional": false}, {"name": "contentColor", "type": "object", "optional": true}, {"name": "contentOutlineColor", "type": "object", "optional": true}], []); +InspectorBackend.registerCommand("DOM.pushNodeByPathToFrontend", [{"name": "path", "type": "string", "optional": false}], ["nodeId"]); +InspectorBackend.registerCommand("DOM.resolveNode", [{"name": "nodeId", "type": "number", "optional": false}, {"name": "objectGroup", "type": "string", "optional": true}], ["object"]); +InspectorBackend.registerCommand("DOM.getAttributes", [{"name": "nodeId", "type": "number", "optional": false}], ["attributes"]); +InspectorBackend.registerCommand("DOM.moveTo", [{"name": "nodeId", "type": "number", "optional": false}, {"name": "targetNodeId", "type": "number", "optional": false}, {"name": "insertBeforeNodeId", "type": "number", "optional": true}], ["nodeId"]); + +// CSS. +InspectorBackend.registerCSSDispatcher = InspectorBackend.registerDomainDispatcher.bind(InspectorBackend, "CSS"); +InspectorBackend.registerEvent("CSS.mediaQueryResultChanged", []); +InspectorBackend.registerCommand("CSS.enable", [], []); +InspectorBackend.registerCommand("CSS.disable", [], []); +InspectorBackend.registerCommand("CSS.getMatchedStylesForNode", [{"name": "nodeId", "type": "number", "optional": false}, {"name": "forcedPseudoClasses", "type": "object", "optional": true}, {"name": "includePseudo", "type": "boolean", "optional": true}, {"name": "includeInherited", "type": "boolean", "optional": true}], ["matchedCSSRules", "pseudoElements", "inherited"]); +InspectorBackend.registerCommand("CSS.getInlineStylesForNode", [{"name": "nodeId", "type": "number", "optional": false}], ["inlineStyle", "styleAttributes"]); +InspectorBackend.registerCommand("CSS.getComputedStyleForNode", [{"name": "nodeId", "type": "number", "optional": false}, {"name": "forcedPseudoClasses", "type": "object", "optional": true}], ["computedStyle"]); +InspectorBackend.registerCommand("CSS.getAllStyleSheets", [], ["headers"]); +InspectorBackend.registerCommand("CSS.getStyleSheet", [{"name": "styleSheetId", "type": "string", "optional": false}], ["styleSheet"]); +InspectorBackend.registerCommand("CSS.getStyleSheetText", [{"name": "styleSheetId", "type": "string", "optional": false}], ["text"]); +InspectorBackend.registerCommand("CSS.setStyleSheetText", [{"name": "styleSheetId", "type": "string", "optional": false}, {"name": "text", "type": "string", "optional": false}], []); +InspectorBackend.registerCommand("CSS.setPropertyText", [{"name": "styleId", "type": "object", "optional": false}, {"name": "propertyIndex", "type": "number", "optional": false}, {"name": "text", "type": "string", "optional": false}, {"name": "overwrite", "type": "boolean", "optional": false}], ["style"]); +InspectorBackend.registerCommand("CSS.toggleProperty", [{"name": "styleId", "type": "object", "optional": false}, {"name": "propertyIndex", "type": "number", "optional": false}, {"name": "disable", "type": "boolean", "optional": false}], ["style"]); +InspectorBackend.registerCommand("CSS.setRuleSelector", [{"name": "ruleId", "type": "object", "optional": false}, {"name": "selector", "type": "string", "optional": false}], ["rule"]); +InspectorBackend.registerCommand("CSS.addRule", [{"name": "contextNodeId", "type": "number", "optional": false}, {"name": "selector", "type": "string", "optional": false}], ["rule"]); +InspectorBackend.registerCommand("CSS.getSupportedCSSProperties", [], ["cssProperties"]); +InspectorBackend.registerCommand("CSS.startSelectorProfiler", [], []); +InspectorBackend.registerCommand("CSS.stopSelectorProfiler", [], ["profile"]); + +// Timeline. +InspectorBackend.registerTimelineDispatcher = InspectorBackend.registerDomainDispatcher.bind(InspectorBackend, "Timeline"); +InspectorBackend.registerEvent("Timeline.eventRecorded", ["record"]); +InspectorBackend.registerCommand("Timeline.start", [{"name": "maxCallStackDepth", "type": "number", "optional": true}], []); +InspectorBackend.registerCommand("Timeline.stop", [], []); + +// Debugger. +InspectorBackend.registerDebuggerDispatcher = InspectorBackend.registerDomainDispatcher.bind(InspectorBackend, "Debugger"); +InspectorBackend.registerEvent("Debugger.globalObjectCleared", []); +InspectorBackend.registerEvent("Debugger.scriptParsed", ["scriptId", "url", "startLine", "startColumn", "endLine", "endColumn", "isContentScript", "sourceMapURL"]); +InspectorBackend.registerEvent("Debugger.scriptFailedToParse", ["url", "scriptSource", "startLine", "errorLine", "errorMessage"]); +InspectorBackend.registerEvent("Debugger.breakpointResolved", ["breakpointId", "location"]); +InspectorBackend.registerEvent("Debugger.paused", ["callFrames", "reason", "data"]); +InspectorBackend.registerEvent("Debugger.resumed", []); +InspectorBackend.registerCommand("Debugger.causesRecompilation", [], ["result"]); +InspectorBackend.registerCommand("Debugger.supportsNativeBreakpoints", [], ["result"]); +InspectorBackend.registerCommand("Debugger.enable", [], []); +InspectorBackend.registerCommand("Debugger.disable", [], []); +InspectorBackend.registerCommand("Debugger.setBreakpointsActive", [{"name": "active", "type": "boolean", "optional": false}], []); +InspectorBackend.registerCommand("Debugger.setBreakpointByUrl", [{"name": "lineNumber", "type": "number", "optional": false}, {"name": "url", "type": "string", "optional": true}, {"name": "urlRegex", "type": "string", "optional": true}, {"name": "columnNumber", "type": "number", "optional": true}, {"name": "condition", "type": "string", "optional": true}], ["breakpointId", "locations"]); +InspectorBackend.registerCommand("Debugger.setBreakpoint", [{"name": "location", "type": "object", "optional": false}, {"name": "condition", "type": "string", "optional": true}], ["breakpointId", "actualLocation"]); +InspectorBackend.registerCommand("Debugger.removeBreakpoint", [{"name": "breakpointId", "type": "string", "optional": false}], []); +InspectorBackend.registerCommand("Debugger.continueToLocation", [{"name": "location", "type": "object", "optional": false}], []); +InspectorBackend.registerCommand("Debugger.stepOver", [], []); +InspectorBackend.registerCommand("Debugger.stepInto", [], []); +InspectorBackend.registerCommand("Debugger.stepOut", [], []); +InspectorBackend.registerCommand("Debugger.pause", [], []); +InspectorBackend.registerCommand("Debugger.resume", [], []); +InspectorBackend.registerCommand("Debugger.searchInContent", [{"name": "scriptId", "type": "string", "optional": false}, {"name": "query", "type": "string", "optional": false}, {"name": "caseSensitive", "type": "boolean", "optional": true}, {"name": "isRegex", "type": "boolean", "optional": true}], ["result"]); +InspectorBackend.registerCommand("Debugger.canSetScriptSource", [], ["result"]); +InspectorBackend.registerCommand("Debugger.setScriptSource", [{"name": "scriptId", "type": "string", "optional": false}, {"name": "scriptSource", "type": "string", "optional": false}, {"name": "preview", "type": "boolean", "optional": true}], ["callFrames", "result"]); +InspectorBackend.registerCommand("Debugger.getScriptSource", [{"name": "scriptId", "type": "string", "optional": false}], ["scriptSource"]); +InspectorBackend.registerCommand("Debugger.getFunctionLocation", [{"name": "functionId", "type": "string", "optional": false}], ["location"]); +InspectorBackend.registerCommand("Debugger.setPauseOnExceptions", [{"name": "state", "type": "string", "optional": false}], []); +InspectorBackend.registerCommand("Debugger.evaluateOnCallFrame", [{"name": "callFrameId", "type": "string", "optional": false}, {"name": "expression", "type": "string", "optional": false}, {"name": "objectGroup", "type": "string", "optional": true}, {"name": "includeCommandLineAPI", "type": "boolean", "optional": true}, {"name": "returnByValue", "type": "boolean", "optional": true}], ["result", "wasThrown"]); + +// DOMDebugger. +InspectorBackend.registerCommand("DOMDebugger.setDOMBreakpoint", [{"name": "nodeId", "type": "number", "optional": false}, {"name": "type", "type": "string", "optional": false}], []); +InspectorBackend.registerCommand("DOMDebugger.removeDOMBreakpoint", [{"name": "nodeId", "type": "number", "optional": false}, {"name": "type", "type": "string", "optional": false}], []); +InspectorBackend.registerCommand("DOMDebugger.setEventListenerBreakpoint", [{"name": "eventName", "type": "string", "optional": false}], []); +InspectorBackend.registerCommand("DOMDebugger.removeEventListenerBreakpoint", [{"name": "eventName", "type": "string", "optional": false}], []); +InspectorBackend.registerCommand("DOMDebugger.setXHRBreakpoint", [{"name": "url", "type": "string", "optional": false}], []); +InspectorBackend.registerCommand("DOMDebugger.removeXHRBreakpoint", [{"name": "url", "type": "string", "optional": false}], []); + +// Profiler. +InspectorBackend.registerProfilerDispatcher = InspectorBackend.registerDomainDispatcher.bind(InspectorBackend, "Profiler"); +InspectorBackend.registerEvent("Profiler.addProfileHeader", ["header"]); +InspectorBackend.registerEvent("Profiler.addHeapSnapshotChunk", ["uid", "chunk"]); +InspectorBackend.registerEvent("Profiler.finishHeapSnapshot", ["uid"]); +InspectorBackend.registerEvent("Profiler.setRecordingProfile", ["isProfiling"]); +InspectorBackend.registerEvent("Profiler.resetProfiles", []); +InspectorBackend.registerEvent("Profiler.reportHeapSnapshotProgress", ["done", "total"]); +InspectorBackend.registerCommand("Profiler.causesRecompilation", [], ["result"]); +InspectorBackend.registerCommand("Profiler.isSampling", [], ["result"]); +InspectorBackend.registerCommand("Profiler.hasHeapProfiler", [], ["result"]); +InspectorBackend.registerCommand("Profiler.enable", [], []); +InspectorBackend.registerCommand("Profiler.disable", [], []); +InspectorBackend.registerCommand("Profiler.start", [], []); +InspectorBackend.registerCommand("Profiler.stop", [], []); +InspectorBackend.registerCommand("Profiler.getProfileHeaders", [], ["headers"]); +InspectorBackend.registerCommand("Profiler.getProfile", [{"name": "type", "type": "string", "optional": false}, {"name": "uid", "type": "number", "optional": false}], ["profile"]); +InspectorBackend.registerCommand("Profiler.removeProfile", [{"name": "type", "type": "string", "optional": false}, {"name": "uid", "type": "number", "optional": false}], []); +InspectorBackend.registerCommand("Profiler.clearProfiles", [], []); +InspectorBackend.registerCommand("Profiler.takeHeapSnapshot", [], []); +InspectorBackend.registerCommand("Profiler.collectGarbage", [], []); +InspectorBackend.registerCommand("Profiler.getObjectByHeapObjectId", [{"name": "objectId", "type": "number", "optional": false}], ["result"]); + +// Worker. +InspectorBackend.registerWorkerDispatcher = InspectorBackend.registerDomainDispatcher.bind(InspectorBackend, "Worker"); +InspectorBackend.registerEvent("Worker.workerCreated", ["workerId", "url", "inspectorConnected"]); +InspectorBackend.registerEvent("Worker.workerTerminated", ["workerId"]); +InspectorBackend.registerEvent("Worker.dispatchMessageFromWorker", ["workerId", "message"]); +InspectorBackend.registerEvent("Worker.disconnectedFromWorker", []); +InspectorBackend.registerCommand("Worker.setWorkerInspectionEnabled", [{"name": "value", "type": "boolean", "optional": false}], []); +InspectorBackend.registerCommand("Worker.sendMessageToWorker", [{"name": "workerId", "type": "number", "optional": false}, {"name": "message", "type": "object", "optional": false}], []); +InspectorBackend.registerCommand("Worker.connectToWorker", [{"name": "workerId", "type": "number", "optional": false}], []); +InspectorBackend.registerCommand("Worker.disconnectFromWorker", [{"name": "workerId", "type": "number", "optional": false}], []); +InspectorBackend.registerCommand("Worker.setAutoconnectToWorkers", [{"name": "value", "type": "boolean", "optional": false}], []); + + +/* ExtensionRegistryStub.js */ + +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +if (!window.InspectorExtensionRegistry) { + +/** + * @constructor + */ +WebInspector.InspectorExtensionRegistryStub = function() +{ +} + +WebInspector.InspectorExtensionRegistryStub.prototype = { + getExtensionsAsync: function() + { + } +} + +var InspectorExtensionRegistry = new WebInspector.InspectorExtensionRegistryStub(); + +} +/* InspectorFrontendAPI.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +InspectorFrontendAPI = { + _pendingCommands: [], + + isDebuggingEnabled: function() + { + return WebInspector.panels.scripts.debuggingEnabled; + }, + + setDebuggingEnabled: function(enabled) + { + if (enabled) { + WebInspector.panels.scripts.enableDebugging(); + WebInspector.inspectorView.setCurrentPanel(WebInspector.panels.scripts); + } else + WebInspector.panels.scripts.disableDebugging(); + }, + + isTimelineProfilingEnabled: function() + { + return WebInspector.panels.timeline.timelineProfilingEnabled; + }, + + setTimelineProfilingEnabled: function(enabled) + { + WebInspector.panels.timeline.setTimelineProfilingEnabled(enabled); + }, + + isProfilingJavaScript: function() + { + return WebInspector.CPUProfileType.instance && WebInspector.CPUProfileType.instance.isRecordingProfile(); + }, + + startProfilingJavaScript: function() + { + WebInspector.panels.profiles.enableProfiler(); + WebInspector.inspectorView.setCurrentPanel(WebInspector.panels.profiles); + if (WebInspector.CPUProfileType.instance) + WebInspector.CPUProfileType.instance.startRecordingProfile(); + }, + + stopProfilingJavaScript: function() + { + if (WebInspector.CPUProfileType.instance) + WebInspector.CPUProfileType.instance.stopRecordingProfile(); + WebInspector.inspectorView.setCurrentPanel(WebInspector.panels.profiles); + }, + + setAttachedWindow: function(attached) + { + WebInspector.attached = attached; + }, + + showConsole: function() + { + WebInspector.inspectorView.setCurrentPanel(WebInspector.panels.console); + }, + + dispatch: function(signature) + { + if (WebInspector.panels) { + var methodName = signature.shift(); + return InspectorFrontendAPI[methodName].apply(InspectorFrontendAPI, signature); + } + InspectorFrontendAPI._pendingCommands.push(signature); + }, + + loadCompleted: function() + { + for (var i = 0; i < InspectorFrontendAPI._pendingCommands.length; ++i) + InspectorFrontendAPI.dispatch(InspectorFrontendAPI._pendingCommands[i]); + InspectorFrontendAPI._pendingCommands = []; + } +} +/* Object.js */ + +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + */ +WebInspector.Object = function() { +} + +WebInspector.Object.prototype = { + /** + * @param {string} eventType + * @param {function(WebInspector.Event)} listener + * @param {Object=} thisObject + */ + addEventListener: function(eventType, listener, thisObject) + { + console.assert(listener); + + if (!this._listeners) + this._listeners = {}; + if (!this._listeners[eventType]) + this._listeners[eventType] = []; + this._listeners[eventType].push({ thisObject: thisObject, listener: listener }); + }, + + /** + * @param {string} eventType + * @param {function(WebInspector.Event)} listener + * @param {Object=} thisObject + */ + removeEventListener: function(eventType, listener, thisObject) + { + console.assert(listener); + + if (!this._listeners || !this._listeners[eventType]) + return; + var listeners = this._listeners[eventType]; + for (var i = 0; i < listeners.length; ++i) { + if (listener && listeners[i].listener === listener && listeners[i].thisObject === thisObject) + listeners.splice(i, 1); + else if (!listener && thisObject && listeners[i].thisObject === thisObject) + listeners.splice(i, 1); + } + + if (!listeners.length) + delete this._listeners[eventType]; + }, + + removeAllListeners: function() + { + delete this._listeners; + }, + + /** + * @param {string} eventType + * @return {boolean} + */ + hasEventListeners: function(eventType) + { + if (!this._listeners || !this._listeners[eventType]) + return false; + return true; + }, + + /** + * @param {string} eventType + * @param {*=} eventData + * @return {boolean} + */ + dispatchEventToListeners: function(eventType, eventData) + { + if (!this._listeners || !this._listeners[eventType]) + return false; + + var event = new WebInspector.Event(this, eventType, eventData); + var listeners = this._listeners[eventType].slice(0); + for (var i = 0; i < listeners.length; ++i) { + listeners[i].listener.call(listeners[i].thisObject, event); + if (event._stoppedPropagation) + break; + } + + return event.defaultPrevented; + } +} + +/** + * @constructor + * @param {WebInspector.Object} target + * @param {string} type + * @param {*=} data + */ +WebInspector.Event = function(target, type, data) +{ + this.target = target; + this.type = type; + this.data = data; + this.defaultPrevented = false; + this._stoppedPropagation = false; +} + +WebInspector.Event.prototype = { + stopPropagation: function() + { + this._stoppedPropagation = true; + }, + + preventDefault: function() + { + this.defaultPrevented = true; + } +} + +WebInspector.notifications = new WebInspector.Object(); +/* Settings.js */ + +/* + * Copyright (C) 2009 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +var Preferences = { + maxInlineTextChildLength: 80, + minConsoleHeight: 75, + minSidebarWidth: 100, + minElementsSidebarWidth: 200, + minScriptsSidebarWidth: 200, + styleRulesExpandedState: {}, + showMissingLocalizedStrings: false, + useLowerCaseMenuTitlesOnWindows: false, + sharedWorkersDebugNote: undefined, + localizeUI: true, + exposeDisableCache: false, + exposeWorkersInspection: false, + applicationTitle: "Web Inspector - %s", + showHeapSnapshotObjectsHiddenProperties: false, + showDockToRight: false +} + +var Capabilities = { + samplingCPUProfiler: false, + debuggerCausesRecompilation: true, + profilerCausesRecompilation: true, + nativeInstrumentationEnabled: false, + heapProfilerPresent: false +} + +/** + * @constructor + */ +WebInspector.Settings = function() +{ + this._eventSupport = new WebInspector.Object(); + + this.colorFormat = this.createSetting("colorFormat", "hex"); + this.consoleHistory = this.createSetting("consoleHistory", []); + this.debuggerEnabled = this.createSetting("debuggerEnabled", false); + this.domWordWrap = this.createSetting("domWordWrap", true); + this.profilerEnabled = this.createSetting("profilerEnabled", false); + this.eventListenersFilter = this.createSetting("eventListenersFilter", "all"); + this.lastActivePanel = this.createSetting("lastActivePanel", "elements"); + this.lastViewedScriptFile = this.createSetting("lastViewedScriptFile", "application"); + this.monitoringXHREnabled = this.createSetting("monitoringXHREnabled", false); + this.preserveConsoleLog = this.createSetting("preserveConsoleLog", false); + this.resourcesLargeRows = this.createSetting("resourcesLargeRows", true); + this.resourcesSortOptions = this.createSetting("resourcesSortOptions", {timeOption: "responseTime", sizeOption: "transferSize"}); + this.resourceViewTab = this.createSetting("resourceViewTab", "preview"); + this.showInheritedComputedStyleProperties = this.createSetting("showInheritedComputedStyleProperties", false); + this.showUserAgentStyles = this.createSetting("showUserAgentStyles", true); + this.watchExpressions = this.createSetting("watchExpressions", []); + this.breakpoints = this.createSetting("breakpoints", []); + this.eventListenerBreakpoints = this.createSetting("eventListenerBreakpoints", []); + this.domBreakpoints = this.createSetting("domBreakpoints", []); + this.xhrBreakpoints = this.createSetting("xhrBreakpoints", []); + this.sourceMapsEnabled = this.createSetting("sourceMapsEnabled", false); + this.cacheDisabled = this.createSetting("cacheDisabled", false); + this.overrideUserAgent = this.createSetting("overrideUserAgent", ""); + this.userAgent = this.createSetting("userAgent", ""); + this.showScriptFolders = this.createSetting("showScriptFolders", true); + this.dockToRight = this.createSetting("dockToRight", false); + + // If there are too many breakpoints in a storage, it is likely due to a recent bug that caused + // periodical breakpoints duplication leading to inspector slowness. + if (window.localStorage.breakpoints && window.localStorage.breakpoints.length > 500000) + delete window.localStorage.breakpoints; +} + +WebInspector.Settings.prototype = { + /** + * @return {WebInspector.Setting} + */ + createSetting: function(key, defaultValue) + { + return new WebInspector.Setting(key, defaultValue, this._eventSupport); + } +} + +/** + * @constructor + */ +WebInspector.Setting = function(name, defaultValue, eventSupport) +{ + this._name = name; + this._defaultValue = defaultValue; + this._eventSupport = eventSupport; +} + +WebInspector.Setting.prototype = { + addChangeListener: function(listener, thisObject) + { + this._eventSupport.addEventListener(this._name, listener, thisObject); + }, + + removeChangeListener: function(listener, thisObject) + { + this._eventSupport.removeEventListener(this._name, listener, thisObject); + }, + + get name() + { + return this._name; + }, + + get: function() + { + var value = this._defaultValue; + if (window.localStorage != null && this._name in window.localStorage) { + try { + value = JSON.parse(window.localStorage[this._name]); + } catch(e) { + window.localStorage.removeItem(this._name); + } + } + return value; + }, + + set: function(value) + { + if (window.localStorage != null) { + try { + window.localStorage[this._name] = JSON.stringify(value); + } catch(e) { + console.error("Error saving setting with name:" + this._name); + } + } + this._eventSupport.dispatchEventToListeners(this._name, value); + } +} + +/** + * @constructor + */ +WebInspector.ExperimentsSettings = function() +{ + this._setting = WebInspector.settings.createSetting("experiments", {}); + this._experiments = []; + this._enabledForTest = {}; + + // Add currently running experiments here. + // FIXME: Move out from experiments once navigator is production-ready. + this.useScriptsNavigator = this._createExperiment("useScriptsNavigator", "Use file navigator and tabbed editor container in scripts panel"); + this.sourceFrameAlwaysEditable = this._createExperiment("sourceFrameAlwaysEditable", "Make resources always editable"); + this.freeFlowDOMEditing = this._createExperiment("freeFlowDOMEditing", "Enable free flow DOM editing"); + + this._cleanUpSetting(); +} + +WebInspector.ExperimentsSettings.prototype = { + /** + * @type {Array.} + */ + get experiments() + { + return this._experiments.slice(); + }, + + /** + * @type {boolean} + */ + get experimentsEnabled() + { + return "experiments" in WebInspector.queryParamsObject; + }, + + /** + * @param {string} experimentName + * @param {string} experimentTitle + * @return {WebInspector.Experiment} + */ + _createExperiment: function(experimentName, experimentTitle) + { + var experiment = new WebInspector.Experiment(this, experimentName, experimentTitle); + this._experiments.push(experiment); + return experiment; + }, + + /** + * @param {string} experimentName + * @return {boolean} + */ + isEnabled: function(experimentName) + { + if (this._enabledForTest[experimentName]) + return true; + + if (!this.experimentsEnabled) + return false; + + var experimentsSetting = this._setting.get(); + return experimentsSetting[experimentName]; + }, + + /** + * @param {string} experimentName + * @param {boolean} enabled + */ + setEnabled: function(experimentName, enabled) + { + var experimentsSetting = this._setting.get(); + experimentsSetting[experimentName] = enabled; + this._setting.set(experimentsSetting); + }, + + /** + * @param {string} experimentName + */ + _enableForTest: function(experimentName) + { + this._enabledForTest[experimentName] = true; + }, + + _cleanUpSetting: function() + { + var experimentsSetting = this._setting.get(); + var cleanedUpExperimentSetting = {}; + for (var i = 0; i < this._experiments.length; ++i) { + var experimentName = this._experiments[i].name; + if (experimentsSetting[experimentName]) + cleanedUpExperimentSetting[experimentName] = true; + } + this._setting.set(cleanedUpExperimentSetting); + } +} + +/** + * @constructor + * @param {WebInspector.ExperimentsSettings} experimentsSettings + * @param {string} name + * @param {string} title + */ +WebInspector.Experiment = function(experimentsSettings, name, title) +{ + this._name = name; + this._title = title; + this._experimentsSettings = experimentsSettings; +} + +WebInspector.Experiment.prototype = { + /** + * @return {string} + */ + get name() + { + return this._name; + }, + + /** + * @return {string} + */ + get title() + { + return this._title; + }, + + /** + * @return {boolean} + */ + isEnabled: function() + { + return this._experimentsSettings.isEnabled(this._name); + }, + + /** + * @param {boolean} enabled + */ + setEnabled: function(enabled) + { + return this._experimentsSettings.setEnabled(this._name, enabled); + }, + + enableForTest: function() + { + this._experimentsSettings._enableForTest(this._name); + } +} + +WebInspector.settings = new WebInspector.Settings(); +WebInspector.experimentsSettings = new WebInspector.ExperimentsSettings(); +/* InspectorFrontendHostStub.js */ + +/* + * Copyright (C) 2009 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +if (!window.InspectorFrontendHost) { + +/** + * @constructor + */ +WebInspector.InspectorFrontendHostStub = function() +{ + this._attachedWindowHeight = 0; +} + +WebInspector.InspectorFrontendHostStub.prototype = { + platform: function() + { + var match = navigator.userAgent.match(/Windows NT/); + if (match) + return "windows"; + match = navigator.userAgent.match(/Mac OS X/); + if (match) + return "mac"; + return "linux"; + }, + + port: function() + { + return "unknown"; + }, + + bringToFront: function() + { + this._windowVisible = true; + }, + + closeWindow: function() + { + this._windowVisible = false; + }, + + requestAttachWindow: function() + { + }, + + requestDetachWindow: function() + { + }, + + requestSetDockSide: function() + { + }, + + search: function(sourceRow, query) + { + }, + + setAttachedWindowHeight: function(height) + { + }, + + moveWindowBy: function(x, y) + { + }, + + setInjectedScriptForOrigin: function(origin, script) + { + }, + + loaded: function() + { + }, + + localizedStringsURL: function() + { + return undefined; + }, + + hiddenPanels: function() + { + return ""; + }, + + inspectedURLChanged: function(url) + { + document.title = WebInspector.UIString(Preferences.applicationTitle, url); + }, + + copyText: function() + { + }, + + openInNewTab: function(url) + { + window.open(url, "_blank"); + }, + + canSaveAs: function(fileName, content) + { + return true; + }, + + saveAs: function(fileName, content) + { + var builder = new WebKitBlobBuilder(); + builder.append(content); + var blob = builder.getBlob("application/octet-stream"); + + var fr = new FileReader(); + fr.onload = function(e) { + // Force download + window.location = this.result; + } + fr.readAsDataURL(blob); + }, + + canAttachWindow: function() + { + return false; + }, + + sendMessageToBackend: function(message) + { + }, + + recordActionTaken: function(actionCode) + { + }, + + recordPanelShown: function(panelCode) + { + }, + + recordSettingChanged: function(settingCode) + { + }, + + loadResourceSynchronously: function(url) + { + return ""; + } +} + +var InspectorFrontendHost = new WebInspector.InspectorFrontendHostStub(); +Preferences.localizeUI = false; + +} +/* Checkbox.js */ + +/* + * Copyright (C) 2010 Google Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @param {string=} tooltip + */ +WebInspector.Checkbox = function(label, className, tooltip) +{ + this.element = document.createElement('label'); + this._inputElement = document.createElement('input'); + this._inputElement.type = "checkbox"; + + this.element.className = className; + this.element.appendChild(this._inputElement); + this.element.appendChild(document.createTextNode(label)); + if (tooltip) + this.element.title = tooltip; +} + +WebInspector.Checkbox.prototype = { + set checked(checked) + { + this._inputElement.checked = checked; + }, + + get checked() + { + return this._inputElement.checked; + }, + + addEventListener: function(listener) + { + function listenerWrapper(event) + { + if (listener) + listener(event); + event.stopPropagation(); + return true; + } + + this._inputElement.addEventListener("click", listenerWrapper, false); + this.element.addEventListener("click", listenerWrapper, false); + } +} +/* ContextMenu.js */ + +/* + * Copyright (C) 2009 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + */ +WebInspector.ContextMenu = function() { + this._items = []; + this._handlers = {}; +} + +WebInspector.ContextMenu.prototype = { + show: function(event) + { + // Remove trailing separator. + while (this._items.length > 0 && !("id" in this._items[this._items.length - 1])) + this._items.splice(this._items.length - 1, 1); + + if (this._items.length) { + WebInspector._contextMenu = this; + InspectorFrontendHost.showContextMenu(event, this._items); + } + event.stopPropagation(); + }, + + /** + * @param {boolean=} disabled + */ + appendItem: function(label, handler, disabled) + { + var id = this._items.length; + this._items.push({type: "item", id: id, label: label, enabled: !disabled}); + this._handlers[id] = handler; + }, + + /** + * @param {boolean=} disabled + */ + appendCheckboxItem: function(label, handler, checked, disabled) + { + var id = this._items.length; + this._items.push({type: "checkbox", id: id, label: label, checked: !!checked, enabled: !disabled}); + this._handlers[id] = handler; + }, + + appendSeparator: function() + { + // No separator dupes allowed. + if (this._items.length === 0) + return; + if (!("id" in this._items[this._items.length - 1])) + return; + this._items.push({type: "separator"}); + }, + + _itemSelected: function(id) + { + if (this._handlers[id]) + this._handlers[id].call(this); + } +} + +WebInspector.contextMenuItemSelected = function(id) +{ + if (WebInspector._contextMenu) + WebInspector._contextMenu._itemSelected(id); +} + +WebInspector.contextMenuCleared = function() +{ + // FIXME: Unfortunately, contextMenuCleared is invoked between show and item selected + // so we can't delete last menu object from WebInspector. Fix the contract. +} +/* SoftContextMenu.js */ + +/* + * Copyright (C) 2011 Google Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +if (!InspectorFrontendHost.showContextMenu) { + +/** + * @constructor + */ +WebInspector.SoftContextMenu = function(items) +{ + this._items = items; +} + +WebInspector.SoftContextMenu.prototype = { + show: function(event) + { + this._x = event.x; + this._y = event.y; + this._time = new Date().getTime(); + + // Absolutely position menu for iframes. + var absoluteX = event.pageX; + var absoluteY = event.pageY; + var targetElement = event.target; + while (targetElement && window !== targetElement.ownerDocument.defaultView) { + var frameElement = targetElement.ownerDocument.defaultView.frameElement; + absoluteY += frameElement.totalOffsetTop(); + absoluteX += frameElement.totalOffsetLeft(); + targetElement = frameElement; + } + + // Install glass pane capturing events. + this._glassPaneElement = document.createElement("div"); + this._glassPaneElement.className = "soft-context-menu-glass-pane"; + this._glassPaneElement.tabIndex = 0; + this._glassPaneElement.addEventListener("mouseup", this._glassPaneMouseUp.bind(this), false); + + // Create context menu. + this._contextMenuElement = document.createElement("div"); + this._contextMenuElement.className = "soft-context-menu"; + this._contextMenuElement.tabIndex = 0; + this._contextMenuElement.style.top = absoluteY + "px"; + this._contextMenuElement.style.left = absoluteX + "px"; + + this._contextMenuElement.addEventListener("mousedown", this._discardMenu.bind(this), false); + this._contextMenuElement.addEventListener("keydown", this._menuKeyDown.bind(this), false); + this._contextMenuElement.addEventListener("blur", this._discardMenu.bind(this), false); + + for (var i = 0; i < this._items.length; ++i) + this._contextMenuElement.appendChild(this._createMenuItem(this._items[i])); + + this._glassPaneElement.appendChild(this._contextMenuElement); + document.body.appendChild(this._glassPaneElement); + this._contextMenuElement.focus(); + + // Re-position menu in case it does not fit. + if (document.body.offsetWidth < this._contextMenuElement.offsetLeft + this._contextMenuElement.offsetWidth) + this._contextMenuElement.style.left = (absoluteX - this._contextMenuElement.offsetWidth) + "px"; + if (document.body.offsetHeight < this._contextMenuElement.offsetTop + this._contextMenuElement.offsetHeight) + this._contextMenuElement.style.top = (document.body.offsetHeight - this._contextMenuElement.offsetHeight) + "px"; + + event.stopPropagation(); + event.preventDefault(); + }, + + _createMenuItem: function(item) + { + if (item.type === "separator") + return this._createSeparator(); + + var menuItemElement = document.createElement("div"); + menuItemElement.className = "soft-context-menu-item"; + + var checkMarkElement = document.createElement("span"); + checkMarkElement.textContent = "\u2713 "; // Checkmark Unicode symbol + checkMarkElement.className = "soft-context-menu-item-checkmark"; + if (!item.checked) + checkMarkElement.style.opacity = "0"; + + menuItemElement.appendChild(checkMarkElement); + menuItemElement.appendChild(document.createTextNode(item.label)); + + menuItemElement.addEventListener("mousedown", this._menuItemMouseDown.bind(this), false); + menuItemElement.addEventListener("mouseup", this._menuItemMouseUp.bind(this), false); + + // Manually manage hover highlight since :hover does not work in case of click-and-hold menu invocation. + menuItemElement.addEventListener("mouseover", this._menuItemMouseOver.bind(this), false); + menuItemElement.addEventListener("mouseout", this._menuItemMouseOut.bind(this), false); + + menuItemElement._actionId = item.id; + return menuItemElement; + }, + + _createSeparator: function() + { + var separatorElement = document.createElement("div"); + separatorElement.className = "soft-context-menu-separator"; + separatorElement._isSeparator = true; + return separatorElement; + }, + + _menuItemMouseDown: function(event) + { + // Do not let separator's mouse down hit menu's handler - we need to receive mouse up! + event.stopPropagation(); + event.preventDefault(); + }, + + _menuItemMouseUp: function(event) + { + this._triggerAction(event.target, event); + }, + + _triggerAction: function(menuItemElement, event) + { + this._discardMenu(event); + if (typeof menuItemElement._actionId !== "undefined") { + WebInspector.contextMenuItemSelected(menuItemElement._actionId); + delete menuItemElement._actionId; + } + }, + + _menuItemMouseOver: function(event) + { + this._highlightMenuItem(event.target); + }, + + _menuItemMouseOut: function(event) + { + this._highlightMenuItem(null); + }, + + _highlightMenuItem: function(menuItemElement) + { + if (this._highlightedMenuItemElement) + this._highlightedMenuItemElement.removeStyleClass("soft-context-menu-item-mouse-over"); + this._highlightedMenuItemElement = menuItemElement; + if (this._highlightedMenuItemElement) + this._highlightedMenuItemElement.addStyleClass("soft-context-menu-item-mouse-over"); + }, + + _highlightPrevious: function() + { + var menuItemElement = this._highlightedMenuItemElement ? this._highlightedMenuItemElement.previousSibling : this._contextMenuElement.lastChild; + while (menuItemElement && menuItemElement._isSeparator) + menuItemElement = menuItemElement.previousSibling; + if (menuItemElement) + this._highlightMenuItem(menuItemElement); + }, + + _highlightNext: function() + { + var menuItemElement = this._highlightedMenuItemElement ? this._highlightedMenuItemElement.nextSibling : this._contextMenuElement.firstChild; + while (menuItemElement && menuItemElement._isSeparator) + menuItemElement = menuItemElement.nextSibling; + if (menuItemElement) + this._highlightMenuItem(menuItemElement); + }, + + _menuKeyDown: function(event) + { + switch (event.keyIdentifier) { + case "Up": + this._highlightPrevious(); break; + case "Down": + this._highlightNext(); break; + case "U+001B": // Escape + this._discardMenu(event); break; + case "Enter": + if (!isEnterKey(event)) + break; + // Fall through + case "U+0020": // Space + if (this._highlightedMenuItemElement) + this._triggerAction(this._highlightedMenuItemElement, event); + break; + } + event.stopPropagation(); + event.preventDefault(); + }, + + _glassPaneMouseUp: function(event) + { + // Return if this is simple 'click', since dispatched on glass pane, can't use 'click' event. + if (event.x === this._x && event.y === this._y && new Date().getTime() - this._time < 300) + return; + this._discardMenu(event); + }, + + _discardMenu: function(event) + { + if (this._glassPaneElement) { + var glassPane = this._glassPaneElement; + delete this._glassPaneElement; + // This can re-enter discardMenu due to blur. + document.body.removeChild(glassPane); + + event.stopPropagation(); + event.preventDefault(); + } + } +} + +InspectorFrontendHost.showContextMenu = function(event, items) +{ + new WebInspector.SoftContextMenu(items).show(event); +} + +} +/* KeyboardShortcut.js */ + +/* + * Copyright (C) 2009 Apple Inc. All rights reserved. + * Copyright (C) 2009 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + */ +WebInspector.KeyboardShortcut = function() +{ +} + +/** + * Constants for encoding modifier key set as a bit mask. + * @see #_makeKeyFromCodeAndModifiers + */ +WebInspector.KeyboardShortcut.Modifiers = { + None: 0, // Constant for empty modifiers set. + Shift: 1, + Ctrl: 2, + Alt: 4, + Meta: 8, // Command key on Mac, Win key on other platforms. + get CtrlOrMeta() + { + // "default" command/ctrl key for platform, Command on Mac, Ctrl on other platforms + return WebInspector.isMac() ? this.Meta : this.Ctrl; + } +}; + +WebInspector.KeyboardShortcut.Keys = { + Backspace: { code: 8, name: "\u21a4" }, + Tab: { code: 9, name: { mac: "\u21e5", other: "" } }, + Enter: { code: 13, name: { mac: "\u21a9", other: "" } }, + Esc: { code: 27, name: { mac: "\u238b", other: "" } }, + Space: { code: 32, name: "" }, + PageUp: { code: 33, name: { mac: "\u21de", other: "" } }, // also NUM_NORTH_EAST + PageDown: { code: 34, name: { mac: "\u21df", other: "" } }, // also NUM_SOUTH_EAST + End: { code: 35, name: { mac: "\u2197", other: "" } }, // also NUM_SOUTH_WEST + Home: { code: 36, name: { mac: "\u2196", other: "" } }, // also NUM_NORTH_WEST + Left: { code: 37, name: "\u2190" }, // also NUM_WEST + Up: { code: 38, name: "\u2191" }, // also NUM_NORTH + Right: { code: 39, name: "\u2192" }, // also NUM_EAST + Down: { code: 40, name: "\u2193" }, // also NUM_SOUTH + Delete: { code: 46, name: "" }, + Zero: { code: 48, name: "0" }, + F1: { code: 112, name: "F1" }, + F2: { code: 113, name: "F2" }, + F3: { code: 114, name: "F3" }, + F4: { code: 115, name: "F4" }, + F5: { code: 116, name: "F5" }, + F6: { code: 117, name: "F6" }, + F7: { code: 118, name: "F7" }, + F8: { code: 119, name: "F8" }, + F9: { code: 120, name: "F9" }, + F10: { code: 121, name: "F10" }, + F11: { code: 122, name: "F11" }, + F12: { code: 123, name: "F12" }, + Semicolon: { code: 186, name: ";" }, + Plus: { code: 187, name: "+" }, + Comma: { code: 188, name: "," }, + Minus: { code: 189, name: "-" }, + Period: { code: 190, name: "." }, + Slash: { code: 191, name: "/" }, + Apostrophe: { code: 192, name: "`" }, + SingleQuote: { code: 222, name: "\'" } +}; + +/** + * Creates a number encoding keyCode in the lower 8 bits and modifiers mask in the higher 8 bits. + * It is useful for matching pressed keys. + * keyCode is the Code of the key, or a character "a-z" which is converted to a keyCode value. + * @param {number=} modifiers Optional list of modifiers passed as additional paramerters. + */ +WebInspector.KeyboardShortcut.makeKey = function(keyCode, modifiers) +{ + if (typeof keyCode === "string") + keyCode = keyCode.charCodeAt(0) - 32; + modifiers = modifiers || WebInspector.KeyboardShortcut.Modifiers.None; + return WebInspector.KeyboardShortcut._makeKeyFromCodeAndModifiers(keyCode, modifiers); +} + +WebInspector.KeyboardShortcut.makeKeyFromEvent = function(keyboardEvent) +{ + var modifiers = WebInspector.KeyboardShortcut.Modifiers.None; + if (keyboardEvent.shiftKey) + modifiers |= WebInspector.KeyboardShortcut.Modifiers.Shift; + if (keyboardEvent.ctrlKey) + modifiers |= WebInspector.KeyboardShortcut.Modifiers.Ctrl; + if (keyboardEvent.altKey) + modifiers |= WebInspector.KeyboardShortcut.Modifiers.Alt; + if (keyboardEvent.metaKey) + modifiers |= WebInspector.KeyboardShortcut.Modifiers.Meta; + return WebInspector.KeyboardShortcut._makeKeyFromCodeAndModifiers(keyboardEvent.keyCode, modifiers); +} + +WebInspector.KeyboardShortcut.eventHasCtrlOrMeta = function(event) +{ + return WebInspector.isMac() ? event.metaKey && !event.ctrlKey : event.ctrlKey && !event.metaKey; +} + +/** + * @param {number=} modifiers + */ +WebInspector.KeyboardShortcut.makeDescriptor = function(key, modifiers) +{ + return { + key: WebInspector.KeyboardShortcut.makeKey(typeof key === "string" ? key : key.code, modifiers), + name: WebInspector.KeyboardShortcut.shortcutToString(key, modifiers) + }; +} + +/** + * @param {number=} modifiers + */ +WebInspector.KeyboardShortcut.shortcutToString = function(key, modifiers) +{ + return WebInspector.KeyboardShortcut._modifiersToString(modifiers) + WebInspector.KeyboardShortcut._keyName(key); +} + +WebInspector.KeyboardShortcut._keyName = function(key) +{ + if (typeof key === "string") + return key.toUpperCase(); + if (typeof key.name === "string") + return key.name; + return key.name[WebInspector.platform()] || key.name.other; +} + +WebInspector.KeyboardShortcut._makeKeyFromCodeAndModifiers = function(keyCode, modifiers) +{ + return (keyCode & 255) | (modifiers << 8); +}; + +WebInspector.KeyboardShortcut._modifiersToString = function(modifiers) +{ + const cmdKey = "\u2318"; + const optKey = "\u2325"; + const shiftKey = "\u21e7"; + const ctrlKey = "\u2303"; + + var isMac = WebInspector.isMac(); + var res = ""; + if (modifiers & WebInspector.KeyboardShortcut.Modifiers.Ctrl) + res += isMac ? ctrlKey : " + "; + if (modifiers & WebInspector.KeyboardShortcut.Modifiers.Alt) + res += isMac ? optKey : " + "; + if (modifiers & WebInspector.KeyboardShortcut.Modifiers.Shift) + res += isMac ? shiftKey : " + "; + if (modifiers & WebInspector.KeyboardShortcut.Modifiers.Meta) + res += isMac ? cmdKey : " + "; + + return res; +}; +/* TextPrompt.js */ + +/* + * Copyright (C) 2008 Apple Inc. All rights reserved. + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends WebInspector.Object + * @param {function(Range, boolean, function(Array.=))} completions + * @param {string} stopCharacters + */ +WebInspector.TextPrompt = function(completions, stopCharacters) +{ + /** + * @type {Element|undefined} + */ + this._proxyElement; + this._proxyElementDisplay = "inline-block"; + this._loadCompletions = completions; + this._completionStopCharacters = stopCharacters; + this._suggestForceable = true; +} + +WebInspector.TextPrompt.Events = { + ItemApplied: "text-prompt-item-applied", + ItemAccepted: "text-prompt-item-accepted" +}; + +WebInspector.TextPrompt.prototype = { + get proxyElement() + { + return this._proxyElement; + }, + + setSuggestForceable: function(x) + { + this._suggestForceable = x; + }, + + setSuggestBoxEnabled: function(className) + { + this._suggestBoxClassName = className; + }, + + renderAsBlock: function() + { + this._proxyElementDisplay = "block"; + }, + + /** + * Clients should never attach any event listeners to the |element|. Instead, + * they should use the result of this method to attach listeners for bubbling events. + * + * @param {Element} element + */ + attach: function(element) + { + return this._attachInternal(element); + }, + + /** + * Clients should never attach any event listeners to the |element|. Instead, + * they should use the result of this method to attach listeners for bubbling events + * or the |blurListener| parameter to register a "blur" event listener on the |element| + * (since the "blur" event does not bubble.) + * + * @param {Element} element + * @param {function(Event)} blurListener + */ + attachAndStartEditing: function(element, blurListener) + { + this._attachInternal(element); + this._startEditing(blurListener); + return this.proxyElement; + }, + + _attachInternal: function(element) + { + if (this.proxyElement) + throw "Cannot attach an attached TextPrompt"; + this._element = element; + + this._boundOnKeyDown = this.onKeyDown.bind(this); + this._boundSelectStart = this._selectStart.bind(this); + this._proxyElement = element.ownerDocument.createElement("span"); + this._proxyElement.style.display = this._proxyElementDisplay; + element.parentElement.insertBefore(this.proxyElement, element); + this.proxyElement.appendChild(element); + this._element.addStyleClass("text-prompt"); + this._element.addEventListener("keydown", this._boundOnKeyDown, false); + this._element.addEventListener("selectstart", this._selectStart.bind(this), false); + + if (typeof this._suggestBoxClassName === "string") + this._suggestBox = new WebInspector.TextPrompt.SuggestBox(this, this._element, this._suggestBoxClassName); + + return this.proxyElement; + }, + + detach: function() + { + this._removeFromElement(); + this.proxyElement.parentElement.insertBefore(this._element, this.proxyElement); + this.proxyElement.parentElement.removeChild(this.proxyElement); + delete this._proxyElement; + WebInspector.restoreFocusFromElement(this._element); + }, + + get text() + { + return this._element.textContent; + }, + + set text(x) + { + this._removeSuggestionAids(); + if (!x) { + // Append a break element instead of setting textContent to make sure the selection is inside the prompt. + this._element.removeChildren(); + this._element.appendChild(document.createElement("br")); + } else + this._element.textContent = x; + + this.moveCaretToEndOfPrompt(); + this._element.scrollIntoView(); + }, + + _removeFromElement: function() + { + this.clearAutoComplete(true); + this._element.removeEventListener("keydown", this._boundOnKeyDown, false); + this._element.removeEventListener("selectstart", this._boundSelectStart, false); + if (this._isEditing) + this._stopEditing(); + if (this._suggestBox) + this._suggestBox.removeFromElement(); + }, + + _startEditing: function(blurListener) + { + if (!WebInspector.markBeingEdited(this._element, true)) + return; + this._isEditing = true; + this._element.addStyleClass("editing"); + if (blurListener) { + this._blurListener = blurListener; + this._element.addEventListener("blur", this._blurListener, false); + } + this._oldTabIndex = this._element.tabIndex; + if (this._element.tabIndex < 0) + this._element.tabIndex = 0; + WebInspector.setCurrentFocusElement(this._element); + }, + + _stopEditing: function() + { + this._element.tabIndex = this._oldTabIndex; + if (this._blurListener) + this._element.removeEventListener("blur", this._blurListener, false); + this._element.removeStyleClass("editing"); + delete this._isEditing; + WebInspector.markBeingEdited(this._element, false); + }, + + _removeSuggestionAids: function() + { + this.clearAutoComplete(); + this.hideSuggestBox(); + }, + + _selectStart: function(event) + { + if (this._selectionTimeout) + clearTimeout(this._selectionTimeout); + + this._removeSuggestionAids(); + + function moveBackIfOutside() + { + delete this._selectionTimeout; + if (!this.isCaretInsidePrompt() && window.getSelection().isCollapsed) { + this.moveCaretToEndOfPrompt(); + this.autoCompleteSoon(); + } + } + + this._selectionTimeout = setTimeout(moveBackIfOutside.bind(this), 100); + }, + + /** + * @param {boolean=} force + */ + defaultKeyHandler: function(event, force) + { + this.clearAutoComplete(); + this.autoCompleteSoon(force); + return false; + }, + + onKeyDown: function(event) + { + var handled = false; + var invokeDefault = true; + + switch (event.keyIdentifier) { + case "Up": + handled = this.upKeyPressed(event); + break; + case "Down": + handled = this.downKeyPressed(event); + break; + case "PageUp": + handled = this.pageUpKeyPressed(event); + break; + case "PageDown": + handled = this.pageDownKeyPressed(event); + break; + case "U+0009": // Tab + handled = this.tabKeyPressed(event); + break; + case "Enter": + handled = this.enterKeyPressed(event); + break; + case "Left": + case "Home": + this._removeSuggestionAids(); + invokeDefault = false; + break; + case "Right": + case "End": + if (this.isCaretAtEndOfPrompt()) + handled = this.acceptAutoComplete(); + else + this._removeSuggestionAids(); + invokeDefault = false; + break; + case "U+001B": // Esc + if (this.isSuggestBoxVisible()) { + this._suggestBox.hide(); + handled = true; + } + break; + case "U+0020": // Space + if (this._suggestForceable && event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey) { + this.defaultKeyHandler(event, true); + handled = true; + } + break; + case "Alt": + case "Meta": + case "Shift": + case "Control": + invokeDefault = false; + break; + } + + if (!handled && invokeDefault) + handled = this.defaultKeyHandler(event); + + if (handled) { + event.preventDefault(); + event.stopPropagation(); + } + + return handled; + }, + + acceptAutoComplete: function() + { + var result = false; + if (this.isSuggestBoxVisible()) + result = this._suggestBox.acceptSuggestion(); + if (!result) + result = this.acceptSuggestion(); + + return result; + }, + + /** + * @param {boolean=} includeTimeout + */ + clearAutoComplete: function(includeTimeout) + { + if (includeTimeout && this._completeTimeout) { + clearTimeout(this._completeTimeout); + delete this._completeTimeout; + } + + if (!this.autoCompleteElement) + return; + + if (this.autoCompleteElement.parentNode) + this.autoCompleteElement.parentNode.removeChild(this.autoCompleteElement); + delete this.autoCompleteElement; + + if (!this._userEnteredRange || !this._userEnteredText) + return; + + this._userEnteredRange.deleteContents(); + this._element.pruneEmptyTextNodes(); + + var userTextNode = document.createTextNode(this._userEnteredText); + this._userEnteredRange.insertNode(userTextNode); + + var selectionRange = document.createRange(); + selectionRange.setStart(userTextNode, this._userEnteredText.length); + selectionRange.setEnd(userTextNode, this._userEnteredText.length); + + var selection = window.getSelection(); + selection.removeAllRanges(); + selection.addRange(selectionRange); + + delete this._userEnteredRange; + delete this._userEnteredText; + }, + + /** + * @param {boolean=} force + */ + autoCompleteSoon: function(force) + { + var immediately = this.isSuggestBoxVisible() || force; + if (!this._completeTimeout) + this._completeTimeout = setTimeout(this.complete.bind(this, true, force), immediately ? 0 : 250); + }, + + /** + * @param {boolean=} reverse + */ + complete: function(auto, force, reverse) + { + this.clearAutoComplete(true); + var selection = window.getSelection(); + if (!selection.rangeCount) + return; + + var selectionRange = selection.getRangeAt(0); + var isEmptyInput = selectionRange.commonAncestorContainer === this._element; // this._element has no child Text nodes. + + var shouldExit; + + // Do not attempt to auto-complete an empty input in the auto mode (only on demand). + if (auto && isEmptyInput && !force) + shouldExit = true; + else if (!auto && !isEmptyInput && !selectionRange.commonAncestorContainer.isDescendant(this._element)) + shouldExit = true; + else if (auto && !force && !this.isCaretAtEndOfPrompt() && !this.isSuggestBoxVisible()) + shouldExit = true; + else if (!selection.isCollapsed) + shouldExit = true; + else if (!force) { + // BUG72018: Do not show suggest box if caret is followed by a non-stop character. + var wordSuffixRange = selectionRange.startContainer.rangeOfWord(selectionRange.endOffset, this._completionStopCharacters, this._element, "forward"); + if (wordSuffixRange.toString().length) + shouldExit = true; + } + if (shouldExit) { + this.hideSuggestBox(); + return; + } + + var wordPrefixRange = selectionRange.startContainer.rangeOfWord(selectionRange.startOffset, this._completionStopCharacters, this._element, "backward"); + this._loadCompletions(wordPrefixRange, force, this._completionsReady.bind(this, selection, auto, wordPrefixRange, !!reverse)); + }, + + _boxForAnchorAtStart: function(selection, textRange) + { + var rangeCopy = selection.getRangeAt(0).cloneRange(); + var anchorElement = document.createElement("span"); + anchorElement.textContent = "\u200B"; + textRange.insertNode(anchorElement); + var box = anchorElement.boxInWindow(window); + anchorElement.parentElement.removeChild(anchorElement); + selection.removeAllRanges(); + selection.addRange(rangeCopy); + return box; + }, + + /** + * @param {Selection} selection + * @param {boolean} auto + * @param {Range} originalWordPrefixRange + * @param {boolean} reverse + * @param {Array.=} completions + */ + _completionsReady: function(selection, auto, originalWordPrefixRange, reverse, completions) + { + if (!completions || !completions.length) { + this.hideSuggestBox(); + return; + } + + var selectionRange = selection.getRangeAt(0); + + var fullWordRange = document.createRange(); + fullWordRange.setStart(originalWordPrefixRange.startContainer, originalWordPrefixRange.startOffset); + fullWordRange.setEnd(selectionRange.endContainer, selectionRange.endOffset); + + if (originalWordPrefixRange.toString() + selectionRange.toString() != fullWordRange.toString()) + return; + + this._userEnteredRange = fullWordRange; + this._userEnteredText = fullWordRange.toString(); + + if (this._suggestBox) + this._suggestBox.updateSuggestions(this._boxForAnchorAtStart(selection, fullWordRange), completions, !this.isCaretAtEndOfPrompt()); + + var wordPrefixLength = originalWordPrefixRange.toString().length; + + if (auto) + var completionText = completions[0]; + else { + if (completions.length === 1) { + var completionText = completions[0]; + wordPrefixLength = completionText.length; + } else { + var commonPrefix = completions[0]; + for (var i = 0; i < completions.length; ++i) { + var completion = completions[i]; + var lastIndex = Math.min(commonPrefix.length, completion.length); + for (var j = wordPrefixLength; j < lastIndex; ++j) { + if (commonPrefix[j] !== completion[j]) { + commonPrefix = commonPrefix.substr(0, j); + break; + } + } + } + wordPrefixLength = commonPrefix.length; + + if (selection.isCollapsed) + var completionText = completions[0]; + else { + var currentText = fullWordRange.toString(); + + var foundIndex = null; + for (var i = 0; i < completions.length; ++i) { + if (completions[i] === currentText) + foundIndex = i; + } + + var nextIndex = foundIndex + (reverse ? -1 : 1); + if (foundIndex === null || nextIndex >= completions.length) + var completionText = completions[0]; + else if (nextIndex < 0) + var completionText = completions[completions.length - 1]; + else + var completionText = completions[nextIndex]; + } + } + } + + if (auto) { + if (this.isCaretAtEndOfPrompt()) { + this._userEnteredRange.deleteContents(); + this._element.pruneEmptyTextNodes(); + var finalSelectionRange = document.createRange(); + var prefixText = completionText.substring(0, wordPrefixLength); + var suffixText = completionText.substring(wordPrefixLength); + + var prefixTextNode = document.createTextNode(prefixText); + fullWordRange.insertNode(prefixTextNode); + + this.autoCompleteElement = document.createElement("span"); + this.autoCompleteElement.className = "auto-complete-text"; + this.autoCompleteElement.textContent = suffixText; + + prefixTextNode.parentNode.insertBefore(this.autoCompleteElement, prefixTextNode.nextSibling); + + finalSelectionRange.setStart(prefixTextNode, wordPrefixLength); + finalSelectionRange.setEnd(prefixTextNode, wordPrefixLength); + selection.removeAllRanges(); + selection.addRange(finalSelectionRange); + } + } else + this.applySuggestion(completionText, completions.length > 1, originalWordPrefixRange); + }, + + /** + * @param {Range=} originalPrefixRange + */ + applySuggestion: function(completionText, isIntermediateSuggestion, originalPrefixRange) + { + var wordPrefixLength; + if (originalPrefixRange) + wordPrefixLength = originalPrefixRange.toString().length; + else + wordPrefixLength = this._userEnteredText ? this._userEnteredText.length : 0; + + this._userEnteredRange.deleteContents(); + this._element.pruneEmptyTextNodes(); + var finalSelectionRange = document.createRange(); + var completionTextNode = document.createTextNode(completionText); + this._userEnteredRange.insertNode(completionTextNode); + if (this.autoCompleteElement && this.autoCompleteElement.parentNode) { + this.autoCompleteElement.parentNode.removeChild(this.autoCompleteElement); + delete this.autoCompleteElement; + } + + if (isIntermediateSuggestion) + finalSelectionRange.setStart(completionTextNode, wordPrefixLength); + else + finalSelectionRange.setStart(completionTextNode, completionText.length); + + finalSelectionRange.setEnd(completionTextNode, completionText.length); + + var selection = window.getSelection(); + selection.removeAllRanges(); + selection.addRange(finalSelectionRange); + if (isIntermediateSuggestion) + this.dispatchEventToListeners(WebInspector.TextPrompt.Events.ItemApplied, { itemText: completionText }); + }, + + acceptSuggestion: function() + { + if (this._isAcceptingSuggestion) + return false; + + if (!this.autoCompleteElement || !this.autoCompleteElement.parentNode) + return false; + + var text = this.autoCompleteElement.textContent; + var textNode = document.createTextNode(text); + this.autoCompleteElement.parentNode.replaceChild(textNode, this.autoCompleteElement); + delete this.autoCompleteElement; + + var finalSelectionRange = document.createRange(); + finalSelectionRange.setStart(textNode, text.length); + finalSelectionRange.setEnd(textNode, text.length); + + var selection = window.getSelection(); + selection.removeAllRanges(); + selection.addRange(finalSelectionRange); + + this.hideSuggestBox(); + this.dispatchEventToListeners(WebInspector.TextPrompt.Events.ItemAccepted); + + return true; + }, + + hideSuggestBox: function() + { + if (this.isSuggestBoxVisible()) + this._suggestBox.hide(); + }, + + isSuggestBoxVisible: function() + { + return this._suggestBox && this._suggestBox.visible; + }, + + isCaretInsidePrompt: function() + { + return this._element.isInsertionCaretInside(); + }, + + isCaretAtEndOfPrompt: function() + { + var selection = window.getSelection(); + if (!selection.rangeCount || !selection.isCollapsed) + return false; + + var selectionRange = selection.getRangeAt(0); + var node = selectionRange.startContainer; + if (!node.isSelfOrDescendant(this._element)) + return false; + + if (node.nodeType === Node.TEXT_NODE && selectionRange.startOffset < node.nodeValue.length) + return false; + + var foundNextText = false; + while (node) { + if (node.nodeType === Node.TEXT_NODE && node.nodeValue.length) { + if (foundNextText && (!this.autoCompleteElement || !this.autoCompleteElement.isAncestor(node))) + return false; + foundNextText = true; + } + + node = node.traverseNextNode(this._element); + } + + return true; + }, + + isCaretOnFirstLine: function() + { + var selection = window.getSelection(); + var focusNode = selection.focusNode; + if (!focusNode || focusNode.nodeType !== Node.TEXT_NODE || focusNode.parentNode !== this._element) + return true; + + if (focusNode.textContent.substring(0, selection.focusOffset).indexOf("\n") !== -1) + return false; + focusNode = focusNode.previousSibling; + + while (focusNode) { + if (focusNode.nodeType !== Node.TEXT_NODE) + return true; + if (focusNode.textContent.indexOf("\n") !== -1) + return false; + focusNode = focusNode.previousSibling; + } + + return true; + }, + + isCaretOnLastLine: function() + { + var selection = window.getSelection(); + var focusNode = selection.focusNode; + if (!focusNode || focusNode.nodeType !== Node.TEXT_NODE || focusNode.parentNode !== this._element) + return true; + + if (focusNode.textContent.substring(selection.focusOffset).indexOf("\n") !== -1) + return false; + focusNode = focusNode.nextSibling; + + while (focusNode) { + if (focusNode.nodeType !== Node.TEXT_NODE) + return true; + if (focusNode.textContent.indexOf("\n") !== -1) + return false; + focusNode = focusNode.nextSibling; + } + + return true; + }, + + moveCaretToEndOfPrompt: function() + { + var selection = window.getSelection(); + var selectionRange = document.createRange(); + + var offset = this._element.childNodes.length; + selectionRange.setStart(this._element, offset); + selectionRange.setEnd(this._element, offset); + + selection.removeAllRanges(); + selection.addRange(selectionRange); + }, + + tabKeyPressed: function(event) + { + // Just consume the key. + return true; + }, + + enterKeyPressed: function(event) + { + if (this.isSuggestBoxVisible()) + return this._suggestBox.enterKeyPressed(event); + + return false; + }, + + upKeyPressed: function(event) + { + if (this.isSuggestBoxVisible()) + return this._suggestBox.upKeyPressed(event); + + return false; + }, + + downKeyPressed: function(event) + { + if (this.isSuggestBoxVisible()) + return this._suggestBox.downKeyPressed(event); + + return false; + }, + + pageUpKeyPressed: function(event) + { + if (this.isSuggestBoxVisible()) + return this._suggestBox.pageUpKeyPressed(event); + + return false; + }, + + pageDownKeyPressed: function(event) + { + if (this.isSuggestBoxVisible()) + return this._suggestBox.pageDownKeyPressed(event); + + return false; + }, +} + +WebInspector.TextPrompt.prototype.__proto__ = WebInspector.Object.prototype; + +/** + * @constructor + * @extends {WebInspector.TextPrompt} + * @param {function(Range, boolean, function(Array.=))} completions + * @param {string} stopCharacters + */ +WebInspector.TextPromptWithHistory = function(completions, stopCharacters) +{ + WebInspector.TextPrompt.call(this, completions, stopCharacters); + + /** + * @type {Array.} + */ + this._data = []; + + /** + * 1-based entry in the history stack. + * @type {number} + */ + this._historyOffset = 1; + + /** + * Whether to coalesce duplicate items in the history, default is true. + * @type {boolean} + */ + this._coalesceHistoryDupes = true; +} + +WebInspector.TextPromptWithHistory.prototype = { + get historyData() + { + // FIXME: do we need to copy this? + return this._data; + }, + + setCoalesceHistoryDupes: function(x) + { + this._coalesceHistoryDupes = x; + }, + + /** + * @param {Array.} data + */ + setHistoryData: function(data) + { + this._data = [].concat(data); + this._historyOffset = 1; + }, + + /** + * Pushes a committed text into the history. + * @param {string} text + */ + pushHistoryItem: function(text) + { + if (this._uncommittedIsTop) { + this._data.pop(); + delete this._uncommittedIsTop; + } + + this._historyOffset = 1; + if (this._coalesceHistoryDupes && text === this._currentHistoryItem()) + return; + this._data.push(text); + }, + + /** + * Pushes the current (uncommitted) text into the history. + */ + _pushCurrentText: function() + { + if (this._uncommittedIsTop) + this._data.pop(); // Throw away obsolete uncommitted text. + this._uncommittedIsTop = true; + this.clearAutoComplete(true); + this._data.push(this.text); + }, + + /** + * @return {string|undefined} + */ + _previous: function() + { + if (this._historyOffset > this._data.length) + return undefined; + if (this._historyOffset === 1) + this._pushCurrentText(); + ++this._historyOffset; + return this._currentHistoryItem(); + }, + + /** + * @return {string|undefined} + */ + _next: function() + { + if (this._historyOffset === 1) + return undefined; + --this._historyOffset; + return this._currentHistoryItem(); + }, + + _currentHistoryItem: function() + { + return this._data[this._data.length - this._historyOffset]; + }, + + /** + * @override + */ + defaultKeyHandler: function(event, force) + { + var newText; + var isPrevious; + + switch (event.keyIdentifier) { + case "Up": + if (!this.isCaretOnFirstLine()) + break; + newText = this._previous(); + isPrevious = true; + break; + case "Down": + if (!this.isCaretOnLastLine()) + break; + newText = this._next(); + break; + case "U+0050": // Ctrl+P = Previous + if (WebInspector.isMac() && event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey) { + newText = this._previous(); + isPrevious = true; + } + break; + case "U+004E": // Ctrl+N = Next + if (WebInspector.isMac() && event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey) + newText = this._next(); + break; + } + + if (newText !== undefined) { + event.stopPropagation(); + event.preventDefault(); + this.text = newText; + + if (isPrevious) { + var firstNewlineIndex = this.text.indexOf("\n"); + if (firstNewlineIndex === -1) + this.moveCaretToEndOfPrompt(); + else { + var selection = window.getSelection(); + var selectionRange = document.createRange(); + + selectionRange.setStart(this._element.firstChild, firstNewlineIndex); + selectionRange.setEnd(this._element.firstChild, firstNewlineIndex); + + selection.removeAllRanges(); + selection.addRange(selectionRange); + } + } + + return true; + } + + return WebInspector.TextPrompt.prototype.defaultKeyHandler.apply(this, arguments); + } +} + +WebInspector.TextPromptWithHistory.prototype.__proto__ = WebInspector.TextPrompt.prototype; + +/** + * @constructor + */ +WebInspector.TextPrompt.SuggestBox = function(textPrompt, inputElement, className) +{ + this._textPrompt = textPrompt; + this._inputElement = inputElement; + this._selectedElement = null; + this._boundOnScroll = this._onscrollresize.bind(this, true); + this._boundOnResize = this._onscrollresize.bind(this, false); + window.addEventListener("scroll", this._boundOnScroll, true); + window.addEventListener("resize", this._boundOnResize, true); + + var bodyElement = inputElement.ownerDocument.body; + this._element = bodyElement.createChild("div", "suggest-box " + (className || "")); + this._element.addEventListener("mousedown", this._onboxmousedown.bind(this), true); + this.containerElement = this._element.createChild("div", "container"); + this.contentElement = this.containerElement.createChild("div", "content"); +} + +WebInspector.TextPrompt.SuggestBox.prototype = { + get visible() + { + return this._element.hasStyleClass("visible"); + }, + + get hasSelection() + { + return !!this._selectedElement; + }, + + _onscrollresize: function(isScroll, event) + { + if (isScroll && this._element.isAncestor(event.target) || !this.visible) + return; + this._updateBoxPositionWithExistingAnchor(); + }, + + _updateBoxPositionWithExistingAnchor: function() + { + this._updateBoxPosition(this._anchorBox); + }, + + /** + * @param {AnchorBox} anchorBox + */ + _updateBoxPosition: function(anchorBox) + { + // Measure the content element box. + this.contentElement.style.display = "inline-block"; + document.body.appendChild(this.contentElement); + this.contentElement.positionAt(0, 0); + var contentWidth = this.contentElement.offsetWidth; + var contentHeight = this.contentElement.offsetHeight; + this.contentElement.style.display = "block"; + this.containerElement.appendChild(this.contentElement); + + // Lay out the suggest-box relative to the anchorBox. + this._anchorBox = anchorBox; + const spacer = 6; + + const suggestBoxPaddingX = 21; + var maxWidth = document.body.offsetWidth - anchorBox.x - spacer; + var width = Math.min(contentWidth, maxWidth - suggestBoxPaddingX) + suggestBoxPaddingX; + var paddedWidth = contentWidth + suggestBoxPaddingX; + var boxX = anchorBox.x; + if (width < paddedWidth) { + // Shift the suggest box to the left to accommodate the content without trimming to the BODY edge. + maxWidth = document.body.offsetWidth - spacer; + width = Math.min(contentWidth, maxWidth - suggestBoxPaddingX) + suggestBoxPaddingX; + boxX = document.body.offsetWidth - width; + } + + const suggestBoxPaddingY = 2; + var boxY; + var aboveHeight = anchorBox.y; + var underHeight = document.body.offsetHeight - anchorBox.y - anchorBox.height; + var maxHeight = Math.max(underHeight, aboveHeight) - spacer; + var height = Math.min(contentHeight, maxHeight - suggestBoxPaddingY) + suggestBoxPaddingY; + if (underHeight >= aboveHeight) { + // Locate the suggest box under the anchorBox. + boxY = anchorBox.y + anchorBox.height; + this._element.removeStyleClass("above-anchor"); + this._element.addStyleClass("under-anchor"); + } else { + // Locate the suggest box above the anchorBox. + boxY = anchorBox.y - height; + this._element.removeStyleClass("under-anchor"); + this._element.addStyleClass("above-anchor"); + } + + this._element.positionAt(boxX, boxY); + this._element.style.width = width + "px"; + this._element.style.height = height + "px"; + }, + + _onboxmousedown: function(event) + { + event.preventDefault(); + }, + + hide: function() + { + if (!this.visible) + return; + + this._element.removeStyleClass("visible"); + delete this._selectedElement; + }, + + removeFromElement: function() + { + window.removeEventListener("scroll", this._boundOnScroll, true); + window.removeEventListener("resize", this._boundOnResize, true); + this._element.parentElement.removeChild(this._element); + }, + + /** + * @param {string=} text + * @param {boolean=} isIntermediateSuggestion + */ + _applySuggestion: function(text, isIntermediateSuggestion) + { + if (!this.visible || !(text || this._selectedElement)) + return false; + + var suggestion = text || this._selectedElement.textContent; + if (!suggestion) + return false; + + this._textPrompt.applySuggestion(suggestion, isIntermediateSuggestion); + return true; + }, + + /** + * @param {string=} text + */ + acceptSuggestion: function(text) + { + var result = this._applySuggestion(text, false); + this.hide(); + if (!result) + return false; + + this._textPrompt.acceptSuggestion(); + + return true; + }, + + _onNextItem: function(event, isPageScroll) + { + var children = this.contentElement.childNodes; + if (!children.length) + return false; + + if (!this._selectedElement) + this._selectedElement = this.contentElement.firstChild; + else { + if (!isPageScroll) + this._selectedElement = this._selectedElement.nextSibling || this.contentElement.firstChild; + else { + var candidate = this._selectedElement; + + for (var itemsLeft = this._rowCountPerViewport; itemsLeft; --itemsLeft) { + if (candidate.nextSibling) + candidate = candidate.nextSibling; + else + break; + } + + this._selectedElement = candidate; + } + } + this._updateSelection(); + this._applySuggestion(undefined, true); + return true; + }, + + _onPreviousItem: function(event, isPageScroll) + { + var children = this.contentElement.childNodes; + if (!children.length) + return false; + + if (!this._selectedElement) + this._selectedElement = this.contentElement.lastChild; + else { + if (!isPageScroll) + this._selectedElement = this._selectedElement.previousSibling || this.contentElement.lastChild; + else { + var candidate = this._selectedElement; + + for (var itemsLeft = this._rowCountPerViewport; itemsLeft; --itemsLeft) { + if (candidate.previousSibling) + candidate = candidate.previousSibling; + else + break; + } + + this._selectedElement = candidate; + } + } + this._updateSelection(); + this._applySuggestion(undefined, true); + return true; + }, + + /** + * @param {AnchorBox} anchorBox + * @param {Array.=} completions + * @param {boolean=} canShowForSingleItem + */ + updateSuggestions: function(anchorBox, completions, canShowForSingleItem) + { + if (this._suggestTimeout) { + clearTimeout(this._suggestTimeout); + delete this._suggestTimeout; + } + this._completionsReady(anchorBox, completions, canShowForSingleItem); + }, + + _onItemMouseDown: function(text, event) + { + this.acceptSuggestion(text); + event.stopPropagation(); + event.preventDefault(); + }, + + _createItemElement: function(prefix, text) + { + var element = document.createElement("div"); + element.className = "suggest-box-content-item source-code"; + element.tabIndex = -1; + if (prefix && prefix.length && !text.indexOf(prefix)) { + var prefixElement = element.createChild("span", "prefix"); + prefixElement.textContent = prefix; + var suffixElement = element.createChild("span", "suffix"); + suffixElement.textContent = text.substring(prefix.length); + } else { + var suffixElement = element.createChild("span", "suffix"); + suffixElement.textContent = text; + } + element.addEventListener("mousedown", this._onItemMouseDown.bind(this, text), false); + return element; + }, + + /** + * @param {boolean=} canShowForSingleItem + */ + _updateItems: function(items, canShowForSingleItem) + { + this.contentElement.removeChildren(); + + var userEnteredText = this._textPrompt._userEnteredText; + for (var i = 0; i < items.length; ++i) { + var item = items[i]; + var currentItemElement = this._createItemElement(userEnteredText, item); + this.contentElement.appendChild(currentItemElement); + } + + this._selectedElement = canShowForSingleItem ? this.contentElement.firstChild : null; + this._updateSelection(); + }, + + _updateSelection: function() + { + // FIXME: might want some optimization if becomes a bottleneck. + for (var child = this.contentElement.firstChild; child; child = child.nextSibling) { + if (child !== this._selectedElement) + child.removeStyleClass("selected"); + } + if (this._selectedElement) { + this._selectedElement.addStyleClass("selected"); + this._selectedElement.scrollIntoViewIfNeeded(false); + } + }, + + /** + * @param {Array.=} completions + * @param {boolean=} canShowForSingleItem + */ + _canShowBox: function(completions, canShowForSingleItem) + { + if (!completions || !completions.length) + return false; + + if (completions.length > 1) + return true; + + // Do not show a single suggestion if it is the same as user-entered prefix, even if allowed to show single-item suggest boxes. + return canShowForSingleItem && completions[0] !== this._textPrompt._userEnteredText; + }, + + _rememberRowCountPerViewport: function() + { + if (!this.contentElement.firstChild) + return; + + this._rowCountPerViewport = Math.floor(this.containerElement.offsetHeight / this.contentElement.firstChild.offsetHeight); + }, + + /** + * @param {AnchorBox} anchorBox + * @param {Array.=} completions + * @param {boolean=} canShowForSingleItem + */ + _completionsReady: function(anchorBox, completions, canShowForSingleItem) + { + if (this._canShowBox(completions, canShowForSingleItem)) { + this._updateItems(completions, canShowForSingleItem); + this._updateBoxPosition(anchorBox); + this._element.addStyleClass("visible"); + this._rememberRowCountPerViewport(); + } else + this.hide(); + }, + + upKeyPressed: function(event) + { + return this._onPreviousItem(event, false); + }, + + downKeyPressed: function(event) + { + return this._onNextItem(event, false); + }, + + pageUpKeyPressed: function(event) + { + return this._onPreviousItem(event, true); + }, + + pageDownKeyPressed: function(event) + { + return this._onNextItem(event, true); + }, + + enterKeyPressed: function(event) + { + var hasSelectedItem = !!this._selectedElement; + this.acceptSuggestion(); + + // Report the event as non-handled if there is no selected item, + // to commit the input or handle it otherwise. + return hasSelectedItem; + }, + + tabKeyPressed: function(event) + { + return this.enterKeyPressed(event); + } +} +/* Popover.js */ + +/* + * Copyright (C) 2009 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @param {WebInspector.PopoverHelper=} popoverHelper + */ +WebInspector.Popover = function(popoverHelper) +{ + this.element = document.createElement("div"); + this.element.className = "popover custom-popup-vertical-scroll custom-popup-horizontal-scroll"; + + this._popupArrowElement = document.createElement("div"); + this._popupArrowElement.className = "arrow"; + this.element.appendChild(this._popupArrowElement); + + this._contentDiv = document.createElement("div"); + this._contentDiv.className = "content"; + this._visible = false; + this._popoverHelper = popoverHelper; +} + +WebInspector.Popover.prototype = { + show: function(contentElement, anchor, preferredWidth, preferredHeight) + { + if (this._disposed) + return; + this.contentElement = contentElement; + + // This should not happen, but we hide previous popup to be on the safe side. + if (WebInspector.Popover._popoverElement) + document.body.removeChild(WebInspector.Popover._popoverElement); + WebInspector.Popover._popoverElement = this.element; + + // Temporarily attach in order to measure preferred dimensions. + this.contentElement.positionAt(0, 0); + document.body.appendChild(this.contentElement); + preferredWidth = preferredWidth || this.contentElement.offsetWidth; + preferredHeight = preferredHeight || this.contentElement.offsetHeight; + + this._contentDiv.appendChild(this.contentElement); + this.element.appendChild(this._contentDiv); + document.body.appendChild(this.element); + this._positionElement(anchor, preferredWidth, preferredHeight); + this._visible = true; + if (this._popoverHelper) + contentElement.addEventListener("mousemove", this._popoverHelper._killHidePopoverTimer.bind(this._popoverHelper), true); + }, + + hide: function() + { + if (WebInspector.Popover._popoverElement) { + delete WebInspector.Popover._popoverElement; + document.body.removeChild(this.element); + } + this._visible = false; + }, + + get visible() + { + return this._visible; + }, + + get disposed() + { + return this._disposed; + }, + + dispose: function() + { + if (this.visible) + this.hide(); + this._disposed = true; + }, + + _positionElement: function(anchorElement, preferredWidth, preferredHeight) + { + const borderWidth = 25; + const scrollerWidth = 11; + const arrowHeight = 15; + const arrowOffset = 10; + const borderRadius = 10; + + // Skinny tooltips are not pretty, their arrow location is not nice. + preferredWidth = Math.max(preferredWidth, 50); + const totalWidth = window.innerWidth; + const totalHeight = window.innerHeight; + + var anchorBox = anchorElement.boxInWindow(window); + var newElementPosition = { x: 0, y: 0, width: preferredWidth + scrollerWidth, height: preferredHeight }; + + var verticalAlignment; + var roomAbove = anchorBox.y; + var roomBelow = totalHeight - anchorBox.y - anchorBox.height; + + if (roomAbove > roomBelow) { + // Positioning above the anchor. + if (anchorBox.y > newElementPosition.height + arrowHeight + borderRadius) + newElementPosition.y = anchorBox.y - newElementPosition.height - arrowHeight; + else { + newElementPosition.y = borderRadius * 2; + newElementPosition.height = anchorBox.y - borderRadius * 2 - arrowHeight; + } + verticalAlignment = "bottom"; + } else { + // Positioning below the anchor. + newElementPosition.y = anchorBox.y + anchorBox.height + arrowHeight; + if (newElementPosition.y + newElementPosition.height + arrowHeight - borderWidth >= totalHeight) + newElementPosition.height = totalHeight - anchorBox.y - anchorBox.height - borderRadius * 2 - arrowHeight; + // Align arrow. + verticalAlignment = "top"; + } + + var horizontalAlignment; + if (anchorBox.x + newElementPosition.width < totalWidth) { + newElementPosition.x = Math.max(borderRadius, anchorBox.x - borderRadius - arrowOffset); + horizontalAlignment = "left"; + } else if (newElementPosition.width + borderRadius * 2 < totalWidth) { + newElementPosition.x = totalWidth - newElementPosition.width - borderRadius; + horizontalAlignment = "right"; + // Position arrow accurately. + var arrowRightPosition = Math.max(0, totalWidth - anchorBox.x - anchorBox.width - borderRadius - arrowOffset); + arrowRightPosition += anchorBox.width / 2; + this._popupArrowElement.style.right = arrowRightPosition + "px"; + } else { + newElementPosition.x = borderRadius; + newElementPosition.width = totalWidth - borderRadius * 2; + newElementPosition.height += scrollerWidth; + horizontalAlignment = "left"; + if (verticalAlignment === "bottom") + newElementPosition.y -= scrollerWidth; + // Position arrow accurately. + this._popupArrowElement.style.left = Math.max(0, anchorBox.x - borderRadius * 2 - arrowOffset) + "px"; + this._popupArrowElement.style.left += anchorBox.width / 2; + } + + this.element.className = "popover custom-popup-vertical-scroll custom-popup-horizontal-scroll " + verticalAlignment + "-" + horizontalAlignment + "-arrow"; + this.element.positionAt(newElementPosition.x - borderWidth, newElementPosition.y - borderWidth); + this.element.style.width = newElementPosition.width + borderWidth * 2 + "px"; + this.element.style.height = newElementPosition.height + borderWidth * 2 + "px"; + } +} + +/** + * @constructor + * @param {function()=} onHide + * @param {boolean=} disableOnClick + */ +WebInspector.PopoverHelper = function(panelElement, getAnchor, showPopover, onHide, disableOnClick) +{ + this._panelElement = panelElement; + this._getAnchor = getAnchor; + this._showPopover = showPopover; + this._onHide = onHide; + this._disableOnClick = !!disableOnClick; + panelElement.addEventListener("mousedown", this._mouseDown.bind(this), false); + panelElement.addEventListener("mousemove", this._mouseMove.bind(this), false); + this.setTimeout(1000); +} + +WebInspector.PopoverHelper.prototype = { + setTimeout: function(timeout) + { + this._timeout = timeout; + }, + + _mouseDown: function(event) + { + if (this._disableOnClick) + this.hidePopover(); + else { + this._killHidePopoverTimer(); + this._handleMouseAction(event, true); + } + }, + + _mouseMove: function(event) + { + // Pretend that nothing has happened. + if (event.target.isSelfOrDescendant(this._hoverElement)) + return; + + // User has 500ms (this._timeout / 2) to reach the popup. + if (this._popover && !this._hidePopoverTimer) { + var self = this; + function doHide() + { + self._hidePopover(); + delete self._hidePopoverTimer; + } + this._hidePopoverTimer = setTimeout(doHide, this._timeout / 2); + } + + this._handleMouseAction(event, false); + }, + + _handleMouseAction: function(event, isMouseDown) + { + this._resetHoverTimer(); + if (event.which && this._disableOnClick) + return; + this._hoverElement = this._getAnchor(event.target); + if (!this._hoverElement) + return; + const toolTipDelay = isMouseDown ? 0 : (this._popup ? this._timeout * 0.6 : this._timeout); + this._hoverTimer = setTimeout(this._mouseHover.bind(this, this._hoverElement), toolTipDelay); + }, + + _resetHoverTimer: function() + { + if (this._hoverTimer) { + clearTimeout(this._hoverTimer); + delete this._hoverTimer; + } + }, + + hidePopover: function() + { + this._resetHoverTimer(); + this._hidePopover(); + }, + + _hidePopover: function() + { + if (!this._popover) + return; + + if (this._onHide) + this._onHide(); + + this._popover.dispose(); + delete this._popover; + }, + + _mouseHover: function(element) + { + delete this._hoverTimer; + + this._hidePopover(); + this._popover = new WebInspector.Popover(this); + this._showPopover(element, this._popover); + }, + + _killHidePopoverTimer: function() + { + if (this._hidePopoverTimer) { + clearTimeout(this._hidePopoverTimer); + delete this._hidePopoverTimer; + + // We know that we reached the popup, but we might have moved over other elements. + // Discard pending command. + this._resetHoverTimer(); + } + } +} +/* Placard.js */ + +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @param {string} title + * @param {string} subtitle + */ +WebInspector.Placard = function(title, subtitle) +{ + this.element = document.createElement("div"); + this.element.className = "placard"; + this.element.placard = this; + + this.titleElement = document.createElement("div"); + this.titleElement.className = "title"; + + this.subtitleElement = document.createElement("div"); + this.subtitleElement.className = "subtitle"; + + this.element.appendChild(this.subtitleElement); + this.element.appendChild(this.titleElement); + + this.title = title; + this.subtitle = subtitle; + this.selected = false; +} + +WebInspector.Placard.prototype = { + /** @return {string} */ + get title() + { + return this._title; + }, + + set title(x) + { + if (this._title === x) + return; + this._title = x; + this.titleElement.textContent = x; + }, + + /** @return {string} */ + get subtitle() + { + return this._subtitle; + }, + + set subtitle(x) + { + if (this._subtitle === x) + return; + this._subtitle = x; + this.subtitleElement.textContent = x; + }, + + /** @return {boolean} */ + get selected() + { + return this._selected; + }, + + set selected(x) + { + if (x) + this.select(); + else + this.deselect(); + }, + + select: function() + { + if (this._selected) + return; + this._selected = true; + this.element.addStyleClass("selected"); + }, + + deselect: function() + { + if (!this._selected) + return; + this._selected = false; + this.element.removeStyleClass("selected"); + }, + + toggleSelected: function() + { + this.selected = !this.selected; + }, + + discard: function() + { + } +} +/* View.js */ + +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * Copyright (C) 2011 Google Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.Object} + */ +WebInspector.View = function() +{ + this.element = document.createElement("div"); + this.element.__view = this; + this._visible = true; + this._isRoot = false; + this._isShowing = false; + this._children = []; + this._hideOnDetach = false; + this._cssFiles = []; +} + +WebInspector.View._cssFileToVisibleViewCount = {}; +WebInspector.View._cssFileToStyleElement = {}; + +WebInspector.View.prototype = { + markAsRoot: function() + { + this._isRoot = true; + }, + + isShowing: function() + { + return this._isShowing; + }, + + setHideOnDetach: function() + { + this._hideOnDetach = true; + }, + + _parentIsShowing: function() + { + return this._isRoot || (this._parentView && this._parentView.isShowing()); + }, + + _callOnVisibleChildren: function(method) + { + for (var i = 0; i < this._children.length; ++i) + if (this._children[i]._visible) + method.call(this._children[i]); + }, + + _processWillShow: function() + { + this._loadCSSIfNeeded(); + this._callOnVisibleChildren(this._processWillShow); + }, + + _processWasShown: function() + { + this._isShowing = true; + this.restoreScrollPositions(); + + this.wasShown(); + this.onResize(); + + this._callOnVisibleChildren(this._processWasShown); + }, + + _processWillHide: function() + { + this.storeScrollPositions(); + + this._callOnVisibleChildren(this._processWillHide); + + this.willHide(); + this._isShowing = false; + }, + + _processWasHidden: function() + { + this._disableCSSIfNeeded(); + this._callOnVisibleChildren(this._processWasHidden); + }, + + _processOnResize: function() + { + if (!this.isShowing()) + return; + + this.onResize(); + this._callOnVisibleChildren(this._processOnResize); + }, + + wasShown: function() + { + }, + + willHide: function() + { + }, + + onResize: function() + { + }, + + /** + * @param {Element} parentElement + * @param {Element=} insertBefore + */ + show: function(parentElement, insertBefore) + { + WebInspector.View._assert(parentElement, "Attempt to attach view with no parent element"); + + // Update view hierarchy + if (this.element.parentElement !== parentElement) { + var currentParent = parentElement; + while (currentParent && !currentParent.__view) + currentParent = currentParent.parentElement; + + if (currentParent) { + this._parentView = currentParent.__view; + this._parentView._children.push(this); + this._isRoot = false; + } else + WebInspector.View._assert(this._isRoot, "Attempt to attach view to orphan node"); + } else if (this._visible) + return; + + this._visible = true; + if (this._parentIsShowing()) + this._processWillShow(); + + this.element.addStyleClass("visible"); + + // Reparent + if (this.element.parentElement !== parentElement) { + WebInspector.View._incrementViewCounter(parentElement, this.element); + if (insertBefore) + WebInspector.View._originalInsertBefore.call(parentElement, this.element, insertBefore); + else + WebInspector.View._originalAppendChild.call(parentElement, this.element); + } + + if (this._parentIsShowing()) + this._processWasShown(); + }, + + /** + * @param {boolean=} overrideHideOnDetach + */ + detach: function(overrideHideOnDetach) + { + var parentElement = this.element.parentElement; + if (!parentElement) + return; + + if (this._parentIsShowing()) + this._processWillHide(); + + if (this._hideOnDetach && !overrideHideOnDetach) { + this.element.removeStyleClass("visible"); + this._visible = false; + if (this._parentIsShowing()) + this._processWasHidden(); + return; + } + + // Force legal removal + WebInspector.View._decrementViewCounter(parentElement, this.element); + WebInspector.View._originalRemoveChild.call(parentElement, this.element); + + this._visible = false; + if (this._parentIsShowing()) + this._processWasHidden(); + + // Update view hierarchy + if (this._parentView) { + var childIndex = this._parentView._children.indexOf(this); + WebInspector.View._assert(childIndex >= 0, "Attempt to remove non-child view"); + this._parentView._children.splice(childIndex, 1); + this._parentView = null; + } else + WebInspector.View._assert(this._isRoot, "Removing non-root view from DOM"); + }, + + detachChildViews: function() + { + var children = this._children.slice(); + for (var i = 0; i < children.length; ++i) + children[i].detach(); + }, + + elementsToRestoreScrollPositionsFor: function() + { + return [this.element]; + }, + + storeScrollPositions: function() + { + var elements = this.elementsToRestoreScrollPositionsFor(); + for (var i = 0; i < elements.length; ++i) { + var container = elements[i]; + container._scrollTop = container.scrollTop; + container._scrollLeft = container.scrollLeft; + } + }, + + restoreScrollPositions: function() + { + var elements = this.elementsToRestoreScrollPositionsFor(); + for (var i = 0; i < elements.length; ++i) { + var container = elements[i]; + if (container._scrollTop) + container.scrollTop = container._scrollTop; + if (container._scrollLeft) + container.scrollLeft = container._scrollLeft; + } + }, + + canHighlightLine: function() + { + return false; + }, + + highlightLine: function(line) + { + }, + + doResize: function() + { + this._processOnResize(); + }, + + registerRequiredCSS: function(cssFile) + { + this._cssFiles.push(cssFile); + }, + + _loadCSSIfNeeded: function() + { + for (var i = 0; i < this._cssFiles.length; ++i) { + var cssFile = this._cssFiles[i]; + + var viewsWithCSSFile = WebInspector.View._cssFileToVisibleViewCount[cssFile]; + WebInspector.View._cssFileToVisibleViewCount[cssFile] = (viewsWithCSSFile || 0) + 1; + if (!viewsWithCSSFile) + this._doLoadCSS(cssFile); + } + }, + + _doLoadCSS: function(cssFile) + { + var styleElement = WebInspector.View._cssFileToStyleElement[cssFile]; + if (styleElement) { + styleElement.disabled = false; + return; + } + + var xhr = new XMLHttpRequest(); + xhr.open("GET", cssFile, false); + xhr.send(null); + + styleElement = document.createElement("style"); + styleElement.type = "text/css"; + styleElement.textContent = xhr.responseText; + document.head.insertBefore(styleElement, document.head.firstChild); + + WebInspector.View._cssFileToStyleElement[cssFile] = styleElement; + }, + + _disableCSSIfNeeded: function() + { + for (var i = 0; i < this._cssFiles.length; ++i) { + var cssFile = this._cssFiles[i]; + + var viewsWithCSSFile = WebInspector.View._cssFileToVisibleViewCount[cssFile]; + viewsWithCSSFile--; + WebInspector.View._cssFileToVisibleViewCount[cssFile] = viewsWithCSSFile; + + if (!viewsWithCSSFile) + this._doUnloadCSS(cssFile); + } + }, + + _doUnloadCSS: function(cssFile) + { + var styleElement = WebInspector.View._cssFileToStyleElement[cssFile]; + styleElement.disabled = true; + }, + + printViewHierarchy: function() + { + var lines = []; + this._collectViewHierarchy("", lines); + console.log(lines.join("\n")); + }, + + _collectViewHierarchy: function(prefix, lines) + { + lines.push(prefix + "[" + this.element.className + "]" + (this._children.length ? " {" : "")); + + for (var i = 0; i < this._children.length; ++i) + this._children[i]._collectViewHierarchy(prefix + " ", lines); + + if (this._children.length) + lines.push(prefix + "}"); + } +} + +WebInspector.View.prototype.__proto__ = WebInspector.Object.prototype; + +WebInspector.View._originalAppendChild = Element.prototype.appendChild; +WebInspector.View._originalInsertBefore = Element.prototype.insertBefore; +WebInspector.View._originalRemoveChild = Element.prototype.removeChild; +WebInspector.View._originalRemoveChildren = Element.prototype.removeChildren; + +WebInspector.View._incrementViewCounter = function(parentElement, childElement) +{ + var count = (childElement.__viewCounter || 0) + (childElement.__view ? 1 : 0); + if (!count) + return; + + while (parentElement) { + parentElement.__viewCounter = (parentElement.__viewCounter || 0) + count; + parentElement = parentElement.parentElement; + } +} + +WebInspector.View._decrementViewCounter = function(parentElement, childElement) +{ + var count = (childElement.__viewCounter || 0) + (childElement.__view ? 1 : 0); + if (!count) + return; + + while (parentElement) { + parentElement.__viewCounter -= count; + parentElement = parentElement.parentElement; + } +} + +WebInspector.View._assert = function(condition, message) +{ + if (!condition) { + console.trace(); + throw new Error(message); + } +} + +Element.prototype.appendChild = function(child) +{ + WebInspector.View._assert(!child.__view, "Attempt to add view via regular DOM operation."); + return WebInspector.View._originalAppendChild.call(this, child); +} + +Element.prototype.insertBefore = function(child, anchor) +{ + WebInspector.View._assert(!child.__view, "Attempt to add view via regular DOM operation."); + return WebInspector.View._originalInsertBefore.call(this, child, anchor); +} + + +Element.prototype.removeChild = function(child) +{ + WebInspector.View._assert(!child.__viewCounter && !child.__view, "Attempt to remove element containing view via regular DOM operation"); + return WebInspector.View._originalRemoveChild.call(this, child); +} + +Element.prototype.removeChildren = function() +{ + WebInspector.View._assert(!this.__viewCounter, "Attempt to remove element containing view via regular DOM operation"); + WebInspector.View._originalRemoveChildren.call(this); +} +/* TabbedPane.js */ + +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @extends {WebInspector.View} + * @constructor + */ +WebInspector.TabbedPane = function() +{ + WebInspector.View.call(this); + this.registerRequiredCSS("tabbedPane.css"); + this.element.addStyleClass("tabbed-pane"); + this._headerElement = this.element.createChild("div", "tabbed-pane-header"); + this._headerContentsElement = this._headerElement.createChild("div", "tabbed-pane-header-contents"); + this._tabsElement = this._headerContentsElement.createChild("div", "tabbed-pane-header-tabs"); + this._contentElement = this.element.createChild("div", "tabbed-pane-content"); + this._tabs = []; + this._tabsHistory = []; + this._tabsById = {}; + + this._dropDownButton = this._createDropDownButton(); +} + +WebInspector.TabbedPane.EventTypes = { + TabSelected: "TabSelected", + TabClosed: "TabClosed" +} + +WebInspector.TabbedPane.prototype = { + /** + * @type {WebInspector.View} + */ + get visibleView() + { + return this._currentTab ? this._currentTab.view : null; + }, + + /** + * @type {string} + */ + get selectedTabId() + { + return this._currentTab ? this._currentTab.id : null; + }, + + /** + * @type {boolean} shrinkableTabs + */ + set shrinkableTabs(shrinkableTabs) + { + this._shrinkableTabs = shrinkableTabs; + }, + + /** + * @type {boolean} shrinkableTabs + */ + set closeableTabs(closeableTabs) + { + this._closeableTabs = closeableTabs; + }, + + /** + * @param {string} id + * @param {string} tabTitle + * @param {WebInspector.View} view + * @param {string=} tabTooltip + */ + appendTab: function(id, tabTitle, view, tabTooltip) + { + var tab = new WebInspector.TabbedPaneTab(this, this._tabsElement, id, tabTitle, this._closeableTabs, view, tabTooltip); + + this._tabs.push(tab); + this._tabsById[id] = tab; + this._tabsHistory.push(tab); + this._updateTabElements(); + }, + + /** + * @param {string} id + * @param {boolean=} userGesture + */ + closeTab: function(id, userGesture) + { + if (this._currentTab && this._currentTab.id === id) + this._hideCurrentTab(); + + var tab = this._tabsById[id]; + delete this._tabsById[id]; + + this._tabsHistory.splice(this._tabsHistory.indexOf(tab), 1); + this._tabs.splice(this._tabs.indexOf(tab), 1); + if (tab.shown) + this._hideTabElement(tab); + + if (this._tabsHistory.length) + this.selectTab(this._tabsHistory[0].id); + else + this._updateTabElements(); + + var eventData = { tabId: id, view: tab.view, isUserGesture: userGesture }; + this.dispatchEventToListeners(WebInspector.TabbedPane.EventTypes.TabClosed, eventData); + return true; + }, + + closeAllTabs: function() + { + var tabs = this._tabs.slice(); + for (var i = 0; i < tabs.length; ++i) + this.closeTab(tabs[i].id, false); + }, + + /** + * @param {string} id + * @param {boolean=} userGesture + */ + selectTab: function(id, userGesture) + { + var tab = this._tabsById[id]; + if (!tab) + return; + if (this._currentTab && this._currentTab.id === id) + return; + + this._hideCurrentTab(); + this._showTab(tab); + this._currentTab = tab; + + this._tabsHistory.splice(this._tabsHistory.indexOf(tab), 1); + this._tabsHistory.splice(0, 0, tab); + + this._updateTabElements(); + + var eventData = { tabId: id, view: tab.view, isUserGesture: userGesture }; + this.dispatchEventToListeners(WebInspector.TabbedPane.EventTypes.TabSelected, eventData); + return true; + }, + + /** + * @param {string} id + * @param {string} tabTitle + */ + changeTabTitle: function(id, tabTitle) + { + var tab = this._tabsById[id]; + tab.title = tabTitle; + this._updateTabElements(); + }, + + /** + * @param {string} id + * @param {WebInspector.View} view + */ + changeTabView: function(id, view) + { + var tab = this._tabsById[id]; + if (this._currentTab && this._currentTab.id === tab.id) { + this._hideTab(tab); + tab.view = view; + this._showTab(tab); + } else + tab.view = view; + }, + + /** + * @param {string} id + * @param {string=} tabTooltip + */ + changeTabTooltip: function(id, tabTooltip) + { + var tab = this._tabsById[id]; + tab.tooltip = tabTooltip; + }, + + onResize: function() + { + this._updateTabElements(); + }, + + _updateTabElements: function() + { + if (!this.isShowing()) + return; + + if (!this._tabs.length) + this._contentElement.addStyleClass("has-no-tabs"); + else + this._contentElement.removeStyleClass("has-no-tabs"); + + if (!this._measuredDropDownButtonWidth) + this._measureDropDownButton(); + + if (this._shrinkableTabs) + this._updateWidths(); + + this._updateTabsDropDown(); + }, + + /** + * @param {number} index + * @param {WebInspector.TabbedPaneTab} tab + */ + _showTabElement: function(index, tab) + { + if (index >= this._tabsElement.children.length) + this._tabsElement.appendChild(tab.tabElement); + else + this._tabsElement.insertBefore(tab.tabElement, this._tabsElement.children[index]); + tab.shown = true; + }, + + /** + * @param {WebInspector.TabbedPaneTab} tab + */ + _hideTabElement: function(tab) + { + this._tabsElement.removeChild(tab.tabElement); + tab.shown = false; + }, + + _createDropDownButton: function() + { + var dropDownContainer = document.createElement("div"); + dropDownContainer.addStyleClass("tabbed-pane-header-tabs-drop-down-container"); + var dropDownButton = dropDownContainer.createChild("div", "tabbed-pane-header-tabs-drop-down"); + dropDownButton.appendChild(document.createTextNode("\u00bb")); + this._tabsSelect = dropDownButton.createChild("select", "tabbed-pane-header-tabs-drop-down-select"); + this._tabsSelect.addEventListener("change", this._tabsSelectChanged.bind(this), false); + return dropDownContainer; + }, + + _updateTabsDropDown: function() + { + var tabsToShowIndexes = this._tabsToShowIndexes(this._tabs, this._tabsHistory, this._headerContentsElement.offsetWidth, this._measuredDropDownButtonWidth); + + for (var i = 0; i < this._tabs.length; ++i) { + if (this._tabs[i].shown && tabsToShowIndexes.indexOf(i) === -1) + this._hideTabElement(this._tabs[i]); + } + for (var i = 0; i < tabsToShowIndexes.length; ++i) { + var tab = this._tabs[tabsToShowIndexes[i]]; + if (!tab.shown) + this._showTabElement(i, tab); + } + + this._populateDropDownFromIndex(); + }, + + _populateDropDownFromIndex: function() + { + if (this._dropDownButton.parentElement) + this._headerContentsElement.removeChild(this._dropDownButton); + + this._tabsSelect.removeChildren(); + for (var i = 0; i < this._tabs.length; ++i) { + if (this._tabs[i].shown) + continue; + + var option = new Option(this._tabs[i].title); + option.tab = this._tabs[i]; + this._tabsSelect.appendChild(option); + } + if (this._tabsSelect.options.length) { + this._headerContentsElement.appendChild(this._dropDownButton); + this._tabsSelect.selectedIndex = -1; + } + }, + + _tabsSelectChanged: function() + { + var options = this._tabsSelect.options; + var selectedOption = options[this._tabsSelect.selectedIndex]; + this.selectTab(selectedOption.tab.id); + }, + + _measureDropDownButton: function() + { + this._dropDownButton.addStyleClass("measuring"); + this._headerContentsElement.appendChild(this._dropDownButton); + this._measuredDropDownButtonWidth = this._dropDownButton.offsetWidth; + this._headerContentsElement.removeChild(this._dropDownButton); + this._dropDownButton.removeStyleClass("measuring"); + }, + + _updateWidths: function() + { + var measuredWidths = []; + for (var tabId in this._tabs) + measuredWidths.push(this._tabs[tabId].measuredWidth); + + var maxWidth = this._calculateMaxWidth(measuredWidths, this._headerContentsElement.offsetWidth); + + for (var tabId in this._tabs) { + var tab = this._tabs[tabId]; + tab.width = Math.min(tab.measuredWidth, maxWidth); + } + }, + + /** + * @param {Array.} measuredWidths + * @param {number} totalWidth + */ + _calculateMaxWidth: function(measuredWidths, totalWidth) + { + if (!measuredWidths.length) + return 0; + + measuredWidths.sort(function(x, y) { return x - y }); + + var totalMeasuredWidth = 0; + for (var i = 0; i < measuredWidths.length; ++i) + totalMeasuredWidth += measuredWidths[i]; + + if (totalWidth >= totalMeasuredWidth) + return measuredWidths[measuredWidths.length - 1]; + + var totalExtraWidth = 0; + for (var i = measuredWidths.length - 1; i > 0; --i) { + var extraWidth = measuredWidths[i] - measuredWidths[i - 1]; + totalExtraWidth += (measuredWidths.length - i) * extraWidth; + + if (totalWidth + totalExtraWidth >= totalMeasuredWidth) + return measuredWidths[i - 1] + (totalWidth + totalExtraWidth - totalMeasuredWidth) / (measuredWidths.length - i); + } + + return totalWidth / measuredWidths.length; + }, + + /** + * @param {Array.} tabsOrdered + * @param {Array.} tabsHistory + * @param {number} totalWidth + * @param {number} measuredDropDownButtonWidth + * @return {Array.} + */ + _tabsToShowIndexes: function(tabsOrdered, tabsHistory, totalWidth, measuredDropDownButtonWidth) + { + var tabsToShowIndexes = []; + + var totalTabsWidth = 0; + for (var i = 0; i < tabsHistory.length; ++i) { + totalTabsWidth += tabsHistory[i].width; + var minimalRequiredWidth = totalTabsWidth; + if (i !== tabsHistory.length - 1) + minimalRequiredWidth += measuredDropDownButtonWidth; + if (minimalRequiredWidth > totalWidth) + break; + tabsToShowIndexes.push(tabsOrdered.indexOf(tabsHistory[i])); + } + + tabsToShowIndexes.sort(function(x, y) { return x - y }); + + return tabsToShowIndexes; + }, + + _hideCurrentTab: function() + { + if (!this._currentTab) + return; + + this._hideTab(this._currentTab); + delete this._currentTab; + }, + + /** + * @param {WebInspector.TabbedPaneTab} tab + */ + _showTab: function(tab) + { + tab.tabElement.addStyleClass("selected"); + tab.view.show(this._contentElement); + }, + + /** + * @param {WebInspector.TabbedPaneTab} tab + */ + _hideTab: function(tab) + { + tab.tabElement.removeStyleClass("selected"); + tab.view.detach(); + }, + + canHighlightLine: function() + { + return this._currentTab && this._currentTab.view && this._currentTab.view.canHighlightLine(); + }, + + highlightLine: function(line) + { + if (this.canHighlightLine()) + this._currentTab.view.highlightLine(line); + }, + + /** + * @return {Array.} + */ + elementsToRestoreScrollPositionsFor: function() + { + return [ this._contentElement ]; + } +} + +WebInspector.TabbedPane.prototype.__proto__ = WebInspector.View.prototype; + + +/** + * @constructor + * @param {WebInspector.TabbedPane} tabbedPane + * @param {Element} measureElement + * @param {string} id + * @param {string} title + * @param {boolean} closeable + * @param {WebInspector.View} view + * @param {string=} tooltip + */ +WebInspector.TabbedPaneTab = function(tabbedPane, measureElement, id, title, closeable, view, tooltip) +{ + this._closeable = closeable; + this._tabbedPane = tabbedPane; + this._measureElement = measureElement; + this._id = id; + this._title = title; + this._tooltip = tooltip; + this._view = view; + this.shown = false; +} + +WebInspector.TabbedPaneTab.prototype = { + /** + * @type {string} + */ + get id() + { + return this._id; + }, + + /** + * @type {string} + */ + get title() + { + return this._title; + }, + + set title(title) + { + this._title = title; + if (this._titleElement) + this._titleElement.textContent = title; + delete this._measuredWidth; + }, + + /** + * @type {WebInspector.View} + */ + get view() + { + return this._view; + }, + + set view(view) + { + this._view = view; + }, + + /** + * @type {string|undefined} + */ + get tooltip() + { + return this._tooltip; + }, + + set tooltip(tooltip) + { + this._tooltip = tooltip; + if (this._titleElement) + this._titleElement.title = tooltip || ""; + }, + + /** + * @type {Element} + */ + get tabElement() + { + if (typeof(this._tabElement) !== "undefined") + return this._tabElement; + + this._createTabElement(false); + return this._tabElement; + }, + + /** + * @type {number} + */ + get measuredWidth() + { + if (typeof(this._measuredWidth) !== "undefined") + return this._measuredWidth; + + this._measure(); + return this._measuredWidth; + }, + + /** + * @type {number} + */ + get width() + { + return this._width || this.measuredWidth; + }, + + set width(width) + { + this.tabElement.style.width = width + "px"; + this._width = width; + }, + + /** + * @param {boolean} measuring + */ + _createTabElement: function(measuring) + { + var tabElement = document.createElement("div"); + tabElement.addStyleClass("tabbed-pane-header-tab"); + + var titleElement = tabElement.createChild("span", "tabbed-pane-header-tab-title"); + titleElement.textContent = this.title; + titleElement.title = this.tooltip || ""; + if (!measuring) + this._titleElement = titleElement; + + if (this._closeable) { + var closeButtonSpan = tabElement.createChild("span", "tabbed-pane-header-tab-close-button"); + closeButtonSpan.textContent = "\u00D7"; // 'MULTIPLICATION SIGN' + } + + if (measuring) + tabElement.addStyleClass("measuring"); + else { + this._tabElement = tabElement; + tabElement.addEventListener("click", this._tabSelected.bind(this), false); + if (this._closeable) + closeButtonSpan.addEventListener("click", this._tabClosed.bind(this), false); + } + + return tabElement; + }, + + _measure: function() + { + var measuringTabElement = this._createTabElement(true); + this._measureElement.appendChild(measuringTabElement); + this._measuredWidth = measuringTabElement.offsetWidth; + this._measureElement.removeChild(measuringTabElement); + }, + + _tabSelected: function() + { + this._tabbedPane.selectTab(this.id, true); + }, + + _tabClosed: function() + { + this._tabbedPane.closeTab(this.id, true); + } +}/* Drawer.js */ + +/* + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2009 Joseph Pecoraro + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + */ +WebInspector.Drawer = function() +{ + this.element = document.getElementById("drawer"); + this._savedHeight = 200; // Default. + this._mainElement = document.getElementById("main"); + this._toolbarElement = document.getElementById("toolbar"); + this._mainStatusBar = document.getElementById("main-status-bar"); + this._mainStatusBar.addEventListener("mousedown", this._startStatusBarDragging.bind(this), true); + this._counters = document.getElementById("counters"); + + this._drawerContentsElement = document.createElement("div"); + this._drawerContentsElement.id = "drawer-contents"; + this._drawerContentsElement.className = "drawer-contents"; + this.element.appendChild(this._drawerContentsElement); + + this._drawerStatusBar = document.createElement("div"); + this._drawerStatusBar.id = "drawer-status-bar"; + this._drawerStatusBar.className = "status-bar"; + this.element.appendChild(this._drawerStatusBar); + + this._viewStatusBar = document.createElement("div"); + this._drawerStatusBar.appendChild(this._viewStatusBar); +} + +WebInspector.Drawer.AnimationType = { + Immediately: 0, + Normal: 1, + Slow: 2 +} + +WebInspector.Drawer.prototype = { + get visible() + { + return !!this._view; + }, + + _constrainHeight: function(height) + { + return Number.constrain(height, Preferences.minConsoleHeight, window.innerHeight - this._mainElement.totalOffsetTop() - Preferences.minConsoleHeight); + }, + + show: function(view, animationType) + { + this.immediatelyFinishAnimation(); + if (this._view && this._view.counterElement) + this._view.counterElement.parentNode.removeChild(this._view.counterElement); + + var drawerWasVisible = this.visible; + + if (this._view) { + this._view.detach(); + this._drawerContentsElement.removeChildren(); + } + + this._view = view; + + var statusBarItems = this._view.statusBarItems || []; + this._viewStatusBar.removeChildren(); + for (var i = 0; i < statusBarItems.length; ++i) + this._viewStatusBar.appendChild(statusBarItems[i]); + + if (this._view.counterElement) + this._counters.insertBefore(this._view.counterElement, this._counters.firstChild); + + document.body.addStyleClass("drawer-visible"); + this._view.markAsRoot(); + this._view.show(this._drawerContentsElement); + + if (drawerWasVisible) + return; + + var anchoredItems = document.getElementById("anchored-status-bar-items"); + var height = this._constrainHeight(this._savedHeight || this.element.offsetHeight); + var animations = [ + {element: this.element, end: {height: height}}, + {element: this._mainElement, end: {bottom: height}}, + {element: this._mainStatusBar, start: {"padding-left": anchoredItems.offsetWidth - 1}, end: {"padding-left": 0}}, + {element: this._viewStatusBar, start: {opacity: 0}, end: {opacity: 1}} + ]; + + this._drawerStatusBar.insertBefore(anchoredItems, this._drawerStatusBar.firstChild); + + if (this._currentPanelCounters) { + var oldRight = this._drawerStatusBar.clientWidth - (this._counters.offsetLeft + this._currentPanelCounters.offsetWidth); + var newRight = WebInspector.Panel.counterRightMargin; + var rightPadding = (oldRight - newRight); + animations.push({element: this._currentPanelCounters, start: {"padding-right": rightPadding}, end: {"padding-right": 0}}); + this._currentPanelCounters.parentNode.removeChild(this._currentPanelCounters); + this._mainStatusBar.appendChild(this._currentPanelCounters); + } + + function animationFinished() + { + WebInspector.inspectorView.currentPanel().statusBarResized(); + if (this._view && this._view.afterShow) + this._view.afterShow(); + delete this._currentAnimation; + if (this._currentPanelCounters) + this._currentPanelCounters.removeAttribute("style"); + } + + this._currentAnimation = WebInspector.animateStyle(animations, this._animationDuration(animationType), animationFinished.bind(this)); + if (animationType === WebInspector.Drawer.AnimationType.Immediately) + this._currentAnimation.forceComplete(); + }, + + hide: function(animationType) + { + this.immediatelyFinishAnimation(); + if (!this.visible) + return; + + this._savedHeight = this.element.offsetHeight; + + WebInspector.restoreFocusFromElement(this.element); + + var anchoredItems = document.getElementById("anchored-status-bar-items"); + + // Temporarily set properties and classes to mimic the post-animation values so panels + // like Elements in their updateStatusBarItems call will size things to fit the final location. + this._mainStatusBar.style.setProperty("padding-left", (anchoredItems.offsetWidth - 1) + "px"); + document.body.removeStyleClass("drawer-visible"); + WebInspector.inspectorView.currentPanel().statusBarResized(); + document.body.addStyleClass("drawer-visible"); + + var animations = [ + {element: this._mainElement, end: {bottom: 0}}, + {element: this._mainStatusBar, start: {"padding-left": 0}, end: {"padding-left": anchoredItems.offsetWidth - 1}}, + {element: this._viewStatusBar, start: {opacity: 1}, end: {opacity: 0}} + ]; + + if (this._currentPanelCounters) { + var newRight = this._drawerStatusBar.clientWidth - this._counters.offsetLeft; + var oldRight = this._mainStatusBar.clientWidth - (this._currentPanelCounters.offsetLeft + this._currentPanelCounters.offsetWidth); + var rightPadding = (newRight - oldRight); + animations.push({element: this._currentPanelCounters, start: {"padding-right": 0}, end: {"padding-right": rightPadding}}); + } + + function animationFinished() + { + WebInspector.inspectorView.currentPanel().doResize(); + this._mainStatusBar.insertBefore(anchoredItems, this._mainStatusBar.firstChild); + this._mainStatusBar.style.removeProperty("padding-left"); + + if (this._view.counterElement) + this._view.counterElement.parentNode.removeChild(this._view.counterElement); + + if (this._currentPanelCounters) { + this._currentPanelCounters.setAttribute("style", null); + this._currentPanelCounters.parentNode.removeChild(this._currentPanelCounters); + this._counters.insertBefore(this._currentPanelCounters, this._counters.firstChild); + } + + this._view.detach(); + delete this._view; + this._drawerContentsElement.removeChildren(); + document.body.removeStyleClass("drawer-visible"); + delete this._currentAnimation; + } + + this._currentAnimation = WebInspector.animateStyle(animations, this._animationDuration(animationType), animationFinished.bind(this)); + if (animationType === WebInspector.Drawer.AnimationType.Immediately) + this._currentAnimation.forceComplete(); + }, + + resize: function() + { + if (!this.visible) + return; + + this._view.storeScrollPositions(); + var height = this._constrainHeight(parseInt(this.element.style.height, 10)); + this._mainElement.style.bottom = height + "px"; + this.element.style.height = height + "px"; + this._view.doResize(); + }, + + immediatelyFinishAnimation: function() + { + if (this._currentAnimation) + this._currentAnimation.forceComplete(); + }, + + set currentPanelCounters(x) + { + if (!x) { + if (this._currentPanelCounters) + this._currentPanelCounters.parentElement.removeChild(this._currentPanelCounters); + delete this._currentPanelCounters; + return; + } + + this._currentPanelCounters = x; + if (this.visible) + this._mainStatusBar.appendChild(x); + else + this._counters.insertBefore(x, this._counters.firstChild); + }, + + _animationDuration: function(animationType) + { + switch (animationType) { + case WebInspector.Drawer.AnimationType.Slow: + return 2000; + case WebInspector.Drawer.AnimationType.Normal: + return 250; + default: + return 0; + } + }, + + _startStatusBarDragging: function(event) + { + if (!this.visible || event.target !== this._mainStatusBar) + return; + + this._view.storeScrollPositions(); + WebInspector.elementDragStart(this._mainStatusBar, this._statusBarDragging.bind(this), this._endStatusBarDragging.bind(this), event, "row-resize"); + + this._statusBarDragOffset = event.pageY - this.element.totalOffsetTop(); + + event.stopPropagation(); + }, + + _statusBarDragging: function(event) + { + var height = window.innerHeight - event.pageY + this._statusBarDragOffset; + height = Number.constrain(height, Preferences.minConsoleHeight, window.innerHeight - this._mainElement.totalOffsetTop() - Preferences.minConsoleHeight); + + this._mainElement.style.bottom = height + "px"; + this.element.style.height = height + "px"; + if (WebInspector.inspectorView.currentPanel()) + WebInspector.inspectorView.currentPanel().doResize(); + this._view.doResize(); + + event.preventDefault(); + event.stopPropagation(); + }, + + _endStatusBarDragging: function(event) + { + WebInspector.elementDragEnd(event); + + this._savedHeight = this.element.offsetHeight; + delete this._statusBarDragOffset; + + event.stopPropagation(); + } +} + +/** + * @type {WebInspector.Drawer} + */ +WebInspector.drawer = null; +/* ConsoleModel.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.Object} + */ +WebInspector.ConsoleModel = function() +{ + this.messages = []; + this.warnings = 0; + this.errors = 0; + this._interruptRepeatCount = false; + InspectorBackend.registerConsoleDispatcher(new WebInspector.ConsoleDispatcher(this)); +} + +WebInspector.ConsoleModel.Events = { + ConsoleCleared: "console-cleared", + MessageAdded: "console-message-added", + RepeatCountUpdated: "repeat-count-updated" +} + +WebInspector.ConsoleModel.prototype = { + enableAgent: function() + { + if (WebInspector.settings.monitoringXHREnabled.get()) + ConsoleAgent.setMonitoringXHREnabled(true); + + ConsoleAgent.enable(); + }, + + /** + * @param {WebInspector.ConsoleMessage} msg + */ + addMessage: function(msg) + { + this.messages.push(msg); + this._previousMessage = msg; + this._incrementErrorWarningCount(msg); + this.dispatchEventToListeners(WebInspector.ConsoleModel.Events.MessageAdded, msg); + this._interruptRepeatCount = false; + }, + + /** + * @param {WebInspector.ConsoleMessage} msg + */ + _incrementErrorWarningCount: function(msg) + { + switch (msg.level) { + case WebInspector.ConsoleMessage.MessageLevel.Warning: + this.warnings += msg.repeatDelta; + break; + case WebInspector.ConsoleMessage.MessageLevel.Error: + this.errors += msg.repeatDelta; + break; + } + }, + + requestClearMessages: function() + { + ConsoleAgent.clearMessages(); + this.clearMessages(); + }, + + clearMessages: function() + { + this.messages = []; + + this.errors = 0; + this.warnings = 0; + + this.dispatchEventToListeners(WebInspector.ConsoleModel.Events.ConsoleCleared); + }, + + interruptRepeatCount: function() + { + this._interruptRepeatCount = true; + }, + + /** + * @param {number} count + */ + _messageRepeatCountUpdated: function(count) + { + var msg = this._previousMessage; + if (!msg) + return; + + var prevRepeatCount = msg.totalRepeatCount; + + if (!this._interruptRepeatCount) { + msg.repeatDelta = count - prevRepeatCount; + msg.repeatCount = msg.repeatCount + msg.repeatDelta; + msg.totalRepeatCount = count; + msg.updateRepeatCount(); + + this._incrementErrorWarningCount(msg); + this.dispatchEventToListeners(WebInspector.ConsoleModel.Events.RepeatCountUpdated, msg); + } else { + var msgCopy = msg.clone(); + msgCopy.totalRepeatCount = count; + msgCopy.repeatCount = (count - prevRepeatCount) || 1; + msgCopy.repeatDelta = msgCopy.repeatCount; + this.addMessage(msgCopy); + } + } +} + +WebInspector.ConsoleModel.prototype.__proto__ = WebInspector.Object.prototype; + +/** + * @constructor + * @param {string} source + * @param {string} level + * @param {string=} url + * @param {number=} line + * @param {number=} repeatCount + */ +WebInspector.ConsoleMessage = function(source, level, url, line, repeatCount) +{ + this.source = source; + this.level = level; + this.url = url || null; + this.line = line || 0; + + repeatCount = repeatCount || 1; + this.repeatCount = repeatCount; + this.repeatDelta = repeatCount; + this.totalRepeatCount = repeatCount; +} + +WebInspector.ConsoleMessage.prototype = { + /** + * @return {boolean} + */ + isErrorOrWarning: function() + { + return (this.level === WebInspector.ConsoleMessage.MessageLevel.Warning || this.level === WebInspector.ConsoleMessage.MessageLevel.Error); + }, + + updateRepeatCount: function() + { + // Implemented by concrete instances + }, + + /** + * @return {WebInspector.ConsoleMessage} + */ + clone: function() + { + // Implemented by concrete instances + } +} + +/** + * @param {string} source + * @param {string} level + * @param {string} message + * @param {string=} type + * @param {string=} url + * @param {number=} line + * @param {number=} repeatCount + * @param {Array.=} parameters + * @param {ConsoleAgent.StackTrace=} stackTrace + * @param {WebInspector.Resource=} request + * + * @return {WebInspector.ConsoleMessage} + */ +WebInspector.ConsoleMessage.create = function(source, level, message, type, url, line, repeatCount, parameters, stackTrace, request) +{ +} + +// Note: Keep these constants in sync with the ones in Console.h +WebInspector.ConsoleMessage.MessageSource = { + HTML: "html", + XML: "xml", + JS: "javascript", + Network: "network", + ConsoleAPI: "console-api", + Other: "other" +} + +WebInspector.ConsoleMessage.MessageType = { + Log: "log", + Dir: "dir", + DirXML: "dirxml", + Trace: "trace", + StartGroup: "startGroup", + StartGroupCollapsed: "startGroupCollapsed", + EndGroup: "endGroup", + Assert: "assert", + Result: "result" +} + +WebInspector.ConsoleMessage.MessageLevel = { + Tip: "tip", + Log: "log", + Warning: "warning", + Error: "error", + Debug: "debug" +} + + +/** + * @constructor + * @implements {ConsoleAgent.Dispatcher} + * @param {WebInspector.ConsoleModel} console + */ +WebInspector.ConsoleDispatcher = function(console) +{ + this._console = console; +} + +WebInspector.ConsoleDispatcher.prototype = { + /** + * @param {ConsoleAgent.ConsoleMessage} payload + */ + messageAdded: function(payload) + { + var consoleMessage = WebInspector.ConsoleMessage.create( + payload.source, + payload.level, + payload.text, + payload.type, + payload.url, + payload.line, + payload.repeatCount, + payload.parameters, + payload.stackTrace, + payload.networkRequestId ? WebInspector.networkResourceById(payload.networkRequestId) : undefined); + this._console.addMessage(consoleMessage); + }, + + /** + * @param {number} count + */ + messageRepeatCountUpdated: function(count) + { + this._console._messageRepeatCountUpdated(count); + }, + + messagesCleared: function() + { + if (!WebInspector.settings.preserveConsoleLog.get()) + this._console.clearMessages(); + } +} + +/** + * @type {?WebInspector.ConsoleModel} + */ +WebInspector.console = null; +/* ConsoleMessage.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2009 Joseph Pecoraro + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.ConsoleMessage} + * + * @param {string} source + * @param {string} level + * @param {string} message + * @param {WebInspector.DebuggerPresentationModel.Linkifier} linkifier + * @param {string=} type + * @param {string=} url + * @param {number=} line + * @param {number=} repeatCount + * @param {Array.=} parameters + * @param {ConsoleAgent.StackTrace=} stackTrace + * @param {WebInspector.Resource=} request + */ +WebInspector.ConsoleMessageImpl = function(source, level, message, linkifier, type, url, line, repeatCount, parameters, stackTrace, request) +{ + WebInspector.ConsoleMessage.call(this, source, level, url, line, repeatCount); + + this._linkifier = linkifier; + this.type = type || WebInspector.ConsoleMessage.MessageType.Log; + this._messageText = message; + this._parameters = parameters; + this._stackTrace = stackTrace; + this._request = request; + + this._customFormatters = { + "object": this._formatParameterAsObject, + "array": this._formatParameterAsArray, + "node": this._formatParameterAsNode, + "string": this._formatParameterAsString + }; +} + +WebInspector.ConsoleMessageImpl.prototype = { + _formatMessage: function() + { + this._formattedMessage = document.createElement("span"); + this._formattedMessage.className = "console-message-text source-code"; + + if (this.source === WebInspector.ConsoleMessage.MessageSource.ConsoleAPI) { + switch (this.type) { + case WebInspector.ConsoleMessage.MessageType.Trace: + this._messageElement = document.createTextNode("console.trace()"); + break; + case WebInspector.ConsoleMessage.MessageType.Assert: + var args = [WebInspector.UIString("Assertion failed:")]; + if (this._parameters) + args = args.concat(this._parameters); + this._messageElement = this._format(args); + break; + case WebInspector.ConsoleMessage.MessageType.Dir: + var obj = this._parameters ? this._parameters[0] : undefined; + var args = ["%O", obj]; + this._messageElement = this._format(args); + break; + default: + var args = this._parameters || [this._messageText]; + this._messageElement = this._format(args); + } + } else if (this.source === WebInspector.ConsoleMessage.MessageSource.Network) { + if (this._request) { + this._stackTrace = this._request.stackTrace; + if (this._request.initiator && this._request.initiator.url) { + this.url = this._request.initiator.url; + this.line = this._request.initiator.lineNumber; + } + this._messageElement = document.createElement("span"); + if (this.level === WebInspector.ConsoleMessage.MessageLevel.Error) { + this._messageElement.appendChild(document.createTextNode(this._request.requestMethod + " ")); + this._messageElement.appendChild(WebInspector.linkifyRequestAsNode(this._request)); + if (this._request.failed) + this._messageElement.appendChild(document.createTextNode(" " + this._request.localizedFailDescription)); + else + this._messageElement.appendChild(document.createTextNode(" " + this._request.statusCode + " (" + this._request.statusText + ")")); + } else { + var fragment = WebInspector.linkifyStringAsFragmentWithCustomLinkifier(this._messageText, WebInspector.linkifyRequestAsNode.bind(null, this._request, "")); + this._messageElement.appendChild(fragment); + } + } else { + if (this.url) { + var isExternal = !WebInspector.resourceForURL(this.url); + this._anchorElement = WebInspector.linkifyURLAsNode(this.url, this.url, "console-message-url", isExternal); + } + this._messageElement = this._format([this._messageText]); + } + } else { + var args = this._parameters || [this._messageText]; + this._messageElement = this._format(args); + } + + if (this.source !== WebInspector.ConsoleMessage.MessageSource.Network || this._request) { + if (this._stackTrace && this._stackTrace.length && this._stackTrace[0].url) { + this._anchorElement = this._linkifyCallFrame(this._stackTrace[0]); + } else if (this.url && this.url !== "undefined") { + this._anchorElement = this._linkifyLocation(this.url, this.line, 0); + } + } + + if (this._anchorElement) + this._formattedMessage.appendChild(this._anchorElement); + this._formattedMessage.appendChild(this._messageElement); + + var dumpStackTrace = !!this._stackTrace && this._stackTrace.length && (this.source === WebInspector.ConsoleMessage.MessageSource.Network || this.level === WebInspector.ConsoleMessage.MessageLevel.Error || this.type === WebInspector.ConsoleMessage.MessageType.Trace); + if (dumpStackTrace) { + var ol = document.createElement("ol"); + ol.className = "outline-disclosure"; + var treeOutline = new TreeOutline(ol); + + var content = this._formattedMessage; + var root = new TreeElement(content, null, true); + content.treeElementForTest = root; + treeOutline.appendChild(root); + if (this.type === WebInspector.ConsoleMessage.MessageType.Trace) + root.expand(); + + this._populateStackTraceTreeElement(root); + this._formattedMessage = ol; + } + + // This is used for inline message bubbles in SourceFrames, or other plain-text representations. + this._message = this._messageElement.textContent; + }, + + get message() + { + // force message formatting + var formattedMessage = this.formattedMessage; + return this._message; + }, + + get formattedMessage() + { + if (!this._formattedMessage) + this._formatMessage(); + return this._formattedMessage; + }, + + _linkifyLocation: function(url, lineNumber, columnNumber) + { + // FIXME(62725): stack trace line/column numbers are one-based. + lineNumber = lineNumber ? lineNumber - 1 : 0; + columnNumber = columnNumber ? columnNumber - 1 : 0; + return this._linkifier.linkifyLocation(url, lineNumber, columnNumber, "console-message-url"); + }, + + _linkifyCallFrame: function(callFrame) + { + return this._linkifyLocation(callFrame.url, callFrame.lineNumber, callFrame.columnNumber); + }, + + isErrorOrWarning: function() + { + return (this.level === WebInspector.ConsoleMessage.MessageLevel.Warning || this.level === WebInspector.ConsoleMessage.MessageLevel.Error); + }, + + _format: function(parameters) + { + // This node is used like a Builder. Values are continually appended onto it. + var formattedResult = document.createElement("span"); + if (!parameters.length) + return formattedResult; + + // Formatting code below assumes that parameters are all wrappers whereas frontend console + // API allows passing arbitrary values as messages (strings, numbers, etc.). Wrap them here. + for (var i = 0; i < parameters.length; ++i) { + // FIXME: Only pass runtime wrappers here. + if (parameters[i] instanceof WebInspector.RemoteObject) + continue; + + if (typeof parameters[i] === "object") + parameters[i] = WebInspector.RemoteObject.fromPayload(parameters[i]); + else + parameters[i] = WebInspector.RemoteObject.fromPrimitiveValue(parameters[i]); + } + + // There can be string log and string eval result. We distinguish between them based on message type. + var shouldFormatMessage = WebInspector.RemoteObject.type(parameters[0]) === "string" && this.type !== WebInspector.ConsoleMessage.MessageType.Result; + + // Multiple parameters with the first being a format string. Save unused substitutions. + if (shouldFormatMessage) { + // Multiple parameters with the first being a format string. Save unused substitutions. + var result = this._formatWithSubstitutionString(parameters, formattedResult); + parameters = result.unusedSubstitutions; + if (parameters.length) + formattedResult.appendChild(document.createTextNode(" ")); + } + + // Single parameter, or unused substitutions from above. + for (var i = 0; i < parameters.length; ++i) { + // Inline strings when formatting. + if (shouldFormatMessage && parameters[i].type === "string") + formattedResult.appendChild(document.createTextNode(parameters[i].description)); + else + formattedResult.appendChild(this._formatParameter(parameters[i])); + if (i < parameters.length - 1) + formattedResult.appendChild(document.createTextNode(" ")); + } + return formattedResult; + }, + + /** + * @param {boolean=} forceObjectFormat + */ + _formatParameter: function(output, forceObjectFormat) + { + var type; + if (forceObjectFormat) + type = "object"; + else if (output instanceof WebInspector.RemoteObject) + type = output.subtype || output.type; + else + type = typeof output; + + var formatter = this._customFormatters[type]; + if (!formatter) { + formatter = this._formatParameterAsValue; + output = output.description; + } + + var span = document.createElement("span"); + span.className = "console-formatted-" + type + " source-code"; + formatter.call(this, output, span); + return span; + }, + + _formatParameterAsValue: function(val, elem) + { + elem.appendChild(document.createTextNode(val)); + }, + + _formatParameterAsObject: function(obj, elem) + { + elem.appendChild(new WebInspector.ObjectPropertiesSection(obj, obj.description).element); + }, + + _formatParameterAsNode: function(object, elem) + { + function printNode(nodeId) + { + if (!nodeId) { + // Sometimes DOM is loaded after the sync message is being formatted, so we get no + // nodeId here. So we fall back to object formatting here. + this._formatParameterAsObject(object, elem); + return; + } + var treeOutline = new WebInspector.ElementsTreeOutline(false, false, true); + treeOutline.setVisible(true); + treeOutline.rootDOMNode = WebInspector.domAgent.nodeForId(nodeId); + treeOutline.element.addStyleClass("outline-disclosure"); + if (!treeOutline.children[0].hasChildren) + treeOutline.element.addStyleClass("single-node"); + elem.appendChild(treeOutline.element); + } + object.pushNodeToFrontend(printNode.bind(this)); + }, + + _formatParameterAsArray: function(arr, elem) + { + arr.getOwnProperties(this._printArray.bind(this, elem)); + }, + + _formatParameterAsString: function(output, elem) + { + var span = document.createElement("span"); + span.className = "console-formatted-string source-code"; + span.appendChild(WebInspector.linkifyStringAsFragment(output.description)); + + // Make black quotes. + elem.removeStyleClass("console-formatted-string"); + elem.appendChild(document.createTextNode("\"")); + elem.appendChild(span); + elem.appendChild(document.createTextNode("\"")); + }, + + _printArray: function(elem, properties) + { + if (!properties) + return; + + var elements = []; + for (var i = 0; i < properties.length; ++i) { + var name = properties[i].name; + if (name == parseInt(name, 10)) + elements[name] = this._formatAsArrayEntry(properties[i].value); + } + + elem.appendChild(document.createTextNode("[")); + for (var i = 0; i < elements.length; ++i) { + var element = elements[i]; + if (element) + elem.appendChild(element); + else + elem.appendChild(document.createTextNode("undefined")) + if (i < elements.length - 1) + elem.appendChild(document.createTextNode(", ")); + } + elem.appendChild(document.createTextNode("]")); + }, + + _formatAsArrayEntry: function(output) + { + // Prevent infinite expansion of cross-referencing arrays. + return this._formatParameter(output, output.subtype && output.subtype === "array"); + }, + + _formatWithSubstitutionString: function(parameters, formattedResult) + { + var formatters = {} + + function parameterFormatter(force, obj) + { + return this._formatParameter(obj, force); + } + + function valueFormatter(obj) + { + return obj.description; + } + + // Firebug uses %o for formatting objects. + formatters.o = parameterFormatter.bind(this, false); + formatters.s = valueFormatter; + formatters.f = valueFormatter; + // Firebug allows both %i and %d for formatting integers. + formatters.i = valueFormatter; + formatters.d = valueFormatter; + + // Support %O to force object formatting, instead of the type-based %o formatting. + formatters.O = parameterFormatter.bind(this, true); + + function append(a, b) + { + if (!(b instanceof Node)) + a.appendChild(WebInspector.linkifyStringAsFragment(b.toString())); + else + a.appendChild(b); + return a; + } + + // String.format does treat formattedResult like a Builder, result is an object. + return String.format(parameters[0].description, parameters.slice(1), formatters, formattedResult, append); + }, + + clearHighlight: function() + { + if (!this._formattedMessage) + return; + + var highlightedMessage = this._formattedMessage; + delete this._formattedMessage; + delete this._anchorElement; + delete this._messageElement; + this._formatMessage(); + this._element.replaceChild(this._formattedMessage, highlightedMessage); + }, + + highlightSearchResults: function(regexObject) + { + if (!this._formattedMessage) + return; + + this._highlightSearchResultsInElement(regexObject, this._messageElement); + if (this._anchorElement) + this._highlightSearchResultsInElement(regexObject, this._anchorElement); + + this._element.scrollIntoViewIfNeeded(); + }, + + _highlightSearchResultsInElement: function(regexObject, element) + { + regexObject.lastIndex = 0; + var text = element.textContent; + var match = regexObject.exec(text); + var offset = 0; + var matchRanges = []; + while (match) { + matchRanges.push({ offset: match.index, length: match[0].length }); + match = regexObject.exec(text); + } + highlightSearchResults(element, matchRanges); + }, + + matchesRegex: function(regexObject) + { + return regexObject.test(this._message) || (this._anchorElement && regexObject.test(this._anchorElement.textContent)); + }, + + toMessageElement: function() + { + if (this._element) + return this._element; + + var element = document.createElement("div"); + element.message = this; + element.className = "console-message"; + + this._element = element; + + switch (this.level) { + case WebInspector.ConsoleMessage.MessageLevel.Tip: + element.addStyleClass("console-tip-level"); + break; + case WebInspector.ConsoleMessage.MessageLevel.Log: + element.addStyleClass("console-log-level"); + break; + case WebInspector.ConsoleMessage.MessageLevel.Debug: + element.addStyleClass("console-debug-level"); + break; + case WebInspector.ConsoleMessage.MessageLevel.Warning: + element.addStyleClass("console-warning-level"); + break; + case WebInspector.ConsoleMessage.MessageLevel.Error: + element.addStyleClass("console-error-level"); + break; + } + + if (this.type === WebInspector.ConsoleMessage.MessageType.StartGroup || this.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed) + element.addStyleClass("console-group-title"); + + element.appendChild(this.formattedMessage); + + if (this.repeatCount > 1) + this.updateRepeatCount(); + + return element; + }, + + _populateStackTraceTreeElement: function(parentTreeElement) + { + for (var i = 0; i < this._stackTrace.length; i++) { + var frame = this._stackTrace[i]; + + var content = document.createElement("div"); + var messageTextElement = document.createElement("span"); + messageTextElement.className = "console-message-text source-code"; + var functionName = frame.functionName || WebInspector.UIString("(anonymous function)"); + messageTextElement.appendChild(document.createTextNode(functionName)); + content.appendChild(messageTextElement); + + if (frame.url) { + var urlElement = this._linkifyCallFrame(frame); + content.appendChild(urlElement); + } + + var treeElement = new TreeElement(content); + parentTreeElement.appendChild(treeElement); + } + }, + + updateRepeatCount: function() { + if (!this.repeatCountElement) { + this.repeatCountElement = document.createElement("span"); + this.repeatCountElement.className = "bubble"; + + this._element.insertBefore(this.repeatCountElement, this._element.firstChild); + this._element.addStyleClass("repeated-message"); + } + this.repeatCountElement.textContent = this.repeatCount; + }, + + toString: function() + { + var sourceString; + switch (this.source) { + case WebInspector.ConsoleMessage.MessageSource.HTML: + sourceString = "HTML"; + break; + case WebInspector.ConsoleMessage.MessageSource.XML: + sourceString = "XML"; + break; + case WebInspector.ConsoleMessage.MessageSource.JS: + sourceString = "JS"; + break; + case WebInspector.ConsoleMessage.MessageSource.Network: + sourceString = "Network"; + break; + case WebInspector.ConsoleMessage.MessageSource.ConsoleAPI: + sourceString = "ConsoleAPI"; + break; + case WebInspector.ConsoleMessage.MessageSource.Other: + sourceString = "Other"; + break; + } + + var typeString; + switch (this.type) { + case WebInspector.ConsoleMessage.MessageType.Log: + typeString = "Log"; + break; + case WebInspector.ConsoleMessage.MessageType.Dir: + typeString = "Dir"; + break; + case WebInspector.ConsoleMessage.MessageType.DirXML: + typeString = "Dir XML"; + break; + case WebInspector.ConsoleMessage.MessageType.Trace: + typeString = "Trace"; + break; + case WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed: + case WebInspector.ConsoleMessage.MessageType.StartGroup: + typeString = "Start Group"; + break; + case WebInspector.ConsoleMessage.MessageType.EndGroup: + typeString = "End Group"; + break; + case WebInspector.ConsoleMessage.MessageType.Assert: + typeString = "Assert"; + break; + case WebInspector.ConsoleMessage.MessageType.Result: + typeString = "Result"; + break; + } + + var levelString; + switch (this.level) { + case WebInspector.ConsoleMessage.MessageLevel.Tip: + levelString = "Tip"; + break; + case WebInspector.ConsoleMessage.MessageLevel.Log: + levelString = "Log"; + break; + case WebInspector.ConsoleMessage.MessageLevel.Warning: + levelString = "Warning"; + break; + case WebInspector.ConsoleMessage.MessageLevel.Debug: + levelString = "Debug"; + break; + case WebInspector.ConsoleMessage.MessageLevel.Error: + levelString = "Error"; + break; + } + + return sourceString + " " + typeString + " " + levelString + ": " + this.formattedMessage.textContent + "\n" + this.url + " line " + this.line; + }, + + get text() + { + return this._messageText; + }, + + isEqual: function(msg) + { + if (!msg) + return false; + + if (this._stackTrace) { + if (!msg._stackTrace) + return false; + var l = this._stackTrace; + var r = msg._stackTrace; + for (var i = 0; i < l.length; i++) { + if (l[i].url !== r[i].url || + l[i].functionName !== r[i].functionName || + l[i].lineNumber !== r[i].lineNumber || + l[i].columnNumber !== r[i].columnNumber) + return false; + } + } + + return (this.source === msg.source) + && (this.type === msg.type) + && (this.level === msg.level) + && (this.line === msg.line) + && (this.url === msg.url) + && (this.message === msg.message) + && (this._request === msg._request); + }, + + get stackTrace() + { + return this._stackTrace; + }, + + /** + * @return {WebInspector.ConsoleMessage} + */ + clone: function() + { + return WebInspector.ConsoleMessage.create(this.source, this.level, this._messageText, this.type, this.url, this.line, this.repeatCount, this._parameters, this._stackTrace, this._request); + } +} + +WebInspector.ConsoleMessageImpl.prototype.__proto__ = WebInspector.ConsoleMessage.prototype; +/* ConsoleView.js */ + +/* + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2009 Joseph Pecoraro + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +const ExpressionStopCharacters = " =:[({;,!+-*/&|^<>"; + +/** + * @extends {WebInspector.View} + * @constructor + * @param {boolean} hideContextSelector + */ +WebInspector.ConsoleView = function(hideContextSelector) +{ + WebInspector.View.call(this); + + this.element.id = "console-view"; + this.messages = []; + + this._clearConsoleButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear console log."), "clear-status-bar-item"); + this._clearConsoleButton.addEventListener("click", this._requestClearMessages, this); + + this._contextSelectElement = document.createElement("select"); + this._contextSelectElement.id = "console-context"; + this._contextSelectElement.className = "status-bar-item"; + + if (hideContextSelector) + this._contextSelectElement.addStyleClass("hidden"); + + this.messagesElement = document.createElement("div"); + this.messagesElement.id = "console-messages"; + this.messagesElement.className = "monospace"; + this.messagesElement.addEventListener("click", this._messagesClicked.bind(this), true); + this.element.appendChild(this.messagesElement); + this._scrolledToBottom = true; + + this.promptElement = document.createElement("div"); + this.promptElement.id = "console-prompt"; + this.promptElement.className = "source-code"; + this.promptElement.spellcheck = false; + this.messagesElement.appendChild(this.promptElement); + this.messagesElement.appendChild(document.createElement("br")); + + this.topGroup = new WebInspector.ConsoleGroup(null); + this.messagesElement.insertBefore(this.topGroup.element, this.promptElement); + this.currentGroup = this.topGroup; + + this._filterBarElement = document.createElement("div"); + this._filterBarElement.id = "console-filter"; + this._filterBarElement.className = "scope-bar status-bar-item"; + + function createDividerElement() { + var dividerElement = document.createElement("div"); + dividerElement.addStyleClass("scope-bar-divider"); + this._filterBarElement.appendChild(dividerElement); + } + + var updateFilterHandler = this._updateFilter.bind(this); + function createFilterElement(category, label) { + var categoryElement = document.createElement("li"); + categoryElement.category = category; + categoryElement.className = category; + categoryElement.addEventListener("click", updateFilterHandler, false); + categoryElement.textContent = label; + + this._filterBarElement.appendChild(categoryElement); + + return categoryElement; + } + + this.allElement = createFilterElement.call(this, "all", WebInspector.UIString("All")); + createDividerElement.call(this); + this.errorElement = createFilterElement.call(this, "errors", WebInspector.UIString("Errors")); + this.warningElement = createFilterElement.call(this, "warnings", WebInspector.UIString("Warnings")); + this.logElement = createFilterElement.call(this, "logs", WebInspector.UIString("Logs")); + + this.filter(this.allElement, false); + this._registerShortcuts(); + this.registerRequiredCSS("textPrompt.css"); + + this.messagesElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), false); + + WebInspector.settings.monitoringXHREnabled.addChangeListener(this._monitoringXHREnabledSettingChanged.bind(this)); + + WebInspector.console.addEventListener(WebInspector.ConsoleModel.Events.MessageAdded, this._consoleMessageAdded, this); + WebInspector.console.addEventListener(WebInspector.ConsoleModel.Events.ConsoleCleared, this._consoleCleared, this); + + this._linkifier = WebInspector.debuggerPresentationModel.createLinkifier(); + + this.prompt = new WebInspector.TextPromptWithHistory(this.completions.bind(this), ExpressionStopCharacters + "."); + this.prompt.setSuggestBoxEnabled("generic-suggest"); + this.prompt.renderAsBlock(); + this.prompt.attach(this.promptElement); + this.prompt.proxyElement.addEventListener("keydown", this._promptKeyDown.bind(this), false); + this.prompt.setHistoryData(WebInspector.settings.consoleHistory.get()); +} + +WebInspector.ConsoleView.Events = { + ConsoleCleared: "console-cleared", + EntryAdded: "console-entry-added", +} + +WebInspector.ConsoleView.prototype = { + get statusBarItems() + { + return [this._clearConsoleButton.element, this._contextSelectElement, this._filterBarElement]; + }, + + addContext: function(context) + { + var option = document.createElement("option"); + option.text = context.displayName; + option.title = context.url; + option._context = context; + context._consoleOption = option; + this._contextSelectElement.appendChild(option); + context.addEventListener(WebInspector.FrameEvaluationContext.EventTypes.Updated, this._contextUpdated, this); + }, + + removeContext: function(context) + { + this._contextSelectElement.removeChild(context._consoleOption); + }, + + _contextUpdated: function(event) + { + var context = event.data; + var option= context._consoleOption; + option.text = context.displayName; + option.title = context.url; + }, + + _currentEvaluationContextId: function() + { + if (this._contextSelectElement.selectedIndex === -1) + return undefined; + return this._contextSelectElement[this._contextSelectElement.selectedIndex]._context.frameId; + }, + + _updateFilter: function(e) + { + var isMac = WebInspector.isMac(); + var selectMultiple = false; + if (isMac && e.metaKey && !e.ctrlKey && !e.altKey && !e.shiftKey) + selectMultiple = true; + if (!isMac && e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey) + selectMultiple = true; + + this.filter(e.target, selectMultiple); + }, + + filter: function(target, selectMultiple) + { + function unselectAll() + { + this.allElement.removeStyleClass("selected"); + this.errorElement.removeStyleClass("selected"); + this.warningElement.removeStyleClass("selected"); + this.logElement.removeStyleClass("selected"); + + this.messagesElement.removeStyleClass("filter-all"); + this.messagesElement.removeStyleClass("filter-errors"); + this.messagesElement.removeStyleClass("filter-warnings"); + this.messagesElement.removeStyleClass("filter-logs"); + } + + var targetFilterClass = "filter-" + target.category; + + if (target.category === "all") { + if (target.hasStyleClass("selected")) { + // We can't unselect all, so we break early here + return; + } + + unselectAll.call(this); + } else { + // Something other than all is being selected, so we want to unselect all + if (this.allElement.hasStyleClass("selected")) { + this.allElement.removeStyleClass("selected"); + this.messagesElement.removeStyleClass("filter-all"); + } + } + + if (!selectMultiple) { + // If multiple selection is off, we want to unselect everything else + // and just select ourselves. + unselectAll.call(this); + + target.addStyleClass("selected"); + this.messagesElement.addStyleClass(targetFilterClass); + + return; + } + + if (target.hasStyleClass("selected")) { + // If selectMultiple is turned on, and we were selected, we just + // want to unselect ourselves. + target.removeStyleClass("selected"); + this.messagesElement.removeStyleClass(targetFilterClass); + } else { + // If selectMultiple is turned on, and we weren't selected, we just + // want to select ourselves. + target.addStyleClass("selected"); + this.messagesElement.addStyleClass(targetFilterClass); + } + }, + + willHide: function() + { + this.prompt.hideSuggestBox(); + this.prompt.clearAutoComplete(true); + }, + + wasShown: function() + { + if (!this.prompt.isCaretInsidePrompt()) + this.prompt.moveCaretToEndOfPrompt(); + }, + + afterShow: function() + { + WebInspector.setCurrentFocusElement(this.promptElement); + }, + + storeScrollPositions: function() + { + WebInspector.View.prototype.storeScrollPositions.call(this); + this._scrolledToBottom = this.messagesElement.isScrolledToBottom(); + }, + + restoreScrollPositions: function() + { + if (this._scrolledToBottom) + this._immediatelyScrollIntoView(); + else + WebInspector.View.prototype.restoreScrollPositions.call(this); + }, + + onResize: function() + { + this.restoreScrollPositions(); + }, + + _isScrollIntoViewScheduled: function() + { + return !!this._scrollIntoViewTimer; + }, + + _scheduleScrollIntoView: function() + { + if (this._scrollIntoViewTimer) + return; + + function scrollIntoView() + { + delete this._scrollIntoViewTimer; + this.promptElement.scrollIntoView(true); + } + this._scrollIntoViewTimer = setTimeout(scrollIntoView.bind(this), 20); + }, + + _immediatelyScrollIntoView: function() + { + this.promptElement.scrollIntoView(true); + this._cancelScheduledScrollIntoView(); + }, + + _cancelScheduledScrollIntoView: function() + { + if (!this._isScrollIntoViewScheduled()) + return; + + clearTimeout(this._scrollIntoViewTimer); + delete this._scrollIntoViewTimer; + }, + + _consoleMessageAdded: function(event) + { + this._appendConsoleMessage(event.data); + }, + + _appendConsoleMessage: function(msg) + { + // this.messagesElement.isScrolledToBottom() is forcing style recalculation. + // We just skip it if the scroll action has been scheduled. + if (!this._isScrollIntoViewScheduled() && ((msg instanceof WebInspector.ConsoleCommandResult) || this.messagesElement.isScrolledToBottom())) + this._scheduleScrollIntoView(); + + this.messages.push(msg); + + if (msg.type === WebInspector.ConsoleMessage.MessageType.EndGroup) { + var parentGroup = this.currentGroup.parentGroup + if (parentGroup) + this.currentGroup = parentGroup; + } else { + if (msg.type === WebInspector.ConsoleMessage.MessageType.StartGroup || msg.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed) { + var group = new WebInspector.ConsoleGroup(this.currentGroup); + this.currentGroup.messagesElement.appendChild(group.element); + this.currentGroup = group; + } + + this.currentGroup.addMessage(msg); + } + + this.dispatchEventToListeners(WebInspector.ConsoleView.Events.EntryAdded, msg); + }, + + _consoleCleared: function() + { + this._scrolledToBottom = true; + this.messages = []; + + this.currentGroup = this.topGroup; + this.topGroup.messagesElement.removeChildren(); + + this.dispatchEventToListeners(WebInspector.ConsoleView.Events.ConsoleCleared); + + this._linkifier.reset(); + }, + + completions: function(wordRange, force, completionsReadyCallback) + { + // Pass less stop characters to rangeOfWord so the range will be a more complete expression. + var expressionRange = wordRange.startContainer.rangeOfWord(wordRange.startOffset, ExpressionStopCharacters, this.promptElement, "backward"); + var expressionString = expressionRange.toString(); + var prefix = wordRange.toString(); + this._completions(expressionString, prefix, force, completionsReadyCallback); + }, + + _completions: function(expressionString, prefix, force, completionsReadyCallback) + { + var lastIndex = expressionString.length - 1; + + var dotNotation = (expressionString[lastIndex] === "."); + var bracketNotation = (expressionString[lastIndex] === "["); + + if (dotNotation || bracketNotation) + expressionString = expressionString.substr(0, lastIndex); + + if (expressionString && parseInt(expressionString, 10) == expressionString) { + // User is entering float value, do not suggest anything. + completionsReadyCallback([]); + return; + } + + if (!prefix && !expressionString && !force) { + completionsReadyCallback([]); + return; + } + + if (!expressionString && WebInspector.debuggerPresentationModel.paused) + WebInspector.debuggerPresentationModel.getSelectedCallFrameVariables(receivedPropertyNames.bind(this)); + else + this.evalInInspectedWindow(expressionString, "completion", true, true, false, evaluated.bind(this)); + + function evaluated(result, wasThrown) + { + if (!result || wasThrown) { + completionsReadyCallback([]); + return; + } + + function getCompletions(primitiveType) + { + var object; + if (primitiveType === "string") + object = new String(""); + else if (primitiveType === "number") + object = new Number(0); + else if (primitiveType === "boolean") + object = new Boolean(false); + else + object = this; + + var resultSet = {}; + for (var o = object; o; o = o.__proto__) { + try { + var names = Object.getOwnPropertyNames(o); + for (var i = 0; i < names.length; ++i) + resultSet[names[i]] = true; + } catch (e) { + } + } + return resultSet; + } + + if (result.type === "object" || result.type === "function") + result.callFunctionJSON(getCompletions, receivedPropertyNames.bind(this)); + else if (result.type === "string" || result.type === "number" || result.type === "boolean") + this.evalInInspectedWindow("(" + getCompletions + ")(\"" + result.type + "\")", "completion", false, true, true, receivedPropertyNamesFromEval.bind(this)); + } + + function receivedPropertyNamesFromEval(notRelevant, wasThrown, result) + { + if (result && !wasThrown) + receivedPropertyNames.call(this, result.value); + else + completionsReadyCallback([]); + } + + function receivedPropertyNames(propertyNames) + { + RuntimeAgent.releaseObjectGroup("completion"); + if (!propertyNames) { + completionsReadyCallback([]); + return; + } + var includeCommandLineAPI = (!dotNotation && !bracketNotation); + if (includeCommandLineAPI) { + const commandLineAPI = ["dir", "dirxml", "keys", "values", "profile", "profileEnd", "monitorEvents", "unmonitorEvents", "inspect", "copy", "clear"]; + for (var i = 0; i < commandLineAPI.length; ++i) + propertyNames[commandLineAPI[i]] = true; + } + this._reportCompletions(completionsReadyCallback, dotNotation, bracketNotation, expressionString, prefix, Object.keys(propertyNames)); + } + }, + + _reportCompletions: function(completionsReadyCallback, dotNotation, bracketNotation, expressionString, prefix, properties) { + if (bracketNotation) { + if (prefix.length && prefix[0] === "'") + var quoteUsed = "'"; + else + var quoteUsed = "\""; + } + + var results = []; + + if (!expressionString) { + const keywords = ["break", "case", "catch", "continue", "default", "delete", "do", "else", "finally", "for", "function", "if", "in", + "instanceof", "new", "return", "switch", "this", "throw", "try", "typeof", "var", "void", "while", "with"]; + properties = properties.concat(keywords); + } + + properties.sort(); + + for (var i = 0; i < properties.length; ++i) { + var property = properties[i]; + + if (dotNotation && !/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(property)) + continue; + + if (bracketNotation) { + if (!/^[0-9]+$/.test(property)) + property = quoteUsed + property.escapeCharacters(quoteUsed + "\\") + quoteUsed; + property += "]"; + } + + if (property.length < prefix.length) + continue; + if (prefix.length && property.indexOf(prefix) !== 0) + continue; + + results.push(property); + } + completionsReadyCallback(results); + }, + + _handleContextMenuEvent: function(event) + { + if (!window.getSelection().isCollapsed) { + // If there is a selection, we want to show our normal context menu + // (with Copy, etc.), and not Clear Console. + return; + } + + var contextMenu = new WebInspector.ContextMenu(); + + if (WebInspector.populateHrefContextMenu(contextMenu, null, event)) + contextMenu.appendSeparator(); + + function monitoringXHRItemAction() + { + WebInspector.settings.monitoringXHREnabled.set(!WebInspector.settings.monitoringXHREnabled.get()); + } + contextMenu.appendCheckboxItem(WebInspector.UIString("Log XMLHttpRequests"), monitoringXHRItemAction.bind(this), WebInspector.settings.monitoringXHREnabled.get()); + + function preserveLogItemAction() + { + WebInspector.settings.preserveConsoleLog.set(!WebInspector.settings.preserveConsoleLog.get()); + } + contextMenu.appendCheckboxItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Preserve log upon navigation" : "Preserve Log upon Navigation"), preserveLogItemAction.bind(this), WebInspector.settings.preserveConsoleLog.get()); + + contextMenu.appendSeparator(); + contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Clear console" : "Clear Console"), this._requestClearMessages.bind(this)); + contextMenu.show(event); + }, + + _monitoringXHREnabledSettingChanged: function(event) + { + ConsoleAgent.setMonitoringXHREnabled(event.data); + }, + + _messagesClicked: function(event) + { + if (!this.prompt.isCaretInsidePrompt() && window.getSelection().isCollapsed) + this.prompt.moveCaretToEndOfPrompt(); + }, + + _registerShortcuts: function() + { + this._shortcuts = {}; + + var shortcut = WebInspector.KeyboardShortcut; + + if (WebInspector.isMac()) { + var shortcutK = shortcut.makeDescriptor("k", WebInspector.KeyboardShortcut.Modifiers.Meta); + this._shortcuts[shortcutK.key] = this._requestClearMessages.bind(this); + } + + var shortcutL = shortcut.makeDescriptor("l", WebInspector.KeyboardShortcut.Modifiers.Ctrl); + this._shortcuts[shortcutL.key] = this._requestClearMessages.bind(this); + + var shortcutM = shortcut.makeDescriptor("m", WebInspector.KeyboardShortcut.Modifiers.CtrlOrMeta | WebInspector.KeyboardShortcut.Modifiers.Shift); + this._shortcuts[shortcutM.key] = this._dumpMemory.bind(this); + + var section = WebInspector.shortcutsScreen.section(WebInspector.UIString("Console")); + var keys = WebInspector.isMac() ? [ shortcutK.name, shortcutL.name ] : [ shortcutL.name ]; + section.addAlternateKeys(keys, WebInspector.UIString("Clear Console")); + + keys = [ + shortcut.shortcutToString(shortcut.Keys.Tab), + shortcut.shortcutToString(shortcut.Keys.Tab, shortcut.Modifiers.Shift) + ]; + section.addRelatedKeys(keys, WebInspector.UIString("Next/previous suggestion")); + section.addKey(shortcut.shortcutToString(shortcut.Keys.Right), WebInspector.UIString("Accept suggestion")); + keys = [ + shortcut.shortcutToString(shortcut.Keys.Down), + shortcut.shortcutToString(shortcut.Keys.Up) + ]; + section.addRelatedKeys(keys, WebInspector.UIString("Next/previous line")); + keys = [ + shortcut.shortcutToString("N", shortcut.Modifiers.Alt), + shortcut.shortcutToString("P", shortcut.Modifiers.Alt) + ]; + if (WebInspector.isMac()) + section.addRelatedKeys(keys, WebInspector.UIString("Next/previous command")); + section.addKey(shortcut.shortcutToString(shortcut.Keys.Enter), WebInspector.UIString("Execute command")); + }, + + _requestClearMessages: function() + { + WebInspector.console.requestClearMessages(); + }, + + _promptKeyDown: function(event) + { + if (isEnterKey(event)) { + this._enterKeyPressed(event); + return; + } + + var shortcut = WebInspector.KeyboardShortcut.makeKeyFromEvent(event); + var handler = this._shortcuts[shortcut]; + if (handler) { + handler(); + event.preventDefault(); + return; + } + }, + + /** + * @param {string} expression + * @param {string} objectGroup + * @param {boolean} includeCommandLineAPI + * @param {boolean} doNotPauseOnExceptions + * @param {boolean} returnByValue + * @param {function(?WebInspector.RemoteObject, boolean, RuntimeAgent.RemoteObject=)} callback + */ + evalInInspectedWindow: function(expression, objectGroup, includeCommandLineAPI, doNotPauseOnExceptions, returnByValue, callback) + { + if (WebInspector.debuggerPresentationModel.paused) { + WebInspector.debuggerPresentationModel.evaluateInSelectedCallFrame(expression, objectGroup, includeCommandLineAPI, returnByValue, callback); + return; + } + + if (!expression) { + // There is no expression, so the completion should happen against global properties. + expression = "this"; + } + + function evalCallback(error, result, wasThrown) + { + if (error) { + console.error(error); + callback(null, false); + return; + } + + if (returnByValue) + callback(null, wasThrown, wasThrown ? null : result); + else + callback(WebInspector.RemoteObject.fromPayload(result), wasThrown); + } + RuntimeAgent.evaluate(expression, objectGroup, includeCommandLineAPI, doNotPauseOnExceptions, this._currentEvaluationContextId(), returnByValue, evalCallback); + }, + + evaluateUsingTextPrompt: function(expression) + { + this._appendCommand(expression, this.prompt.text); + }, + + _enterKeyPressed: function(event) + { + if (event.altKey || event.ctrlKey || event.shiftKey) + return; + + event.preventDefault(); + event.stopPropagation(); + + this.prompt.clearAutoComplete(true); + + var str = this.prompt.text; + if (!str.length) + return; + this._appendCommand(str, ""); + }, + + _appendCommand: function(text, newPromptText) + { + var commandMessage = new WebInspector.ConsoleCommand(text); + WebInspector.console.interruptRepeatCount(); + this._appendConsoleMessage(commandMessage); + + function printResult(result, wasThrown) + { + if (!result) + return; + + this.prompt.pushHistoryItem(text); + this.prompt.text = newPromptText; + + WebInspector.settings.consoleHistory.set(this.prompt.historyData.slice(-30)); + + this._appendConsoleMessage(new WebInspector.ConsoleCommandResult(result, wasThrown, commandMessage, this._linkifier)); + } + this.evalInInspectedWindow(text, "console", true, false, false, printResult.bind(this)); + + WebInspector.userMetrics.ConsoleEvaluated.record(); + }, + + elementsToRestoreScrollPositionsFor: function() + { + return [this.messagesElement]; + }, + + _dumpMemory: function() + { + function comparator(a, b) + { + if (a.size < b.size) + return 1; + if (a.size > b.size) + return -1; + return a.title.localeCompare(b.title); + } + + function callback(error, groups) + { + var titles = []; + groups.sort(comparator); + for (var i = 0; i < groups.length; ++i) { + var suffix = groups[i].size > 0 ? " [" + groups[i].size + "]" : ""; + titles.push(groups[i].title + suffix + (groups[i].documentURI ? " (" + groups[i].documentURI + ")" : "")); + } + + var counter = 1; + var previousTitle = null; + for (var i = 0; i < titles.length; ++i) { + var title = titles[i]; + if (title === previousTitle) { + counter++; + continue; + } + if (previousTitle) + WebInspector.log(counter > 1 ? counter + " x " + previousTitle : previousTitle); + previousTitle = title; + counter = 1; + } + WebInspector.log(counter > 1 ? counter + " x " + previousTitle : previousTitle); + } + MemoryAgent.getDOMNodeCount(callback); + } +} + +WebInspector.ConsoleView.prototype.__proto__ = WebInspector.View.prototype; + +/** + * @constructor + */ +WebInspector.ConsoleCommand = function(command) +{ + this.command = command; +} + +WebInspector.ConsoleCommand.prototype = { + clearHighlight: function() + { + var highlightedMessage = this._formattedCommand; + delete this._formattedCommand; + this._formatCommand(); + this._element.replaceChild(this._formattedCommand, highlightedMessage); + }, + + highlightSearchResults: function(regexObject) + { + regexObject.lastIndex = 0; + var text = this.command; + var match = regexObject.exec(text); + var offset = 0; + var matchRanges = []; + while (match) { + matchRanges.push({ offset: match.index, length: match[0].length }); + match = regexObject.exec(text); + } + highlightSearchResults(this._formattedCommand, matchRanges); + this._element.scrollIntoViewIfNeeded(); + }, + + matchesRegex: function(regexObject) + { + return regexObject.test(this.command); + }, + + toMessageElement: function() + { + if (!this._element) { + this._element = document.createElement("div"); + this._element.command = this; + this._element.className = "console-user-command"; + + this._formatCommand(); + this._element.appendChild(this._formattedCommand); + } + return this._element; + }, + + _formatCommand: function() + { + this._formattedCommand = document.createElement("span"); + this._formattedCommand.className = "console-message-text source-code"; + this._formattedCommand.textContent = this.command; + }, +} + +/** + * @extends {WebInspector.ConsoleMessageImpl} + * @constructor + * @param {WebInspector.DebuggerPresentationModel.Linkifier} linkifier + */ +WebInspector.ConsoleCommandResult = function(result, wasThrown, originatingCommand, linkifier) +{ + var level = (wasThrown ? WebInspector.ConsoleMessage.MessageLevel.Error : WebInspector.ConsoleMessage.MessageLevel.Log); + this.originatingCommand = originatingCommand; + WebInspector.ConsoleMessageImpl.call(this, WebInspector.ConsoleMessage.MessageSource.JS, level, "", linkifier, WebInspector.ConsoleMessage.MessageType.Result, undefined, undefined, undefined, [result]); +} + +WebInspector.ConsoleCommandResult.prototype = { + toMessageElement: function() + { + var element = WebInspector.ConsoleMessageImpl.prototype.toMessageElement.call(this); + element.addStyleClass("console-user-command-result"); + return element; + } +} + +WebInspector.ConsoleCommandResult.prototype.__proto__ = WebInspector.ConsoleMessageImpl.prototype; + +/** + * @constructor + */ +WebInspector.ConsoleGroup = function(parentGroup) +{ + this.parentGroup = parentGroup; + + var element = document.createElement("div"); + element.className = "console-group"; + element.group = this; + this.element = element; + + if (parentGroup) { + var bracketElement = document.createElement("div"); + bracketElement.className = "console-group-bracket"; + element.appendChild(bracketElement); + } + + var messagesElement = document.createElement("div"); + messagesElement.className = "console-group-messages"; + element.appendChild(messagesElement); + this.messagesElement = messagesElement; +} + +WebInspector.ConsoleGroup.prototype = { + addMessage: function(msg) + { + var element = msg.toMessageElement(); + + if (msg.type === WebInspector.ConsoleMessage.MessageType.StartGroup || msg.type === WebInspector.ConsoleMessage.MessageType.StartGroupCollapsed) { + this.messagesElement.parentNode.insertBefore(element, this.messagesElement); + 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"); + } else + this.messagesElement.appendChild(element); + + if (element.previousSibling && msg.originatingCommand && element.previousSibling.command === msg.originatingCommand) + element.previousSibling.addStyleClass("console-adjacent-user-command-result"); + }, + + _titleClicked: function(event) + { + var groupTitleElement = event.target.enclosingNodeOrSelfWithClass("console-group-title"); + if (groupTitleElement) { + var groupElement = groupTitleElement.enclosingNodeOrSelfWithClass("console-group"); + if (groupElement) + if (groupElement.hasStyleClass("collapsed")) + groupElement.removeStyleClass("collapsed"); + else + groupElement.addStyleClass("collapsed"); + groupTitleElement.scrollIntoViewIfNeeded(true); + } + + event.stopPropagation(); + event.preventDefault(); + } +} + +/** + * @type {?WebInspector.ConsoleView} + */ +WebInspector.consoleView = null; + +WebInspector.ConsoleMessage.create = function(source, level, message, type, url, line, repeatCount, parameters, stackTrace, request) +{ + return new WebInspector.ConsoleMessageImpl(source, level, message, WebInspector.consoleView._linkifier, type, url, line, repeatCount, parameters, stackTrace, request); +} +/* Panel.js */ + +/* + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @extends {WebInspector.View} + * @constructor + */ +WebInspector.Panel = function(name) +{ + WebInspector.View.call(this); + + this.element.addStyleClass("panel"); + this.element.addStyleClass(name); + this._panelName = name; + + this._shortcuts = {}; + + WebInspector.settings[this._sidebarWidthSettingName()] = WebInspector.settings.createSetting(this._sidebarWidthSettingName(), undefined); +} + +// Should by in sync with style declarations. +WebInspector.Panel.counterRightMargin = 25; + +WebInspector.Panel.prototype = { + get toolbarItem() + { + if (this._toolbarItem) + return this._toolbarItem; + + this._toolbarItem = WebInspector.Toolbar.createPanelToolbarItem(this); + return this._toolbarItem; + }, + + get name() + { + return this._panelName; + }, + + show: function() + { + WebInspector.View.prototype.show.call(this, WebInspector.inspectorView.element); + }, + + wasShown: function() + { + var statusBarItems = this.statusBarItems; + if (statusBarItems) { + this._statusBarItemContainer = document.createElement("div"); + for (var i = 0; i < statusBarItems.length; ++i) + this._statusBarItemContainer.appendChild(statusBarItems[i]); + document.getElementById("main-status-bar").appendChild(this._statusBarItemContainer); + } + + if ("_toolbarItem" in this) + this._toolbarItem.addStyleClass("toggled-on"); + + WebInspector.setCurrentFocusElement(this.defaultFocusedElement); + }, + + willHide: function() + { + if (this._statusBarItemContainer && this._statusBarItemContainer.parentNode) + this._statusBarItemContainer.parentNode.removeChild(this._statusBarItemContainer); + delete this._statusBarItemContainer; + if ("_toolbarItem" in this) + this._toolbarItem.removeStyleClass("toggled-on"); + }, + + reset: function() + { + this.searchCanceled(); + }, + + get defaultFocusedElement() + { + return this.sidebarTreeElement || this.element; + }, + + searchCanceled: function() + { + WebInspector.searchController.updateSearchMatchesCount(0, this); + }, + + performSearch: function(query) + { + // Call searchCanceled since it will reset everything we need before doing a new search. + this.searchCanceled(); + }, + + jumpToNextSearchResult: function() + { + }, + + jumpToPreviousSearchResult: function() + { + }, + + /** + * @param {Element=} parentElement + * @param {string=} position + * @param {number=} defaultWidth + */ + createSplitView: function(parentElement, position, defaultWidth) + { + if (this.splitView) + return; + + if (!parentElement) + parentElement = this.element; + + this.splitView = new WebInspector.SplitView(position || WebInspector.SplitView.SidebarPosition.Left, this._sidebarWidthSettingName(), defaultWidth); + this.splitView.show(parentElement); + this.splitView.addEventListener(WebInspector.SplitView.EventTypes.Resized, this.sidebarResized.bind(this)); + + this.sidebarElement = this.splitView.sidebarElement; + }, + + /** + * @param {Element=} parentElement + * @param {string=} position + * @param {number=} defaultWidth + */ + createSplitViewWithSidebarTree: function(parentElement, position, defaultWidth) + { + if (this.splitView) + return; + + this.createSplitView(parentElement, position); + + this.sidebarTreeElement = document.createElement("ol"); + this.sidebarTreeElement.className = "sidebar-tree"; + this.splitView.sidebarElement.appendChild(this.sidebarTreeElement); + this.splitView.sidebarElement.addStyleClass("sidebar"); + + this.sidebarTree = new TreeOutline(this.sidebarTreeElement); + this.sidebarTree.panel = this; + }, + + _sidebarWidthSettingName: function() + { + return this._panelName + "SidebarWidth"; + }, + + // Should be implemented by ancestors. + + get toolbarItemLabel() + { + }, + + get statusBarItems() + { + }, + + sidebarResized: function(width) + { + }, + + statusBarResized: function() + { + }, + + canShowAnchorLocation: function(anchor) + { + return false; + }, + + showAnchorLocation: function(anchor) + { + return false; + }, + + elementsToRestoreScrollPositionsFor: function() + { + return []; + }, + + handleShortcut: function(event) + { + var shortcutKey = WebInspector.KeyboardShortcut.makeKeyFromEvent(event); + var handler = this._shortcuts[shortcutKey]; + if (handler) { + handler(event); + event.handled = true; + } + }, + + registerShortcut: function(key, handler) + { + this._shortcuts[key] = handler; + } +} + +WebInspector.Panel.prototype.__proto__ = WebInspector.View.prototype; +/* InspectorView.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.View} + */ +WebInspector.InspectorView = function() +{ + WebInspector.View.call(this); + this.markAsRoot(); + this.element.id = "main-panels"; + this.element.setAttribute("spellcheck", false); + this._history = []; + this._historyIterator = -1; + document.addEventListener("keydown", this._keyDown.bind(this), false); + this._panelOrder = []; +} + +WebInspector.InspectorView.Events = { + PanelSelected: "panel-selected" +} + +WebInspector.InspectorView.prototype = { + addPanel: function(panel) + { + this._panelOrder.push(panel); + WebInspector.toolbar.addPanel(panel); + }, + + currentPanel: function() + { + return this._currentPanel; + }, + + setCurrentPanel: function(x) + { + if (this._currentPanel === x) + return; + + if (this._currentPanel) + this._currentPanel.detach(); + + this._currentPanel = x; + + if (x) { + x.show(); + this.dispatchEventToListeners(WebInspector.InspectorView.Events.PanelSelected); + // FIXME: remove search controller. + WebInspector.searchController.activePanelChanged(); + } + for (var panelName in WebInspector.panels) { + if (WebInspector.panels[panelName] === x) { + WebInspector.settings.lastActivePanel.set(panelName); + this._pushToHistory(panelName); + WebInspector.userMetrics.panelShown(panelName); + } + } + }, + + _keyDown: function(event) + { + switch (event.keyIdentifier) { + case "Left": + var isBackKey = !event.shiftKey && WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(event) && !WebInspector.isInEditMode(event); + if (isBackKey && this._canGoBackInHistory()) { + this._goBackInHistory(); + event.preventDefault(); + } + break; + + case "Right": + var isForwardKey = !event.shiftKey && WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(event) && !WebInspector.isInEditMode(event); + if (isForwardKey && this._canGoForwardInHistory()) { + this._goForwardInHistory(); + event.preventDefault(); + } + break; + + // Windows and Mac have two different definitions of [, so accept both. + case "U+005B": + case "U+00DB": // [ key + var isRotateLeft = WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(event) && !event.shiftKey && !event.altKey; + if (isRotateLeft) { + var index = this._panelOrder.indexOf(this.currentPanel()); + index = (index === 0) ? this._panelOrder.length - 1 : index - 1; + this._panelOrder[index].toolbarItem.click(); + event.preventDefault(); + } + break; + + // Windows and Mac have two different definitions of ], so accept both. + case "U+005D": + case "U+00DD": // ] key + var isRotateRight = WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(event) && !event.shiftKey && !event.altKey; + if (isRotateRight) { + var index = this._panelOrder.indexOf(this.currentPanel()); + index = (index + 1) % this._panelOrder.length; + this._panelOrder[index].toolbarItem.click(); + event.preventDefault(); + } + + break; + } + }, + + _canGoBackInHistory: function() + { + return this._historyIterator > 0; + }, + + _goBackInHistory: function() + { + this._inHistory = true; + this.setCurrentPanel(WebInspector.panels[this._history[--this._historyIterator]]); + delete this._inHistory; + }, + + _canGoForwardInHistory: function() + { + return this._historyIterator < this._history.length - 1; + }, + + _goForwardInHistory: function() + { + this._inHistory = true; + this.setCurrentPanel(WebInspector.panels[this._history[++this._historyIterator]]); + delete this._inHistory; + }, + + _pushToHistory: function(panelName) + { + if (this._inHistory) + return; + + this._history.splice(this._historyIterator + 1, this._history.length - this._historyIterator - 1); + if (!this._history.length || this._history[this._history.length - 1] !== panelName) + this._history.push(panelName); + this._historyIterator = this._history.length - 1; + } +} + +WebInspector.InspectorView.prototype.__proto__ = WebInspector.View.prototype; + +/** + * @type {WebInspector.InspectorView} + */ +WebInspector.inspectorView = null; +/* AdvancedSearchController.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY GOOGLE INC. AND ITS CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE INC. + * OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + */ +WebInspector.AdvancedSearchController = function() +{ + this._shortcut = WebInspector.AdvancedSearchController.createShortcut(); + this._searchId = 0; + + WebInspector.settings.advancedSearchConfig = WebInspector.settings.createSetting("advancedSearchConfig", new WebInspector.SearchConfig("", true, false)); + + WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameNavigated, this._frameNavigated, this); +} + +WebInspector.AdvancedSearchController.createShortcut = function() +{ + if (WebInspector.isMac()) + return WebInspector.KeyboardShortcut.makeDescriptor("f", WebInspector.KeyboardShortcut.Modifiers.Meta | WebInspector.KeyboardShortcut.Modifiers.Alt); + else + return WebInspector.KeyboardShortcut.makeDescriptor("f", WebInspector.KeyboardShortcut.Modifiers.Ctrl | WebInspector.KeyboardShortcut.Modifiers.Shift); +} + +WebInspector.AdvancedSearchController.prototype = { + /** + * @param {Event} event + */ + handleShortcut: function(event) + { + if (WebInspector.KeyboardShortcut.makeKeyFromEvent(event) === this._shortcut.key) { + this.show(); + event.handled = true; + } + }, + + _frameNavigated: function() + { + this.resetSearch(); + }, + + /** + * @param {WebInspector.SearchScope} searchScope + */ + registerSearchScope: function(searchScope) + { + // FIXME: implement multiple search scopes. + this._searchScope = searchScope; + }, + + show: function() + { + if (!this._searchView) + this._searchView = new WebInspector.SearchView(this); + + if (this._searchView.isShowing()) + this._searchView.focus(); + else + WebInspector.showViewInDrawer(this._searchView); + }, + + /** + * @param {number} searchId + * @param {Object} searchResult + */ + _onSearchResult: function(searchId, searchResult) + { + if (searchId !== this._searchId) + return; + + this._searchView.addSearchResult(searchResult); + if (!searchResult.searchMatches.length) + return; + + if (!this._searchResultsPane) + this._searchResultsPane = this._currentSearchScope.createSearchResultsPane(this._searchConfig); + this._searchView.resultsPane = this._searchResultsPane; + this._searchResultsPane.addSearchResult(searchResult); + }, + + /** + * @param {number} searchId + * @param {boolean} finished + */ + _onSearchFinished: function(searchId, finished) + { + if (searchId !== this._searchId) + return; + + if (!this._searchResultsPane) + this._searchView.nothingFound(); + + this._searchView.searchFinished(finished); + }, + + /** + * @param {WebInspector.SearchConfig} searchConfig + */ + startSearch: function(searchConfig) + { + this.resetSearch(); + ++this._searchId; + + this._searchConfig = searchConfig; + // FIXME: this._currentSearchScope should be initialized based on searchConfig + this._currentSearchScope = this._searchScope; + + var totalSearchResultsCount = this._currentSearchScope.performSearch(searchConfig, this._onSearchResult.bind(this, this._searchId), this._onSearchFinished.bind(this, this._searchId)); + this._searchView.searchStarted(totalSearchResultsCount); + }, + + resetSearch: function() + { + this.stopSearch(); + + if (this._searchResultsPane) { + this._searchView.resetResults(); + delete this._searchResultsPane; + } + }, + + stopSearch: function() + { + if (this._currentSearchScope) + this._currentSearchScope.stopSearch(); + } +} + +/** + * @constructor + * @extends {WebInspector.View} + * @param {WebInspector.AdvancedSearchController} controller + */ +WebInspector.SearchView = function(controller) +{ + WebInspector.View.call(this); + this.registerRequiredCSS("textViewer.css"); + + this._controller = controller; + + this.element.className = "search-view"; + + this._searchPanelElement = this.element.createChild("div"); + this._searchPanelElement.tabIndex = 0; + this._searchPanelElement.className = "search-panel"; + this._searchPanelElement.addEventListener("keydown", this._onKeyDown.bind(this), false); + + this._searchResultsElement = this.element.createChild("div"); + this._searchResultsElement.className = "search-results"; + + this._search = this._searchPanelElement.createChild("input"); + this._search.setAttribute("type", "search"); + this._search.addStyleClass("search-config-search"); + this._search.setAttribute("results", "0"); + this._search.setAttribute("size", 20); + + this._ignoreCaseLabel = this._searchPanelElement.createChild("label"); + this._ignoreCaseLabel.addStyleClass("search-config-label"); + this._ignoreCaseCheckbox = this._ignoreCaseLabel.createChild("input"); + this._ignoreCaseCheckbox.setAttribute("type", "checkbox"); + this._ignoreCaseCheckbox.addStyleClass("search-config-checkbox"); + this._ignoreCaseLabel.appendChild(document.createTextNode(WebInspector.UIString("Ignore case"))); + + this._regexLabel = this._searchPanelElement.createChild("label"); + this._regexLabel.addStyleClass("search-config-label"); + this._regexCheckbox = this._regexLabel.createChild("input"); + this._regexCheckbox.setAttribute("type", "checkbox"); + this._regexCheckbox.addStyleClass("search-config-checkbox"); + this._regexLabel.appendChild(document.createTextNode(WebInspector.UIString("Regular expression"))); + + this._searchStatusBarElement = document.createElement("div"); + this._searchStatusBarElement.className = "search-status-bar-item"; + this._searchMessageElement = this._searchStatusBarElement.createChild("div"); + this._searchMessageElement.className = "search-status-bar-message"; + this._searchProgressElement = document.createElement("progress"); + this._searchProgressElement.className = "search-status-bar-progress"; + + this._searchStopButtonItem = document.createElement("div"); + this._searchStopButtonItem.className = "search-status-bar-stop-button-item"; + this._searchStopStatusBarButton = new WebInspector.StatusBarButton(WebInspector.UIString("Stop search"), "search-status-bar-stop-button"); + this._searchStopButtonItem.appendChild(this._searchStopStatusBarButton.element); + this._searchStopStatusBarButton.addEventListener("click", this._searchStopButtonPressed, this); + + this._searchResultsMessageElement = document.createElement("span"); + this._searchResultsMessageElement.className = "search-results-status-bar-message"; + + this._load(); +} + +// Number of recent search queries to store. +WebInspector.SearchView.maxQueriesCount = 20; + +WebInspector.SearchView.prototype = { + /** + * @type {Array.} + */ + get statusBarItems() + { + return [this._searchStatusBarElement]; + }, + + /** + * @type {Element} + */ + get counterElement() + { + return this._searchResultsMessageElement; + }, + + /** + * @type {WebInspector.SearchConfig} + */ + get searchConfig() + { + var searchConfig = {}; + searchConfig.query = this._search.value; + searchConfig.ignoreCase = this._ignoreCaseCheckbox.checked; + searchConfig.isRegex = this._regexCheckbox.checked; + return searchConfig; + }, + + /** + * @type {WebInspector.SearchResultsPane} + */ + set resultsPane(resultsPane) + { + this.resetResults(); + this._searchResultsElement.appendChild(resultsPane.element); + }, + + /** + * @param {number} totalSearchResultsCount + */ + searchStarted: function(totalSearchResultsCount) + { + this.resetResults(); + this._resetCounters(); + + this._totalSearchResultsCount = totalSearchResultsCount; + + this._searchMessageElement.textContent = WebInspector.UIString("Searching..."); + this._searchStatusBarElement.appendChild(this._searchProgressElement); + this._searchStatusBarElement.appendChild(this._searchStopButtonItem); + this._updateSearchProgress(); + + this._updateSearchResultsMessage(); + + if (!this._searchingView) + this._searchingView = new WebInspector.EmptyView(WebInspector.UIString("Searching...")); + this._searchingView.show(this._searchResultsElement); + }, + + _updateSearchResultsMessage: function() + { + if (this._searchMatchesCount && this._searchResultsCount) + this._searchResultsMessageElement.textContent = WebInspector.UIString("Found %d matches in %d files.", this._searchMatchesCount, this._nonEmptySearchResultsCount); + else + this._searchResultsMessageElement.textContent = ""; + }, + + _updateSearchProgress: function() + { + this._searchProgressElement.setAttribute("max", this._totalSearchResultsCount); + this._searchProgressElement.setAttribute("value", this._searchResultsCount); + }, + + resetResults: function() + { + if (this._searchingView) + this._searchingView.detach(); + if (this._notFoundView) + this._notFoundView.detach(); + this._searchResultsElement.removeChildren(); + }, + + _resetCounters: function() + { + this._searchMatchesCount = 0; + this._searchResultsCount = 0; + this._nonEmptySearchResultsCount = 0; + }, + + nothingFound: function() + { + this.resetResults(); + + if (!this._notFoundView) + this._notFoundView = new WebInspector.EmptyView(WebInspector.UIString("No matches found.")); + this._notFoundView.show(this._searchResultsElement); + this._searchResultsMessageElement.textContent = WebInspector.UIString("No matches found."); + }, + + /** + * @param {Object} searchResult + */ + addSearchResult: function(searchResult) + { + this._searchMatchesCount += searchResult.searchMatches.length; + this._searchResultsCount++; + if (searchResult.searchMatches.length) + this._nonEmptySearchResultsCount++; + this._updateSearchResultsMessage(); + this._updateSearchProgress(); + }, + + /** + * @param {boolean} finished + */ + searchFinished: function(finished) + { + this._searchMessageElement.textContent = finished ? WebInspector.UIString("Search finished.") : WebInspector.UIString("Search interrupted."); + this._searchStatusBarElement.removeChild(this._searchProgressElement); + this._searchStatusBarElement.removeChild(this._searchStopButtonItem); + }, + + focus: function() + { + WebInspector.setCurrentFocusElement(this._search); + this._search.select(); + }, + + wasShown: function() + { + this.focus(); + }, + + wasHidden: function() + { + this._controller.stopSearch(); + }, + + /** + * @param {Event} event + */ + _onKeyDown: function(event) + { + if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Enter.code) + this._onAction(); + }, + + _save: function() + { + var searchConfig = new WebInspector.SearchConfig(this.searchConfig.query, this.searchConfig.ignoreCase, this.searchConfig.isRegex); + WebInspector.settings.advancedSearchConfig.set(searchConfig); + }, + + _load: function() + { + var searchConfig = WebInspector.settings.advancedSearchConfig.get(); + this._search.value = searchConfig.query; + this._ignoreCaseCheckbox.checked = searchConfig.ignoreCase; + this._regexCheckbox.checked = searchConfig.isRegex; + }, + + _searchStopButtonPressed: function() + { + this._controller.stopSearch(); + this.focus(); + }, + + _onAction: function() + { + if (!this.searchConfig.query || !this.searchConfig.query.length) + return; + + this._save(); + this._controller.startSearch(this.searchConfig); + } +} + +WebInspector.SearchView.prototype.__proto__ = WebInspector.View.prototype; + +/** + * @constructor + * @param {string} query + * @param {boolean} ignoreCase + * @param {boolean} isRegex + */ +WebInspector.SearchConfig = function(query, ignoreCase, isRegex) +{ + this.query = query; + this.ignoreCase = ignoreCase; + this.isRegex = isRegex; +} + +/** + * @interface + */ +WebInspector.SearchScope = function() +{ +} + +WebInspector.SearchScope.prototype = { + /** + * @param {WebInspector.SearchConfig} searchConfig + * @param {function(Object)} searchResultCallback + * @param {function(boolean)} searchFinishedCallback + */ + performSearch: function(searchConfig, searchResultCallback, searchFinishedCallback) { }, + + stopSearch: function() { }, + + /** + * @param {WebInspector.SearchConfig} searchConfig + * @return WebInspector.SearchResultsPane} + */ + createSearchResultsPane: function(searchConfig) { } +} + +/** + * @constructor + * @param {WebInspector.SearchConfig} searchConfig + */ +WebInspector.SearchResultsPane = function(searchConfig) +{ + this._searchConfig = searchConfig; + this.element = document.createElement("div"); +} + +WebInspector.SearchResultsPane.prototype = { + /** + * @type {WebInspector.SearchConfig} + */ + get searchConfig() + { + return this._searchConfig; + }, + + /** + * @param {Object} searchResult + */ + addSearchResult: function(searchResult) { } +} + +/** + * @constructor + * @extends {WebInspector.SearchResultsPane} + * @param {WebInspector.SearchConfig} searchConfig + */ +WebInspector.FileBasedSearchResultsPane = function(searchConfig) +{ + WebInspector.SearchResultsPane.call(this, searchConfig); + + this._searchResults = []; + + this.element.id ="search-results-pane-file-based"; + + this._treeOutlineElement = document.createElement("ol"); + this._treeOutlineElement.className = "outline-disclosure"; + this._treeOutlineElement.addStyleClass("search-results-outline-disclosure"); + this.element.appendChild(this._treeOutlineElement); + this._treeOutline = new TreeOutline(this._treeOutlineElement); + + this._matchesExpandedCount = 0; +} + +WebInspector.FileBasedSearchResultsPane.matchesExpandedByDefaultCount = 20; +WebInspector.FileBasedSearchResultsPane.fileMatchesShownAtOnce = 20; + +WebInspector.FileBasedSearchResultsPane.prototype = { + /** + * @param {Object} file + * @param {number} lineNumber + * @param {number} columnNumber + * @return {Element} + */ + createAnchor: function(file, lineNumber, columnNumber) { }, + + /** + * @param {Object} file + * @return {string} + */ + fileName: function(file) { }, + + /** + * @param {Object} searchResult + */ + addSearchResult: function(searchResult) + { + this._searchResults.push(searchResult); + var file = searchResult.file; + var fileName = this.fileName(file); + var searchMatches = searchResult.searchMatches; + + var fileTreeElement = this._addFileTreeElement(fileName, searchMatches.length, this._searchResults.length - 1); + }, + + /** + * @param {Object} searchResult + * @param {TreeElement} fileTreeElement + */ + _fileTreeElementExpanded: function(searchResult, fileTreeElement) + { + if (fileTreeElement._initialized) + return; + + var toIndex = Math.min(searchResult.searchMatches.length, WebInspector.FileBasedSearchResultsPane.fileMatchesShownAtOnce); + if (toIndex < searchResult.searchMatches.length) { + this._appendSearchMatches(fileTreeElement, searchResult, 0, toIndex - 1); + this._appendShowMoreMatchesElement(fileTreeElement, searchResult, toIndex - 1); + } else + this._appendSearchMatches(fileTreeElement, searchResult, 0, toIndex); + + fileTreeElement._initialized = true; + }, + + /** + * @param {TreeElement} fileTreeElement + * @param {Object} searchResult + * @param {number} fromIndex + * @param {number} toIndex + */ + _appendSearchMatches: function(fileTreeElement, searchResult, fromIndex, toIndex) + { + var file = searchResult.file; + var fileName = this.fileName(file); + var searchMatches = searchResult.searchMatches; + + var regex = createSearchRegex(this._searchConfig.query, !this._searchConfig.ignoreCase, this._searchConfig.isRegex); + for (var i = fromIndex; i < toIndex; ++i) { + var lineNumber = searchMatches[i].lineNumber; + var lineContent = searchMatches[i].lineContent; + var matchRanges = this._regexMatchRanges(lineContent, regex); + + var anchor = this.createAnchor(file, lineNumber, matchRanges[0].offset); + + var numberString = numberToStringWithSpacesPadding(lineNumber + 1, 4); + var lineNumberSpan = document.createElement("span"); + lineNumberSpan.addStyleClass("webkit-line-number"); + lineNumberSpan.addStyleClass("search-match-line-number"); + lineNumberSpan.textContent = numberString; + anchor.appendChild(lineNumberSpan); + + var contentSpan = this._createContentSpan(lineContent, matchRanges); + anchor.appendChild(contentSpan); + + var searchMatchElement = new TreeElement("", null, false); + fileTreeElement.appendChild(searchMatchElement); + searchMatchElement.listItemElement.className = "search-match"; + searchMatchElement.listItemElement.appendChild(anchor); + } + }, + + /** + * @param {TreeElement} fileTreeElement + * @param {Object} searchResult + * @param {number} startMatchIndex + */ + _appendShowMoreMatchesElement: function(fileTreeElement, searchResult, startMatchIndex) + { + var matchesLeftCount = searchResult.searchMatches.length - startMatchIndex; + var showMoreMatchesText = WebInspector.UIString("Show all matches (%d more).", matchesLeftCount); + var showMoreMatchesElement = new TreeElement(showMoreMatchesText, null, false); + fileTreeElement.appendChild(showMoreMatchesElement); + showMoreMatchesElement.listItemElement.addStyleClass("show-more-matches"); + showMoreMatchesElement.onselect = this._showMoreMatchesElementSelected.bind(this, searchResult, startMatchIndex); + }, + + /** + * @param {Object} searchResult + * @param {number} startMatchIndex + * @param {TreeElement} showMoreMatchesElement + */ + _showMoreMatchesElementSelected: function(searchResult, startMatchIndex, showMoreMatchesElement) + { + var fileTreeElement = showMoreMatchesElement.parent; + fileTreeElement.removeChild(showMoreMatchesElement); + this._appendSearchMatches(fileTreeElement, searchResult, startMatchIndex, searchResult.searchMatches.length); + }, + + /** + * @param {string} fileName + * @param {number} searchMatchesCount + * @param {number} searchResultIndex + */ + _addFileTreeElement: function(fileName, searchMatchesCount, searchResultIndex) + { + var fileTreeElement = new TreeElement("", null, true); + fileTreeElement.toggleOnClick = true; + fileTreeElement.selectable = false; + + this._treeOutline.appendChild(fileTreeElement); + fileTreeElement.listItemElement.addStyleClass("search-result"); + + var fileNameSpan = document.createElement("span"); + fileNameSpan.className = "search-result-file-name"; + fileNameSpan.textContent = fileName; + fileTreeElement.listItemElement.appendChild(fileNameSpan); + + var matchesCountSpan = document.createElement("span"); + matchesCountSpan.className = "search-result-matches-count"; + if (searchMatchesCount === 1) + matchesCountSpan.textContent = WebInspector.UIString("(%d match)", searchMatchesCount); + else + matchesCountSpan.textContent = WebInspector.UIString("(%d matches)", searchMatchesCount); + + fileTreeElement.listItemElement.appendChild(matchesCountSpan); + + var searchResult = this._searchResults[searchResultIndex]; + fileTreeElement.onexpand = this._fileTreeElementExpanded.bind(this, searchResult); + + // Expand until at least certain amount of matches is expanded. + if (this._matchesExpandedCount < WebInspector.FileBasedSearchResultsPane.matchesExpandedByDefaultCount) + fileTreeElement.expand(); + this._matchesExpandedCount += searchResult.searchMatches.length; + + return fileTreeElement; + }, + + /** + * @param {string} lineContent + * @param {RegExp} regex + * @return {Array.} + */ + _regexMatchRanges: function(lineContent, regex) + { + regex.lastIndex = 0; + var match; + var offset = 0; + var matchRanges = []; + while ((regex.lastIndex < lineContent.length) && (match = regex.exec(lineContent))) + matchRanges.push({ offset: match.index, length: match[0].length }); + + return matchRanges; + }, + + /** + * @param {string} lineContent + * @param {Array.} matchRanges + */ + _createContentSpan: function(lineContent, matchRanges) + { + var contentSpan = document.createElement("span"); + contentSpan.className = "search-match-content"; + contentSpan.textContent = lineContent; + highlightRangesWithStyleClass(contentSpan, matchRanges, "highlighted-match"); + return contentSpan; + } +} + +WebInspector.FileBasedSearchResultsPane.prototype.__proto__ = WebInspector.SearchResultsPane.prototype; + +/** + * @constructor + * @param {Object} file + * @param {Array.} searchMatches + */ +WebInspector.FileBasedSearchResultsPane.SearchResult = function(file, searchMatches) { + this.file = file; + this.searchMatches = searchMatches; +} + +/** + * @type {WebInspector.AdvancedSearchController} + */ +WebInspector.advancedSearchController = null;/* TimelineGrid.js */ + +/* + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2008, 2009 Anthony Ricaud + * Copyright (C) 2009 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + */ +WebInspector.TimelineGrid = function() +{ + this.element = document.createElement("div"); + + this._itemsGraphsElement = document.createElement("div"); + this._itemsGraphsElement.id = "resources-graphs"; + this.element.appendChild(this._itemsGraphsElement); + + this._dividersElement = document.createElement("div"); + this._dividersElement.className = "resources-dividers"; + this.element.appendChild(this._dividersElement); + + this._eventDividersElement = document.createElement("div"); + this._eventDividersElement.className = "resources-event-dividers"; + this.element.appendChild(this._eventDividersElement); + + this._dividersLabelBarElement = document.createElement("div"); + this._dividersLabelBarElement.className = "resources-dividers-label-bar"; + this.element.appendChild(this._dividersLabelBarElement); +} + +WebInspector.TimelineGrid.prototype = { + get itemsGraphsElement() + { + return this._itemsGraphsElement; + }, + + /** + * @param {number=} paddingLeft + */ + updateDividers: function(force, calculator, paddingLeft) + { + var dividerCount = Math.round(this._dividersElement.offsetWidth / 64); + var slice = calculator.boundarySpan / dividerCount; + if (!force && this._currentDividerSlice === slice) + return false; + + if (typeof paddingLeft !== "number") + paddingLeft = 0; + this._currentDividerSlice = slice; + + // Reuse divider elements and labels. + var divider = this._dividersElement.firstChild; + var dividerLabelBar = this._dividersLabelBarElement.firstChild; + + var dividersLabelBarElementClientWidth = this._dividersLabelBarElement.clientWidth; + var clientWidth = dividersLabelBarElementClientWidth - paddingLeft; + for (var i = paddingLeft ? 0 : 1; i <= dividerCount; ++i) { + if (!divider) { + divider = document.createElement("div"); + divider.className = "resources-divider"; + this._dividersElement.appendChild(divider); + + dividerLabelBar = document.createElement("div"); + dividerLabelBar.className = "resources-divider"; + var label = document.createElement("div"); + label.className = "resources-divider-label"; + dividerLabelBar._labelElement = label; + dividerLabelBar.appendChild(label); + this._dividersLabelBarElement.appendChild(dividerLabelBar); + dividersLabelBarElementClientWidth = this._dividersLabelBarElement.clientWidth; + } + + if (i === (paddingLeft ? 0 : 1)) { + divider.addStyleClass("first"); + dividerLabelBar.addStyleClass("first"); + } else { + divider.removeStyleClass("first"); + dividerLabelBar.removeStyleClass("first"); + } + + if (i === dividerCount) { + divider.addStyleClass("last"); + dividerLabelBar.addStyleClass("last"); + } else { + divider.removeStyleClass("last"); + dividerLabelBar.removeStyleClass("last"); + } + + var left = paddingLeft + clientWidth * (i / dividerCount); + var percentLeft = 100 * left / dividersLabelBarElementClientWidth; + this._setDividerAndBarLeft(divider, dividerLabelBar, percentLeft); + + if (!isNaN(slice)) + dividerLabelBar._labelElement.textContent = calculator.formatValue(slice * i); + else + dividerLabelBar._labelElement.textContent = ""; + + divider = divider.nextSibling; + dividerLabelBar = dividerLabelBar.nextSibling; + } + + // Remove extras. + while (divider) { + var nextDivider = divider.nextSibling; + this._dividersElement.removeChild(divider); + divider = nextDivider; + } + while (dividerLabelBar) { + var nextDivider = dividerLabelBar.nextSibling; + this._dividersLabelBarElement.removeChild(dividerLabelBar); + dividerLabelBar = nextDivider; + } + return true; + }, + + _setDividerAndBarLeft: function(divider, dividerLabelBar, percentLeft) + { + var percentStyleLeft = parseFloat(divider.style.left); + if (!isNaN(percentStyleLeft) && Math.abs(percentStyleLeft - percentLeft) < 0.1) + return; + divider.style.left = percentLeft + "%"; + dividerLabelBar.style.left = percentLeft + "%"; + }, + + addEventDivider: function(divider) + { + this._eventDividersElement.appendChild(divider); + }, + + addEventDividers: function(dividers) + { + this.element.removeChild(this._eventDividersElement); + for (var i = 0; i < dividers.length; ++i) + if (dividers[i]) + this._eventDividersElement.appendChild(dividers[i]); + this.element.appendChild(this._eventDividersElement); + }, + + removeEventDividers: function() + { + this._eventDividersElement.removeChildren(); + }, + + hideEventDividers: function() + { + this._eventDividersElement.addStyleClass("hidden"); + }, + + showEventDividers: function() + { + this._eventDividersElement.removeStyleClass("hidden"); + }, + + setScrollAndDividerTop: function(scrollTop, dividersTop) + { + this._dividersElement.style.top = scrollTop + "px"; + this._eventDividersElement.style.top = scrollTop + "px"; + this._dividersLabelBarElement.style.top = dividersTop + "px"; + } +} +/* Resource.js */ + +/* + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// This table maps MIME types to the Resource.Types which are valid for them. +// The following line: +// "text/html": {0: 1}, +// means that text/html is a valid MIME type for resources that have type +// WebInspector.Resource.Type.Document (which has a value of 0). +WebInspector.MIMETypes = { + "text/html": {0: true}, + "text/xml": {0: true}, + "text/plain": {0: true}, + "application/xhtml+xml": {0: true}, + "text/css": {1: true}, + "text/xsl": {1: true}, + "image/jpeg": {2: true}, + "image/png": {2: true}, + "image/gif": {2: true}, + "image/bmp": {2: true}, + "image/svg+xml": {2: true}, + "image/vnd.microsoft.icon": {2: true}, + "image/webp": {2: true}, + "image/x-icon": {2: true}, + "image/x-xbitmap": {2: true}, + "font/ttf": {3: true}, + "font/opentype": {3: true}, + "application/x-font-type1": {3: true}, + "application/x-font-ttf": {3: true}, + "application/x-font-woff": {3: true}, + "application/x-truetype-font": {3: true}, + "text/javascript": {4: true}, + "text/ecmascript": {4: true}, + "application/javascript": {4: true}, + "application/ecmascript": {4: true}, + "application/x-javascript": {4: true}, + "application/json": {4: true}, + "text/javascript1.1": {4: true}, + "text/javascript1.2": {4: true}, + "text/javascript1.3": {4: true}, + "text/jscript": {4: true}, + "text/livescript": {4: true}, +} + +/** + * @constructor + * @extends {WebInspector.Object} + * + * @param {NetworkAgent.RequestId} requestId + * @param {string} url + * @param {string} frameId + * @param {?NetworkAgent.LoaderId} loaderId + */ +WebInspector.Resource = function(requestId, url, frameId, loaderId) +{ + this.requestId = requestId; + this.url = url; + this.frameId = frameId; + this.loaderId = loaderId; + this._startTime = -1; + this._endTime = -1; + this._category = WebInspector.resourceCategories.other; + this._pendingContentCallbacks = []; + this.history = []; + /** @type {number} */ + this.statusCode = 0; + this.statusText = ""; + this.requestMethod = ""; + this.requestTime = 0; + this.receiveHeadersEnd = 0; +} + +/** + * @param {string} url + * @return {string} + */ +WebInspector.Resource.displayName = function(url) +{ + return new WebInspector.Resource("fake-transient-resource", url, "", null).displayName; +} + +// Keep these in sync with WebCore::InspectorResource::Type +WebInspector.Resource.Type = { + Document: 0, + Stylesheet: 1, + Image: 2, + Font: 3, + Script: 4, + XHR: 5, + WebSocket: 7, + Other: 8, + + /** + * @param {number} type + * @return {boolean} + */ + isTextType: function(type) + { + return (type === WebInspector.Resource.Type.Document) || (type === WebInspector.Resource.Type.Stylesheet) || (type === WebInspector.Resource.Type.Script) || (type === WebInspector.Resource.Type.XHR); + }, + + /** + * @param {number} type + * @return {string} + */ + toUIString: function(type) + { + switch (type) { + case WebInspector.Resource.Type.Document: + return WebInspector.UIString("Document"); + case WebInspector.Resource.Type.Stylesheet: + return WebInspector.UIString("Stylesheet"); + case WebInspector.Resource.Type.Image: + return WebInspector.UIString("Image"); + case WebInspector.Resource.Type.Font: + return WebInspector.UIString("Font"); + case WebInspector.Resource.Type.Script: + return WebInspector.UIString("Script"); + case WebInspector.Resource.Type.XHR: + return WebInspector.UIString("XHR"); + case WebInspector.Resource.Type.WebSocket: + return WebInspector.UIString("WebSocket"); + case WebInspector.Resource.Type.Other: + default: + return WebInspector.UIString("Other"); + } + }, + + /** + * Returns locale-independent string identifier of resource type (primarily for use in extension API). + * The IDs need to be kept in sync with webInspector.resoureces.Types object in ExtensionAPI.js. + * @param {number} type + * @return {string} + */ + toString: function(type) + { + switch (type) { + case WebInspector.Resource.Type.Document: + return "document"; + case WebInspector.Resource.Type.Stylesheet: + return "stylesheet"; + case WebInspector.Resource.Type.Image: + return "image"; + case WebInspector.Resource.Type.Font: + return "font"; + case WebInspector.Resource.Type.Script: + return "script"; + case WebInspector.Resource.Type.XHR: + return "xhr"; + case WebInspector.Resource.Type.WebSocket: + return "websocket"; + case WebInspector.Resource.Type.Other: + default: + return "other"; + } + } +} + +WebInspector.Resource._domainModelBindings = []; + +/** + * @param {number} type + * @param {WebInspector.ResourceDomainModelBinding} binding + */ +WebInspector.Resource.registerDomainModelBinding = function(type, binding) +{ + WebInspector.Resource._domainModelBindings[type] = binding; +} + +WebInspector.Resource._resourceRevisionRegistry = function() +{ + if (!WebInspector.Resource._resourceRevisionRegistryObject) { + if (window.localStorage) { + var resourceHistory = window.localStorage["resource-history"]; + try { + WebInspector.Resource._resourceRevisionRegistryObject = resourceHistory ? JSON.parse(resourceHistory) : {}; + } catch (e) { + WebInspector.Resource._resourceRevisionRegistryObject = {}; + } + } else + WebInspector.Resource._resourceRevisionRegistryObject = {}; + } + return WebInspector.Resource._resourceRevisionRegistryObject; +} + +WebInspector.Resource.restoreRevisions = function() +{ + var registry = WebInspector.Resource._resourceRevisionRegistry(); + var filteredRegistry = {}; + for (var url in registry) { + var historyItems = registry[url]; + var resource = WebInspector.resourceForURL(url); + + var filteredHistoryItems = []; + for (var i = 0; historyItems && i < historyItems.length; ++i) { + var historyItem = historyItems[i]; + if (resource && historyItem.loaderId === resource.loaderId) { + resource.addRevision(window.localStorage[historyItem.key], new Date(historyItem.timestamp), true); + filteredHistoryItems.push(historyItem); + filteredRegistry[url] = filteredHistoryItems; + } else + delete window.localStorage[historyItem.key]; + } + } + WebInspector.Resource._resourceRevisionRegistryObject = filteredRegistry; + + function persist() + { + window.localStorage["resource-history"] = JSON.stringify(filteredRegistry); + } + + // Schedule async storage. + setTimeout(persist, 0); +} + +/** + * @param {WebInspector.Resource} resource + */ +WebInspector.Resource.persistRevision = function(resource) +{ + if (!window.localStorage) + return; + + var url = resource.url; + var loaderId = resource.loaderId; + var timestamp = resource._contentTimestamp.getTime(); + var key = "resource-history|" + url + "|" + loaderId + "|" + timestamp; + var content = resource._content; + + var registry = WebInspector.Resource._resourceRevisionRegistry(); + + var historyItems = registry[resource.url]; + if (!historyItems) { + historyItems = []; + registry[resource.url] = historyItems; + } + historyItems.push({url: url, loaderId: loaderId, timestamp: timestamp, key: key}); + + function persist() + { + window.localStorage[key] = content; + window.localStorage["resource-history"] = JSON.stringify(registry); + } + + // Schedule async storage. + setTimeout(persist, 0); +} + +WebInspector.Resource.Events = { + RevisionAdded: "revision-added", + MessageAdded: "message-added", + MessagesCleared: "messages-cleared", +} + +WebInspector.Resource.prototype = { + /** + * @return {string} + */ + get url() + { + return this._url; + }, + + set url(x) + { + if (this._url === x) + return; + + this._url = x; + delete this._parsedQueryParameters; + + var parsedURL = x.asParsedURL(); + this.domain = parsedURL ? parsedURL.host : ""; + this.path = parsedURL ? parsedURL.path : ""; + this.urlFragment = parsedURL ? parsedURL.fragment : ""; + this.lastPathComponent = parsedURL ? parsedURL.lastPathComponent : ""; + this.lastPathComponentLowerCase = this.lastPathComponent.toLowerCase(); + }, + + /** + * @return {string} + */ + get documentURL() + { + return this._documentURL; + }, + + set documentURL(x) + { + this._documentURL = x; + }, + + /** + * @return {string} + */ + get displayName() + { + if (this._displayName) + return this._displayName; + this._displayName = this.lastPathComponent; + if (!this._displayName) + this._displayName = this.displayDomain; + if (!this._displayName && this.url) + this._displayName = this.url.trimURL(WebInspector.inspectedPageDomain ? WebInspector.inspectedPageDomain : ""); + if (this._displayName === "/") + this._displayName = this.url; + return this._displayName; + }, + + /** + * @return {string} + */ + get folder() + { + var path = this.path; + var indexOfQuery = path.indexOf("?"); + if (indexOfQuery !== -1) + path = path.substring(0, indexOfQuery); + var lastSlashIndex = path.lastIndexOf("/"); + return lastSlashIndex !== -1 ? path.substring(0, lastSlashIndex) : ""; + }, + + /** + * @return {string} + */ + get displayDomain() + { + // WebInspector.Database calls this, so don't access more than this.domain. + if (this.domain && (!WebInspector.inspectedPageDomain || (WebInspector.inspectedPageDomain && this.domain !== WebInspector.inspectedPageDomain))) + return this.domain; + return ""; + }, + + /** + * @return {number} + */ + get startTime() + { + return this._startTime || -1; + }, + + set startTime(x) + { + this._startTime = x; + }, + + /** + * @return {number} + */ + get responseReceivedTime() + { + return this._responseReceivedTime || -1; + }, + + set responseReceivedTime(x) + { + this._responseReceivedTime = x; + }, + + /** + * @return {number} + */ + get endTime() + { + return this._endTime || -1; + }, + + set endTime(x) + { + if (this.timing && this.timing.requestTime) { + // Check against accurate responseReceivedTime. + this._endTime = Math.max(x, this.responseReceivedTime); + } else { + // Prefer endTime since it might be from the network stack. + this._endTime = x; + if (this._responseReceivedTime > x) + this._responseReceivedTime = x; + } + }, + + /** + * @return {number} + */ + get duration() + { + if (this._endTime === -1 || this._startTime === -1) + return -1; + return this._endTime - this._startTime; + }, + + /** + * @return {number} + */ + get latency() + { + if (this._responseReceivedTime === -1 || this._startTime === -1) + return -1; + return this._responseReceivedTime - this._startTime; + }, + + /** + * @return {number} + */ + get receiveDuration() + { + if (this._endTime === -1 || this._responseReceivedTime === -1) + return -1; + return this._endTime - this._responseReceivedTime; + }, + + /** + * @return {number} + */ + get resourceSize() + { + return this._resourceSize || 0; + }, + + set resourceSize(x) + { + this._resourceSize = x; + }, + + /** + * @return {number} + */ + get transferSize() + { + if (this.cached) + return 0; + if (this.statusCode === 304) // Not modified + return this.responseHeadersSize; + if (this._transferSize !== undefined) + return this._transferSize; + // If we did not receive actual transfer size from network + // stack, we prefer using Content-Length over resourceSize as + // resourceSize may differ from actual transfer size if platform's + // network stack performed decoding (e.g. gzip decompression). + // The Content-Length, though, is expected to come from raw + // response headers and will reflect actual transfer length. + // This won't work for chunked content encoding, so fall back to + // resourceSize when we don't have Content-Length. This still won't + // work for chunks with non-trivial encodings. We need a way to + // get actual transfer size from the network stack. + var bodySize = Number(this.responseHeaders["Content-Length"] || this.resourceSize); + return this.responseHeadersSize + bodySize; + }, + + /** + * @param {number} x + */ + increaseTransferSize: function(x) + { + this._transferSize = (this._transferSize || 0) + x; + }, + + /** + * @return {boolean} + */ + get finished() + { + return this._finished; + }, + + set finished(x) + { + if (this._finished === x) + return; + + this._finished = x; + + if (x) { + this.dispatchEventToListeners("finished"); + if (this._pendingContentCallbacks.length) + this._innerRequestContent(); + } + }, + + /** + * @return {boolean} + */ + get failed() + { + return this._failed; + }, + + set failed(x) + { + this._failed = x; + }, + + /** + * @return {boolean} + */ + get canceled() + { + return this._canceled; + }, + + set canceled(x) + { + this._canceled = x; + }, + + /** + * @return {WebInspector.ResourceCategory} + */ + get category() + { + return this._category; + }, + + set category(x) + { + this._category = x; + }, + + /** + * @return {boolean} + */ + get cached() + { + return this._cached; + }, + + set cached(x) + { + this._cached = x; + if (x) + delete this._timing; + }, + + /** + * @return {NetworkAgent.ResourceTiming|undefined} + */ + get timing() + { + return this._timing; + }, + + set timing(x) + { + if (x && !this._cached) { + // Take startTime and responseReceivedTime from timing data for better accuracy. + // Timing's requestTime is a baseline in seconds, rest of the numbers there are ticks in millis. + this._startTime = x.requestTime; + this._responseReceivedTime = x.requestTime + x.receiveHeadersEnd / 1000.0; + + this._timing = x; + this.dispatchEventToListeners("timing changed"); + } + }, + + /** + * @return {string} + */ + get mimeType() + { + return this._mimeType; + }, + + set mimeType(x) + { + this._mimeType = x; + }, + + /** + * @return {number} + */ + get type() + { + return this._type; + }, + + set type(x) + { + if (this._type === x) + return; + + this._type = x; + + switch (x) { + case WebInspector.Resource.Type.Document: + this.category = WebInspector.resourceCategories.documents; + break; + case WebInspector.Resource.Type.Stylesheet: + this.category = WebInspector.resourceCategories.stylesheets; + break; + case WebInspector.Resource.Type.Script: + this.category = WebInspector.resourceCategories.scripts; + break; + case WebInspector.Resource.Type.Image: + this.category = WebInspector.resourceCategories.images; + break; + case WebInspector.Resource.Type.Font: + this.category = WebInspector.resourceCategories.fonts; + break; + case WebInspector.Resource.Type.XHR: + this.category = WebInspector.resourceCategories.xhr; + break; + case WebInspector.Resource.Type.WebSocket: + this.category = WebInspector.resourceCategories.websockets; + break; + case WebInspector.Resource.Type.Other: + default: + this.category = WebInspector.resourceCategories.other; + break; + } + }, + + /** + * @return {WebInspector.Resource|undefined} + */ + get redirectSource() + { + if (this.redirects && this.redirects.length > 0) + return this.redirects[this.redirects.length - 1]; + return this._redirectSource; + }, + + set redirectSource(x) + { + this._redirectSource = x; + }, + + /** + * @return {Object} + */ + get requestHeaders() + { + return this._requestHeaders || {}; + }, + + set requestHeaders(x) + { + this._requestHeaders = x; + delete this._sortedRequestHeaders; + delete this._requestCookies; + + this.dispatchEventToListeners("requestHeaders changed"); + }, + + /** + * @return {string} + */ + get requestHeadersText() + { + if (this._requestHeadersText === undefined) { + this._requestHeadersText = this.requestMethod + " " + this.url + " HTTP/1.1\r\n"; + for (var key in this.requestHeaders) + this._requestHeadersText += key + ": " + this.requestHeaders[key] + "\r\n"; + } + return this._requestHeadersText; + }, + + set requestHeadersText(x) + { + this._requestHeadersText = x; + + this.dispatchEventToListeners("requestHeaders changed"); + }, + + /** + * @return {number} + */ + get requestHeadersSize() + { + return this.requestHeadersText.length; + }, + + /** + * @return {Array.} + */ + get sortedRequestHeaders() + { + if (this._sortedRequestHeaders !== undefined) + return this._sortedRequestHeaders; + + this._sortedRequestHeaders = []; + for (var key in this.requestHeaders) + this._sortedRequestHeaders.push({header: key, value: this.requestHeaders[key]}); + this._sortedRequestHeaders.sort(function(a,b) { return a.header.localeCompare(b.header) }); + + return this._sortedRequestHeaders; + }, + + /** + * @param {string} headerName + * @return {string|undefined} + */ + requestHeaderValue: function(headerName) + { + return this._headerValue(this.requestHeaders, headerName); + }, + + /** + * @type {Array.} + */ + get requestCookies() + { + if (!this._requestCookies) + this._requestCookies = WebInspector.CookieParser.parseCookie(this.requestHeaderValue("Cookie")); + return this._requestCookies; + }, + + /** + * @type {string|undefined} + */ + get requestFormData() + { + return this._requestFormData; + }, + + set requestFormData(x) + { + this._requestFormData = x; + delete this._parsedFormParameters; + }, + + /** + * @type {string|undefined} + */ + get requestHttpVersion() + { + var firstLine = this.requestHeadersText.split(/\r\n/)[0]; + var match = firstLine.match(/(HTTP\/\d+\.\d+)$/); + return match ? match[1] : undefined; + }, + + /** + * @type {Object} + */ + get responseHeaders() + { + return this._responseHeaders || {}; + }, + + set responseHeaders(x) + { + this._responseHeaders = x; + delete this._sortedResponseHeaders; + delete this._responseCookies; + + this.dispatchEventToListeners("responseHeaders changed"); + }, + + /** + * @type {string} + */ + get responseHeadersText() + { + if (this._responseHeadersText === undefined) { + this._responseHeadersText = "HTTP/1.1 " + this.statusCode + " " + this.statusText + "\r\n"; + for (var key in this.responseHeaders) + this._responseHeadersText += key + ": " + this.responseHeaders[key] + "\r\n"; + } + return this._responseHeadersText; + }, + + set responseHeadersText(x) + { + this._responseHeadersText = x; + + this.dispatchEventToListeners("responseHeaders changed"); + }, + + /** + * @type {number} + */ + get responseHeadersSize() + { + return this.responseHeadersText.length; + }, + + /** + * @type {Array.} + */ + get sortedResponseHeaders() + { + if (this._sortedResponseHeaders !== undefined) + return this._sortedResponseHeaders; + + this._sortedResponseHeaders = []; + for (var key in this.responseHeaders) + this._sortedResponseHeaders.push({header: key, value: this.responseHeaders[key]}); + this._sortedResponseHeaders.sort(function(a,b) { return a.header.localeCompare(b.header) }); + + return this._sortedResponseHeaders; + }, + + /** + * @param {string} headerName + * @return {string|undefined} + */ + responseHeaderValue: function(headerName) + { + return this._headerValue(this.responseHeaders, headerName); + }, + + /** + * @type {Array.} + */ + get responseCookies() + { + if (!this._responseCookies) + this._responseCookies = WebInspector.CookieParser.parseSetCookie(this.responseHeaderValue("Set-Cookie")); + return this._responseCookies; + }, + + /** + * @type {?Array.} + */ + get queryParameters() + { + if (this._parsedQueryParameters) + return this._parsedQueryParameters; + var queryString = this.url.split("?", 2)[1]; + if (!queryString) + return null; + queryString = queryString.split("#", 2)[0]; + this._parsedQueryParameters = this._parseParameters(queryString); + return this._parsedQueryParameters; + }, + + /** + * @type {?Array.} + */ + get formParameters() + { + if (this._parsedFormParameters) + return this._parsedFormParameters; + if (!this.requestFormData) + return null; + var requestContentType = this.requestContentType(); + if (!requestContentType || !requestContentType.match(/^application\/x-www-form-urlencoded\s*(;.*)?$/i)) + return null; + this._parsedFormParameters = this._parseParameters(this.requestFormData); + return this._parsedFormParameters; + }, + + /** + * @type {string|undefined} + */ + get responseHttpVersion() + { + var match = this.responseHeadersText.match(/^(HTTP\/\d+\.\d+)/); + return match ? match[1] : undefined; + }, + + /** + * @param {string} queryString + * @return {Array.} + */ + _parseParameters: function(queryString) + { + function parseNameValue(pair) + { + var parameter = {}; + var splitPair = pair.split("=", 2); + + parameter.name = splitPair[0]; + if (splitPair.length === 1) + parameter.value = ""; + else + parameter.value = splitPair[1]; + return parameter; + } + return queryString.split("&").map(parseNameValue); + }, + + /** + * @param {Object} headers + * @param {string} headerName + * @return {string|undefined} + */ + _headerValue: function(headers, headerName) + { + headerName = headerName.toLowerCase(); + for (var header in headers) { + if (header.toLowerCase() === headerName) + return headers[header]; + } + }, + + /** + * @type {Array.} + */ + get messages() + { + return this._messages || []; + }, + + /** + * @param {WebInspector.ConsoleMessage} msg + */ + addMessage: function(msg) + { + if (!msg.isErrorOrWarning() || !msg.message) + return; + + if (!this._messages) + this._messages = []; + this._messages.push(msg); + this.dispatchEventToListeners(WebInspector.Resource.Events.MessageAdded, msg); + }, + + /** + * @type {number} + */ + get errors() + { + return this._errors || 0; + }, + + set errors(x) + { + this._errors = x; + }, + + /** + * @type {number} + */ + get warnings() + { + return this._warnings || 0; + }, + + set warnings(x) + { + this._warnings = x; + }, + + clearErrorsAndWarnings: function() + { + this._messages = []; + this._warnings = 0; + this._errors = 0; + this.dispatchEventToListeners(WebInspector.Resource.Events.MessagesCleared); + }, + + /** + * @type {string} + */ + get content() + { + return this._content; + }, + + /** + * @type {string} + */ + get contentEncoded() + { + return this._contentEncoded; + }, + + /** + * @type {number} + */ + get contentTimestamp() + { + return this._contentTimestamp; + }, + + /** + * @return {boolean} + */ + isEditable: function() + { + if (this._actualResource) + return false; + var binding = WebInspector.Resource._domainModelBindings[this.type]; + return binding && binding.canSetContent(this); + }, + + /** + * @param {string} newContent + * @param {boolean} majorChange + * @param {function(string=)} callback + */ + setContent: function(newContent, majorChange, callback) + { + if (!this.isEditable()) { + if (callback) + callback("Resource is not editable"); + return; + } + var binding = WebInspector.Resource._domainModelBindings[this.type]; + binding.setContent(this, newContent, majorChange, callback); + }, + + /** + * @param {string} newContent + * @param {Date=} timestamp + * @param {boolean=} restoringHistory + */ + addRevision: function(newContent, timestamp, restoringHistory) + { + var revision = new WebInspector.ResourceRevision(this, this._content, this._contentTimestamp); + this.history.push(revision); + + this._content = newContent; + this._contentTimestamp = timestamp || new Date(); + + this.dispatchEventToListeners(WebInspector.Resource.Events.RevisionAdded, revision); + if (!restoringHistory) + this._persistRevision(); + WebInspector.resourceTreeModel.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.ResourceContentCommitted, { resource: this, content: newContent }); + }, + + _persistRevision: function() + { + WebInspector.Resource.persistRevision(this); + }, + + /** + * @param {function(?string, ?string)} callback + */ + requestContent: function(callback) + { + // We do not support content retrieval for WebSockets at the moment. + // Since WebSockets are potentially long-living, fail requests immediately + // to prevent caller blocking until resource is marked as finished. + if (this.type === WebInspector.Resource.Type.WebSocket) { + callback(null, null); + return; + } + if (typeof this._content !== "undefined") { + callback(this.content, this._contentEncoded); + return; + } + this._pendingContentCallbacks.push(callback); + if (this.finished) + this._innerRequestContent(); + }, + + /** + * @param {string} query + * @param {boolean} caseSensitive + * @param {boolean} isRegex + * @param {function(Array.)} callback + */ + searchInContent: function(query, caseSensitive, isRegex, callback) + { + /** + * @param {?Protocol.Error} error + * @param {Array.} searchMatches + */ + function callbackWrapper(error, searchMatches) + { + callback(searchMatches || []); + } + + if (this.frameId) + PageAgent.searchInResource(this.frameId, this.url, query, caseSensitive, isRegex, callbackWrapper); + else + callback([]); + }, + + /** + * @param {Element} image + */ + populateImageSource: function(image) + { + function onResourceContent() + { + image.src = this._contentURL(); + } + + this.requestContent(onResourceContent.bind(this)); + }, + + /** + * @return {boolean} + */ + isHttpFamily: function() + { + return !!this.url.match(/^https?:/i); + }, + + /** + * @return {string|undefined} + */ + requestContentType: function() + { + return this.requestHeaderValue("Content-Type"); + }, + + /** + * @return {boolean} + */ + isPingRequest: function() + { + return "text/ping" === this.requestContentType(); + }, + + /** + * @return {boolean} + */ + hasErrorStatusCode: function() + { + return this.statusCode >= 400; + }, + + /** + * @return {string} + */ + _contentURL: function() + { + const maxDataUrlSize = 1024 * 1024; + // If resource content is not available or won't fit a data URL, fall back to using original URL. + if (this._content == null || this._content.length > maxDataUrlSize) + return this.url; + + return "data:" + this.mimeType + (this._contentEncoded ? ";base64," : ",") + this._content; + }, + + _innerRequestContent: function() + { + if (this._contentRequested) + return; + this._contentRequested = true; + + function onResourceContent(data, contentEncoded) + { + this._contentEncoded = contentEncoded; + this._content = data; + this._originalContent = data; + var callbacks = this._pendingContentCallbacks.slice(); + for (var i = 0; i < callbacks.length; ++i) + callbacks[i](this._content, this._contentEncoded); + this._pendingContentCallbacks.length = 0; + delete this._contentRequested; + } + WebInspector.networkManager.requestContent(this, onResourceContent.bind(this)); + } +} + +WebInspector.Resource.prototype.__proto__ = WebInspector.Object.prototype; + +/** + * @constructor + * @param {WebInspector.Resource} resource + * @param {string} content + * @param {number} timestamp + */ +WebInspector.ResourceRevision = function(resource, content, timestamp) +{ + this._resource = resource; + this._content = content; + this._timestamp = timestamp; +} + +WebInspector.ResourceRevision.prototype = { + /** + * @type {WebInspector.Resource} + */ + get resource() + { + return this._resource; + }, + + /** + * @type {number} + */ + get timestamp() + { + return this._timestamp; + }, + + /** + * @type {string} + */ + get content() + { + return this._content; + }, + + revertToThis: function() + { + function revert(content) + { + this._resource.setContent(content, true); + } + this.requestContent(revert.bind(this)); + }, + + /** + * @param {function(string)} callback + */ + requestContent: function(callback) + { + if (typeof this._content === "string") { + callback(this._content); + return; + } + + // If we are here, this is initial revision. First, look up content fetched over the wire. + if (typeof this.resource._originalContent === "string") { + this._content = this._resource._originalContent; + callback(this._content); + return; + } + + // If unsuccessful, request the content. + function mycallback(content) + { + this._content = content; + callback(content); + } + WebInspector.networkManager.requestContent(this._resource, mycallback.bind(this)); + } +} + +/** + * @interface + */ +WebInspector.ResourceDomainModelBinding = function() { } + +WebInspector.ResourceDomainModelBinding.prototype = { + /** + * @return {boolean} + */ + canSetContent: function() { return true; }, + + /** + * @param {WebInspector.Resource} resource + * @param {string} content + * @param {boolean} majorChange + * @param {function(?string)} callback + */ + setContent: function(resource, content, majorChange, callback) { } +} +/* CSSStyleModel.js */ + +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.Object} + */ +WebInspector.CSSStyleModel = function() +{ + new WebInspector.CSSStyleModelResourceBinding(this); + InspectorBackend.registerCSSDispatcher(new WebInspector.CSSDispatcher(this)); + CSSAgent.enable(); +} + +WebInspector.CSSStyleModel.parseRuleArrayPayload = function(ruleArray) +{ + var result = []; + for (var i = 0; i < ruleArray.length; ++i) + result.push(WebInspector.CSSRule.parsePayload(ruleArray[i])); + return result; +} + +WebInspector.CSSStyleModel.Events = { + StyleSheetChanged: "StyleSheetChanged", + MediaQueryResultChanged: "MediaQueryResultChanged" +} + +WebInspector.CSSStyleModel.prototype = { + /** + * @param {DOMAgent.NodeId} nodeId + * @param {?Array.|undefined} forcedPseudoClasses + * @param {boolean} needPseudo + * @param {boolean} needInherited + * @param {function(?*)} userCallback + */ + getMatchedStylesAsync: function(nodeId, forcedPseudoClasses, needPseudo, needInherited, userCallback) + { + /** + * @param {function(?*)} userCallback + * @param {?Protocol.Error} error + * @param {Array.=} matchedPayload + * @param {Array.=} pseudoPayload + * @param {Array.=} inheritedPayload + */ + function callback(userCallback, error, matchedPayload, pseudoPayload, inheritedPayload) + { + if (error) { + if (userCallback) + userCallback(null); + return; + } + + var result = {}; + if (matchedPayload) + result.matchedCSSRules = WebInspector.CSSStyleModel.parseRuleArrayPayload(matchedPayload); + + if (pseudoPayload) { + result.pseudoElements = []; + for (var i = 0; i < pseudoPayload.length; ++i) { + var entryPayload = pseudoPayload[i]; + result.pseudoElements.push({ pseudoId: entryPayload.pseudoId, rules: WebInspector.CSSStyleModel.parseRuleArrayPayload(entryPayload.rules) }); + } + } + + if (inheritedPayload) { + result.inherited = []; + for (var i = 0; i < inheritedPayload.length; ++i) { + var entryPayload = inheritedPayload[i]; + var entry = {}; + if (entryPayload.inlineStyle) + entry.inlineStyle = WebInspector.CSSStyleDeclaration.parsePayload(entryPayload.inlineStyle); + if (entryPayload.matchedCSSRules) + entry.matchedCSSRules = WebInspector.CSSStyleModel.parseRuleArrayPayload(entryPayload.matchedCSSRules); + result.inherited.push(entry); + } + } + + if (userCallback) + userCallback(result); + } + + CSSAgent.getMatchedStylesForNode(nodeId, forcedPseudoClasses || [], needPseudo, needInherited, callback.bind(null, userCallback)); + }, + + /** + * @param {DOMAgent.NodeId} nodeId + * @param {?Array.|undefined} forcedPseudoClasses + * @param {function(?WebInspector.CSSStyleDeclaration)} userCallback + */ + getComputedStyleAsync: function(nodeId, forcedPseudoClasses, userCallback) + { + /** + * @param {function(?WebInspector.CSSStyleDeclaration)} userCallback + */ + function callback(userCallback, error, computedPayload) + { + if (error || !computedPayload) + userCallback(null); + else + userCallback(WebInspector.CSSStyleDeclaration.parseComputedStylePayload(computedPayload)); + } + + CSSAgent.getComputedStyleForNode(nodeId, forcedPseudoClasses || [], callback.bind(null, userCallback)); + }, + + /** + * @param {DOMAgent.NodeId} nodeId + * @param {function(?WebInspector.CSSStyleDeclaration, ?Object.)} userCallback + */ + getInlineStylesAsync: function(nodeId, userCallback) + { + /** + * @param {function(?WebInspector.CSSStyleDeclaration, ?Object.)} userCallback + */ + function callback(userCallback, error, inlinePayload, attributesPayload) + { + if (error || !inlinePayload) + userCallback(null, null); + else { + var styleAttributes; + if (attributesPayload) { + styleAttributes = {}; + for (var i = 0; i < attributesPayload.length; ++i) { + var name = attributesPayload[i].name; + styleAttributes[name] = WebInspector.CSSStyleDeclaration.parsePayload(attributesPayload[i].style); + } + } + userCallback(WebInspector.CSSStyleDeclaration.parsePayload(inlinePayload), styleAttributes || null); + } + } + + CSSAgent.getInlineStylesForNode(nodeId, callback.bind(null, userCallback)); + }, + + /** + * @param {CSSAgent.CSSRuleId} ruleId + * @param {DOMAgent.NodeId} nodeId + * @param {string} newSelector + * @param {function(WebInspector.CSSRule, boolean)} successCallback + * @param {function()} failureCallback + */ + setRuleSelector: function(ruleId, nodeId, newSelector, successCallback, failureCallback) + { + /** + * @param {DOMAgent.NodeId} nodeId + * @param {function(WebInspector.CSSRule, boolean)} successCallback + * @param {*} rulePayload + * @param {?Array.} selectedNodeIds + */ + function checkAffectsCallback(nodeId, successCallback, rulePayload, selectedNodeIds) + { + if (!selectedNodeIds) + return; + var doesAffectSelectedNode = (selectedNodeIds.indexOf(nodeId) >= 0); + var rule = WebInspector.CSSRule.parsePayload(rulePayload); + successCallback(rule, doesAffectSelectedNode); + this._fireStyleSheetChanged(rule.id.styleSheetId, true); + } + + /** + * @param {DOMAgent.NodeId} nodeId + * @param {function(WebInspector.CSSRule, boolean)} successCallback + * @param {function()} failureCallback + * @param {?Protocol.Error} error + * @param {string} newSelector + * @param {*=} rulePayload + */ + function callback(nodeId, successCallback, failureCallback, newSelector, error, rulePayload) + { + if (error) + failureCallback(); + else { + var ownerDocumentId = this._ownerDocumentId(nodeId); + if (ownerDocumentId) + WebInspector.domAgent.querySelectorAll(ownerDocumentId, newSelector, checkAffectsCallback.bind(this, nodeId, successCallback, rulePayload)); + else + failureCallback(); + } + } + + CSSAgent.setRuleSelector(ruleId, newSelector, callback.bind(this, nodeId, successCallback, failureCallback, newSelector)); + }, + + /** + * @param {DOMAgent.NodeId} nodeId + * @param {string} selector + * @param {function(WebInspector.CSSRule, boolean)} successCallback + * @param {function()} failureCallback + */ + addRule: function(nodeId, selector, successCallback, failureCallback) + { + function checkAffectsCallback(nodeId, successCallback, rulePayload, selectedNodeIds) + { + if (!selectedNodeIds) + return; + + var doesAffectSelectedNode = (selectedNodeIds.indexOf(nodeId) >= 0); + var rule = WebInspector.CSSRule.parsePayload(rulePayload); + successCallback(rule, doesAffectSelectedNode); + this._fireStyleSheetChanged(rule.id.styleSheetId, true); + } + + /** + * @param {function(WebInspector.CSSRule, boolean)} successCallback + * @param {function()} failureCallback + * @param {string} selector + * @param {?Protocol.Error} error + * @param {?CSSAgent.CSSRule} rulePayload + */ + function callback(successCallback, failureCallback, selector, error, rulePayload) + { + if (error) { + // Invalid syntax for a selector + failureCallback(); + } else { + var ownerDocumentId = this._ownerDocumentId(nodeId); + if (ownerDocumentId) + WebInspector.domAgent.querySelectorAll(ownerDocumentId, selector, checkAffectsCallback.bind(this, nodeId, successCallback, rulePayload)); + else + failureCallback(); + } + } + + CSSAgent.addRule(nodeId, selector, callback.bind(this, successCallback, failureCallback, selector)); + }, + + mediaQueryResultChanged: function() + { + this.dispatchEventToListeners(WebInspector.CSSStyleModel.Events.MediaQueryResultChanged); + }, + + _ownerDocumentId: function(nodeId) + { + var node = WebInspector.domAgent.nodeForId(nodeId); + if (!node) + return null; + return node.ownerDocument ? node.ownerDocument.id : null; + }, + + /** + * @param {function()=} callback + */ + _fireStyleSheetChanged: function(styleSheetId, majorChange, callback) + { + callback = callback || function() {}; + + if (!majorChange || !styleSheetId || !this.hasEventListeners(WebInspector.CSSStyleModel.Events.StyleSheetChanged)) { + callback(); + return; + } + + function mycallback(error, content) + { + if (!error) + this.dispatchEventToListeners(WebInspector.CSSStyleModel.Events.StyleSheetChanged, { styleSheetId: styleSheetId, content: content, majorChange: majorChange }); + callback(); + } + + CSSAgent.getStyleSheetText(styleSheetId, mycallback.bind(this)); + }, + + setStyleSheetText: function(styleSheetId, newText, majorChange, userCallback) + { + function callback(error) + { + if (!error) + this._fireStyleSheetChanged(styleSheetId, majorChange, userCallback ? userCallback.bind(this, error) : null); + } + CSSAgent.setStyleSheetText(styleSheetId, newText, callback.bind(this)); + } +} + +WebInspector.CSSStyleModel.prototype.__proto__ = WebInspector.Object.prototype; + +/** + * @constructor + * @param {*} payload + */ +WebInspector.CSSStyleDeclaration = function(payload) +{ + this.id = payload.styleId; + this.width = payload.width; + this.height = payload.height; + this.range = payload.range; + this._shorthandValues = WebInspector.CSSStyleDeclaration.buildShorthandValueMap(payload.shorthandEntries); + this._livePropertyMap = {}; // LIVE properties (source-based or style-based) : { name -> CSSProperty } + this._allProperties = []; // ALL properties: [ CSSProperty ] + this._longhandProperties = {}; // shorthandName -> [ CSSProperty ] + this.__disabledProperties = {}; // DISABLED properties: { index -> CSSProperty } + var payloadPropertyCount = payload.cssProperties.length; + + var propertyIndex = 0; + for (var i = 0; i < payloadPropertyCount; ++i) { + var property = WebInspector.CSSProperty.parsePayload(this, i, payload.cssProperties[i]); + this._allProperties.push(property); + if (property.disabled) + this.__disabledProperties[i] = property; + if (!property.active && !property.styleBased) + continue; + var name = property.name; + this[propertyIndex] = name; + this._livePropertyMap[name] = property; + + // Index longhand properties. + if (property.shorthand) { // only for parsed + var longhands = this._longhandProperties[property.shorthand]; + if (!longhands) { + longhands = []; + this._longhandProperties[property.shorthand] = longhands; + } + longhands.push(property); + } + ++propertyIndex; + } + this.length = propertyIndex; + if ("cssText" in payload) + this.cssText = payload.cssText; +} + +WebInspector.CSSStyleDeclaration.buildShorthandValueMap = function(shorthandEntries) +{ + var result = {}; + for (var i = 0; i < shorthandEntries.length; ++i) + result[shorthandEntries[i].name] = shorthandEntries[i].value; + return result; +} + +WebInspector.CSSStyleDeclaration.parsePayload = function(payload) +{ + return new WebInspector.CSSStyleDeclaration(payload); +} + +WebInspector.CSSStyleDeclaration.parseComputedStylePayload = function(payload) +{ + var newPayload = { cssProperties: [], shorthandEntries: [], width: "", height: "" }; + if (payload) + newPayload.cssProperties = payload; + + return new WebInspector.CSSStyleDeclaration(newPayload); +} + +WebInspector.CSSStyleDeclaration.prototype = { + get allProperties() + { + return this._allProperties; + }, + + getLiveProperty: function(name) + { + return this._livePropertyMap[name]; + }, + + getPropertyValue: function(name) + { + var property = this._livePropertyMap[name]; + return property ? property.value : ""; + }, + + getPropertyPriority: function(name) + { + var property = this._livePropertyMap[name]; + return property ? property.priority : ""; + }, + + getPropertyShorthand: function(name) + { + var property = this._livePropertyMap[name]; + return property ? property.shorthand : ""; + }, + + isPropertyImplicit: function(name) + { + var property = this._livePropertyMap[name]; + return property ? property.implicit : ""; + }, + + styleTextWithShorthands: function() + { + var cssText = ""; + var foundProperties = {}; + for (var i = 0; i < this.length; ++i) { + var individualProperty = this[i]; + var shorthandProperty = this.getPropertyShorthand(individualProperty); + var propertyName = (shorthandProperty || individualProperty); + + if (propertyName in foundProperties) + continue; + + if (shorthandProperty) { + var value = this.getShorthandValue(shorthandProperty); + var priority = this.getShorthandPriority(shorthandProperty); + } else { + var value = this.getPropertyValue(individualProperty); + var priority = this.getPropertyPriority(individualProperty); + } + + foundProperties[propertyName] = true; + + cssText += propertyName + ": " + value; + if (priority) + cssText += " !" + priority; + cssText += "; "; + } + + return cssText; + }, + + getLonghandProperties: function(name) + { + return this._longhandProperties[name] || []; + }, + + getShorthandValue: function(shorthandProperty) + { + var property = this.getLiveProperty(shorthandProperty); + return property ? property.value : this._shorthandValues[shorthandProperty]; + }, + + getShorthandPriority: function(shorthandProperty) + { + var priority = this.getPropertyPriority(shorthandProperty); + if (priority) + return priority; + + var longhands = this._longhandProperties[shorthandProperty]; + return longhands ? this.getPropertyPriority(longhands[0]) : null; + }, + + propertyAt: function(index) + { + return (index < this.allProperties.length) ? this.allProperties[index] : null; + }, + + pastLastSourcePropertyIndex: function() + { + for (var i = this.allProperties.length - 1; i >= 0; --i) { + var property = this.allProperties[i]; + if (property.active || property.disabled) + return i + 1; + } + return 0; + }, + + newBlankProperty: function() + { + return new WebInspector.CSSProperty(this, this.pastLastSourcePropertyIndex(), "", "", "", "active", true, false, false, ""); + }, + + insertPropertyAt: function(index, name, value, userCallback) + { + function callback(userCallback, error, payload) + { + if (!userCallback) + return; + + if (error) { + console.error(JSON.stringify(error)); + userCallback(null); + } else { + userCallback(WebInspector.CSSStyleDeclaration.parsePayload(payload)); + WebInspector.cssModel._fireStyleSheetChanged(this.id.styleSheetId, true); + } + } + + CSSAgent.setPropertyText(this.id, index, name + ": " + value + ";", false, callback.bind(this, userCallback)); + }, + + appendProperty: function(name, value, userCallback) + { + this.insertPropertyAt(this.allProperties.length, name, value, userCallback); + } +} + +/** + * @constructor + */ +WebInspector.CSSRule = function(payload) +{ + this.id = payload.ruleId; + this.selectorText = payload.selectorText; + this.sourceLine = payload.sourceLine; + this.sourceURL = payload.sourceURL; + this.origin = payload.origin; + this.style = WebInspector.CSSStyleDeclaration.parsePayload(payload.style); + this.style.parentRule = this; + this.selectorRange = payload.selectorRange; + if (payload.media) + this.media = WebInspector.CSSMedia.parseMediaArrayPayload(payload.media); +} + +WebInspector.CSSRule.parsePayload = function(payload) +{ + return new WebInspector.CSSRule(payload); +} + +WebInspector.CSSRule.prototype = { + get isUserAgent() + { + return this.origin === "user-agent"; + }, + + get isUser() + { + return this.origin === "user"; + }, + + get isViaInspector() + { + return this.origin === "inspector"; + }, + + get isRegular() + { + return this.origin === "regular"; + } +} + +/** + * @constructor + */ +WebInspector.CSSProperty = function(ownerStyle, index, name, value, priority, status, parsedOk, implicit, shorthand, text) +{ + this.ownerStyle = ownerStyle; + this.index = index; + this.name = name; + this.value = value; + this.priority = priority; + this.status = status; + this.parsedOk = parsedOk; + this.implicit = implicit; + this.shorthand = shorthand; + this.text = text; +} + +WebInspector.CSSProperty.parsePayload = function(ownerStyle, index, payload) +{ + // The following default field values are used in the payload: + // priority: "" + // parsedOk: true + // implicit: false + // status: "style" + // shorthandName: "" + var result = new WebInspector.CSSProperty( + ownerStyle, index, payload.name, payload.value, payload.priority || "", payload.status || "style", ("parsedOk" in payload) ? payload.parsedOk : true, !!payload.implicit, payload.shorthandName || "", payload.text); + return result; +} + +WebInspector.CSSProperty.prototype = { + get propertyText() + { + if (this.text !== undefined) + return this.text; + + if (this.name === "") + return ""; + return this.name + ": " + this.value + (this.priority ? " !" + this.priority : "") + ";"; + }, + + get isLive() + { + return this.active || this.styleBased; + }, + + get active() + { + return this.status === "active"; + }, + + get styleBased() + { + return this.status === "style"; + }, + + get inactive() + { + return this.status === "inactive"; + }, + + get disabled() + { + return this.status === "disabled"; + }, + + // Replaces "propertyName: propertyValue [!important];" in the stylesheet by an arbitrary propertyText. + setText: function(propertyText, majorChange, userCallback) + { + function enabledCallback(style) + { + if (style) + WebInspector.cssModel._fireStyleSheetChanged(style.id.styleSheetId, majorChange, userCallback ? userCallback.bind(null, style) : null); + else if (userCallback) + userCallback(style); + } + + function callback(error, stylePayload) + { + if (!error) { + this.text = propertyText; + var style = WebInspector.CSSStyleDeclaration.parsePayload(stylePayload); + var newProperty = style.allProperties[this.index]; + + if (newProperty && this.disabled && !propertyText.match(/^\s*$/)) { + newProperty.setDisabled(false, enabledCallback); + return; + } + + WebInspector.cssModel._fireStyleSheetChanged(style.id.styleSheetId, majorChange, userCallback ? userCallback.bind(null, style) : null); + } else { + if (userCallback) + userCallback(null); + } + } + + if (!this.ownerStyle) + throw "No ownerStyle for property"; + + // An index past all the properties adds a new property to the style. + CSSAgent.setPropertyText(this.ownerStyle.id, this.index, propertyText, this.index < this.ownerStyle.pastLastSourcePropertyIndex(), callback.bind(this)); + }, + + setValue: function(newValue, majorChange, userCallback) + { + var text = this.name + ": " + newValue + (this.priority ? " !" + this.priority : "") + ";" + this.setText(text, majorChange, userCallback); + }, + + setDisabled: function(disabled, userCallback) + { + if (!this.ownerStyle && userCallback) + userCallback(null); + if (disabled === this.disabled && userCallback) + userCallback(this.ownerStyle); + + function callback(error, stylePayload) + { + if (error) { + if (userCallback) + userCallback(null); + return; + } + if (userCallback) { + var style = WebInspector.CSSStyleDeclaration.parsePayload(stylePayload); + userCallback(style); + } + WebInspector.cssModel._fireStyleSheetChanged(this.ownerStyle.id.styleSheetId, false); + } + + CSSAgent.toggleProperty(this.ownerStyle.id, this.index, disabled, callback.bind(this)); + } +} + +/** + * @constructor + * @param {*} payload + */ +WebInspector.CSSMedia = function(payload) +{ + this.text = payload.text; + this.source = payload.source; + this.sourceURL = payload.sourceURL || ""; + this.sourceLine = typeof payload.sourceLine === "undefined" || this.source === "linkedSheet" ? -1 : payload.sourceLine; +} + +WebInspector.CSSMedia.Source = { + LINKED_SHEET: "linkedSheet", + INLINE_SHEET: "inlineSheet", + MEDIA_RULE: "mediaRule", + IMPORT_RULE: "importRule" +}; + +WebInspector.CSSMedia.parsePayload = function(payload) +{ + return new WebInspector.CSSMedia(payload); +} + +WebInspector.CSSMedia.parseMediaArrayPayload = function(payload) +{ + var result = []; + for (var i = 0; i < payload.length; ++i) + result.push(WebInspector.CSSMedia.parsePayload(payload[i])); + return result; +} + +/** + * @constructor + */ +WebInspector.CSSStyleSheet = function(payload) +{ + this.id = payload.styleSheetId; + this.rules = []; + this.styles = {}; + for (var i = 0; i < payload.rules.length; ++i) { + var rule = WebInspector.CSSRule.parsePayload(payload.rules[i]); + this.rules.push(rule); + if (rule.style) + this.styles[rule.style.id] = rule.style; + } + if ("text" in payload) + this._text = payload.text; +} + +WebInspector.CSSStyleSheet.createForId = function(styleSheetId, userCallback) +{ + function callback(error, styleSheetPayload) + { + if (error) + userCallback(null); + else + userCallback(new WebInspector.CSSStyleSheet(styleSheetPayload)); + } + CSSAgent.getStyleSheet(styleSheetId, callback.bind(this)); +} + +WebInspector.CSSStyleSheet.prototype = { + getText: function() + { + return this._text; + }, + + setText: function(newText, majorChange, userCallback) + { + function callback(error) + { + if (userCallback) + userCallback(error); + if (!error) + WebInspector.cssModel._fireStyleSheetChanged(this.id, majorChange); + } + + CSSAgent.setStyleSheetText(this.id, newText, callback.bind(this)); + } +} + +/** + * @constructor + * @implements {WebInspector.ResourceDomainModelBinding} + */ +WebInspector.CSSStyleModelResourceBinding = function(cssModel) +{ + this._cssModel = cssModel; + this._urlToStyleSheetId = {}; + this._styleSheetIdToURL = {}; + this._cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetChanged, this._styleSheetChanged, this); + WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.InspectedURLChanged, this._inspectedURLChanged, this); + WebInspector.Resource.registerDomainModelBinding(WebInspector.Resource.Type.Stylesheet, this); +} + +WebInspector.CSSStyleModelResourceBinding.prototype = { + setContent: function(resource, content, majorChange, userCallback) + { + if (this._urlToStyleSheetId[resource.url]) { + this._innerSetContent(resource.url, content, majorChange, userCallback, null); + return; + } + this._loadStyleSheetHeaders(this._innerSetContent.bind(this, resource.url, content, majorChange, userCallback)); + }, + + canSetContent: function() + { + return true; + }, + + _inspectedURLChanged: function(event) + { + // Main frame navigation - clear history. + this._urlToStyleSheetId = {}; + this._styleSheetIdToURL = {}; + }, + + _innerSetContent: function(url, content, majorChange, userCallback, error) + { + if (error) { + userCallback(error); + return; + } + + var styleSheetId = this._urlToStyleSheetId[url]; + if (!styleSheetId) { + if (userCallback) + userCallback("No stylesheet found: " + url); + return; + } + this._cssModel.setStyleSheetText(styleSheetId, content, majorChange, userCallback); + }, + + _loadStyleSheetHeaders: function(callback) + { + function didGetAllStyleSheets(error, infos) + { + if (error) { + callback(error); + return; + } + + for (var i = 0; i < infos.length; ++i) { + var info = infos[i]; + this._urlToStyleSheetId[info.sourceURL] = info.styleSheetId; + this._styleSheetIdToURL[info.styleSheetId] = info.sourceURL; + } + callback(); + } + CSSAgent.getAllStyleSheets(didGetAllStyleSheets.bind(this)); + }, + + _styleSheetChanged: function(event) + { + var styleSheetId = event.data.styleSheetId; + function setContent() + { + var url = this._styleSheetIdToURL[styleSheetId]; + if (!url) + return; + + var resource = WebInspector.resourceForURL(url); + if (!resource) + return; + + var majorChange = event.data.majorChange; + if (majorChange) + resource.addRevision(event.data.content); + } + + if (!this._styleSheetIdToURL[styleSheetId]) { + this._loadStyleSheetHeaders(setContent.bind(this)); + return; + } + setContent.call(this); + } +} + +WebInspector.CSSStyleModelResourceBinding.prototype.__proto__ = WebInspector.ResourceDomainModelBinding.prototype; + +/** + * @constructor + * @implements {CSSAgent.Dispatcher} + * @param {WebInspector.CSSStyleModel} cssModel + */ +WebInspector.CSSDispatcher = function(cssModel) +{ + this._cssModel = cssModel; +} + +WebInspector.CSSDispatcher.prototype = { + mediaQueryResultChanged: function() + { + this._cssModel.mediaQueryResultChanged(); + } +} + +/** + * @type {WebInspector.CSSStyleModel} + */ +WebInspector.cssModel = null; +/* NetworkManager.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.Object} + */ +WebInspector.NetworkManager = function() +{ + WebInspector.Object.call(this); + this._dispatcher = new WebInspector.NetworkDispatcher(this); + if (WebInspector.settings.cacheDisabled.get()) + NetworkAgent.setCacheDisabled(true); + + NetworkAgent.enable(); + + WebInspector.settings.cacheDisabled.addChangeListener(this._cacheDisabledSettingChanged, this); + + if (WebInspector.settings.userAgent.get()) + this._userAgentSettingChanged(); + WebInspector.settings.userAgent.addChangeListener(this._userAgentSettingChanged, this); +} + +WebInspector.NetworkManager.EventTypes = { + ResourceTrackingEnabled: "ResourceTrackingEnabled", + ResourceTrackingDisabled: "ResourceTrackingDisabled", + ResourceStarted: "ResourceStarted", + ResourceUpdated: "ResourceUpdated", + ResourceFinished: "ResourceFinished", + ResourceUpdateDropped: "ResourceUpdateDropped" +} + +WebInspector.NetworkManager.prototype = { + /** + * @param {WebInspector.Resource} resource + * @param {function(?string, boolean)} callback + */ + requestContent: function(resource, callback) + { + function callbackWrapper(error, content, contentEncoded) + { + if (error) + callback(null, false); + else + callback(content, content && contentEncoded); + } + // FIXME: https://bugs.webkit.org/show_bug.cgi?id=61363 We should separate NetworkResource (NetworkPanel resource) + // from ResourceRevision (ResourcesPanel/ScriptsPanel resource) and request content accordingly. + if (resource.requestId) + NetworkAgent.getResponseBody(resource.requestId, callbackWrapper); + else + PageAgent.getResourceContent(resource.frameId, resource.url, callbackWrapper); + }, + + enableResourceTracking: function() + { + function callback(error) + { + this.dispatchEventToListeners(WebInspector.NetworkManager.EventTypes.ResourceTrackingEnabled); + } + NetworkAgent.enable(callback.bind(this)); + }, + + disableResourceTracking: function() + { + function callback(error) + { + this.dispatchEventToListeners(WebInspector.NetworkManager.EventTypes.ResourceTrackingDisabled); + } + NetworkAgent.disable(callback.bind(this)); + }, + + /** + * @param {string} url + * @return {WebInspector.Resource} + */ + inflightResourceForURL: function(url) + { + return this._dispatcher._inflightResourcesByURL[url]; + }, + + /** + * @param {WebInspector.Event} event + */ + _cacheDisabledSettingChanged: function(event) + { + var enabled = /** @type {boolean} */ event.data; + NetworkAgent.setCacheDisabled(enabled); + }, + + _userAgentSettingChanged: function() + { + NetworkAgent.setUserAgentOverride(WebInspector.settings.userAgent.get()); + } +} + +WebInspector.NetworkManager.prototype.__proto__ = WebInspector.Object.prototype; + +/** + * @constructor + * @implements {NetworkAgent.Dispatcher} + */ +WebInspector.NetworkDispatcher = function(manager) +{ + this._manager = manager; + this._inflightResourcesById = {}; + this._inflightResourcesByURL = {}; + InspectorBackend.registerNetworkDispatcher(this); +} + +WebInspector.NetworkDispatcher.prototype = { + /** + * @param {WebInspector.Resource} resource + * @param {NetworkAgent.Request} request + */ + _updateResourceWithRequest: function(resource, request) + { + resource.requestMethod = request.method; + resource.requestHeaders = request.headers; + resource.requestFormData = request.postData; + }, + + /** + * @param {WebInspector.Resource} resource + * @param {NetworkAgent.Response=} response + */ + _updateResourceWithResponse: function(resource, response) + { + if (!response) + return; + + if (response.url && resource.url !== response.url) + resource.url = response.url; + resource.mimeType = response.mimeType; + resource.statusCode = response.status; + resource.statusText = response.statusText; + resource.responseHeaders = response.headers; + if (response.headersText) + resource.responseHeadersText = response.headersText; + if (response.requestHeaders) + resource.requestHeaders = response.requestHeaders; + if (response.requestHeadersText) + resource.requestHeadersText = response.requestHeadersText; + + resource.connectionReused = response.connectionReused; + resource.connectionId = response.connectionId; + + if (response.fromDiskCache) + resource.cached = true; + else + resource.timing = response.timing; + + if (!this._mimeTypeIsConsistentWithType(resource)) { + WebInspector.console.addMessage(WebInspector.ConsoleMessage.create(WebInspector.ConsoleMessage.MessageSource.Network, + WebInspector.ConsoleMessage.MessageLevel.Warning, + WebInspector.UIString("Resource interpreted as %s but transferred with MIME type %s: \"%s\".", WebInspector.Resource.Type.toUIString(resource.type), resource.mimeType, resource.url), + WebInspector.ConsoleMessage.MessageType.Log, + "", + 0, + 1, + [], + null, + resource)); + } + }, + + /** + * @param {WebInspector.Resource} resource + * @return {boolean} + */ + _mimeTypeIsConsistentWithType: function(resource) + { + // If status is an error, content is likely to be of an inconsistent type, + // as it's going to be an error message. We do not want to emit a warning + // for this, though, as this will already be reported as resource loading failure. + // Also, if a URL like http://localhost/wiki/load.php?debug=true&lang=en produces text/css and gets reloaded, + // it is 304 Not Modified and its guessed mime-type is text/php, which is wrong. + // Don't check for mime-types in 304-resources. + if (resource.hasErrorStatusCode() || resource.statusCode === 304) + return true; + + if (typeof resource.type === "undefined" + || resource.type === WebInspector.Resource.Type.Other + || resource.type === WebInspector.Resource.Type.XHR + || resource.type === WebInspector.Resource.Type.WebSocket) + return true; + + if (!resource.mimeType) + return true; // Might be not known for cached resources with null responses. + + if (resource.mimeType in WebInspector.MIMETypes) + return resource.type in WebInspector.MIMETypes[resource.mimeType]; + + return false; + }, + + /** + * @param {WebInspector.Resource} resource + * @param {?NetworkAgent.CachedResource} cachedResource + */ + _updateResourceWithCachedResource: function(resource, cachedResource) + { + resource.type = WebInspector.Resource.Type[cachedResource.type]; + resource.resourceSize = cachedResource.bodySize; + this._updateResourceWithResponse(resource, cachedResource.response); + }, + + /** + * @param {NetworkAgent.Response} response + * @return {boolean} + */ + _isNull: function(response) + { + if (!response) + return true; + return !response.status && !response.mimeType && (!response.headers || !Object.keys(response.headers).length); + }, + + /** + * @param {NetworkAgent.RequestId} requestId + * @param {NetworkAgent.FrameId} frameId + * @param {NetworkAgent.LoaderId} loaderId + * @param {string} documentURL + * @param {NetworkAgent.Request} request + * @param {NetworkAgent.Timestamp} time + * @param {NetworkAgent.Initiator} initiator + * @param {ConsoleAgent.StackTrace=} stackTrace + * @param {NetworkAgent.Response=} redirectResponse + */ + requestWillBeSent: function(requestId, frameId, loaderId, documentURL, request, time, initiator, stackTrace, redirectResponse) + { + var resource = this._inflightResourcesById[requestId]; + if (resource) { + // FIXME: move this check to the backend. + if (!redirectResponse) + return; + this.responseReceived(requestId, frameId, loaderId, time, "Other", redirectResponse); + resource = this._appendRedirect(requestId, time, request.url); + } else + resource = this._createResource(requestId, frameId, loaderId, request.url, documentURL, initiator, stackTrace); + resource.hasNetworkData = true; + this._updateResourceWithRequest(resource, request); + resource.startTime = time; + + this._startResource(resource); + }, + + /** + * @param {NetworkAgent.RequestId} requestId + */ + requestServedFromCache: function(requestId) + { + var resource = this._inflightResourcesById[requestId]; + if (!resource) + return; + + resource.cached = true; + }, + + /** + * @param {NetworkAgent.RequestId} requestId + * @param {NetworkAgent.FrameId} frameId + * @param {NetworkAgent.LoaderId} loaderId + * @param {NetworkAgent.Timestamp} time + * @param {PageAgent.ResourceType} resourceType + * @param {NetworkAgent.Response} response + */ + responseReceived: function(requestId, frameId, loaderId, time, resourceType, response) + { + // FIXME: move this check to the backend. + if (this._isNull(response)) + return; + + var resource = this._inflightResourcesById[requestId]; + if (!resource) { + // We missed the requestWillBeSent. + var eventData = {}; + eventData.url = response.url; + eventData.frameId = frameId; + eventData.loaderId = loaderId; + eventData.resourceType = resourceType; + eventData.mimeType = response.mimeType; + this._manager.dispatchEventToListeners(WebInspector.NetworkManager.EventTypes.ResourceUpdateDropped, eventData); + return; + } + + resource.responseReceivedTime = time; + resource.type = WebInspector.Resource.Type[resourceType]; + + this._updateResourceWithResponse(resource, response); + + this._updateResource(resource); + }, + + /** + * @param {NetworkAgent.RequestId} requestId + * @param {NetworkAgent.Timestamp} time + * @param {number} dataLength + * @param {number} encodedDataLength + */ + dataReceived: function(requestId, time, dataLength, encodedDataLength) + { + var resource = this._inflightResourcesById[requestId]; + if (!resource) + return; + + resource.resourceSize += dataLength; + if (encodedDataLength != -1) + resource.increaseTransferSize(encodedDataLength); + resource.endTime = time; + + this._updateResource(resource); + }, + + /** + * @param {NetworkAgent.RequestId} requestId + * @param {NetworkAgent.Timestamp} finishTime + */ + loadingFinished: function(requestId, finishTime) + { + var resource = this._inflightResourcesById[requestId]; + if (!resource) + return; + this._finishResource(resource, finishTime); + }, + + /** + * @param {NetworkAgent.RequestId} requestId + * @param {NetworkAgent.Timestamp} time + * @param {string} localizedDescription + * @param {boolean=} canceled + */ + loadingFailed: function(requestId, time, localizedDescription, canceled) + { + var resource = this._inflightResourcesById[requestId]; + if (!resource) + return; + + resource.failed = true; + resource.canceled = canceled; + resource.localizedFailDescription = localizedDescription; + this._finishResource(resource, time); + }, + + /** + * @param {NetworkAgent.RequestId} requestId + * @param {NetworkAgent.FrameId} frameId + * @param {NetworkAgent.LoaderId} loaderId + * @param {string} documentURL + * @param {NetworkAgent.Timestamp} time + * @param {NetworkAgent.Initiator} initiator + * @param {NetworkAgent.CachedResource} cachedResource + */ + requestServedFromMemoryCache: function(requestId, frameId, loaderId, documentURL, time, initiator, cachedResource) + { + var resource = this._createResource(requestId, frameId, loaderId, cachedResource.url, documentURL, initiator, null); + this._updateResourceWithCachedResource(resource, cachedResource); + resource.cached = true; + resource.requestMethod = "GET"; + this._startResource(resource); + resource.startTime = resource.responseReceivedTime = time; + this._finishResource(resource, time); + }, + + /** + * @param {NetworkAgent.RequestId} requestId + * @param {string} requestURL + */ + webSocketCreated: function(requestId, requestURL) + { + var resource = new WebInspector.Resource(requestId, requestURL, "", null); + resource.type = WebInspector.Resource.Type.WebSocket; + this._startResource(resource); + }, + + /** + * @param {NetworkAgent.RequestId} requestId + * @param {NetworkAgent.Timestamp} time + * @param {NetworkAgent.WebSocketRequest} request + */ + webSocketWillSendHandshakeRequest: function(requestId, time, request) + { + var resource = this._inflightResourcesById[requestId]; + if (!resource) + return; + + resource.requestMethod = "GET"; + resource.requestHeaders = request.headers; + resource.webSocketRequestKey3 = request.requestKey3; + resource.startTime = time; + + this._updateResource(resource); + }, + + /** + * @param {NetworkAgent.RequestId} requestId + * @param {NetworkAgent.Timestamp} time + * @param {NetworkAgent.WebSocketResponse} response + */ + webSocketHandshakeResponseReceived: function(requestId, time, response) + { + var resource = this._inflightResourcesById[requestId]; + if (!resource) + return; + + resource.statusCode = response.status; + resource.statusText = response.statusText; + resource.responseHeaders = response.headers; + resource.webSocketChallengeResponse = response.challengeResponse; + resource.responseReceivedTime = time; + + this._updateResource(resource); + }, + + /** + * @param {NetworkAgent.RequestId} requestId + * @param {NetworkAgent.Timestamp} time + */ + webSocketClosed: function(requestId, time) + { + var resource = this._inflightResourcesById[requestId]; + if (!resource) + return; + this._finishResource(resource, time); + }, + + /** + * @param {NetworkAgent.RequestId} requestId + * @param {NetworkAgent.Timestamp} time + * @param {string} redirectURL + */ + _appendRedirect: function(requestId, time, redirectURL) + { + var originalResource = this._inflightResourcesById[requestId]; + var previousRedirects = originalResource.redirects || []; + originalResource.requestId = "redirected:" + requestId + "." + previousRedirects.length; + delete originalResource.redirects; + if (previousRedirects.length > 0) + originalResource.redirectSource = previousRedirects[previousRedirects.length - 1]; + this._finishResource(originalResource, time); + var newResource = this._createResource(requestId, originalResource.frameId, originalResource.loaderId, + redirectURL, originalResource.documentURL, originalResource.initiator, originalResource.stackTrace); + newResource.redirects = previousRedirects.concat(originalResource); + return newResource; + }, + + /** + * @param {WebInspector.Resource} resource + */ + _startResource: function(resource) + { + this._inflightResourcesById[resource.requestId] = resource; + this._inflightResourcesByURL[resource.url] = resource; + this._dispatchEventToListeners(WebInspector.NetworkManager.EventTypes.ResourceStarted, resource); + }, + + /** + * @param {WebInspector.Resource} resource + */ + _updateResource: function(resource) + { + this._dispatchEventToListeners(WebInspector.NetworkManager.EventTypes.ResourceUpdated, resource); + }, + + /** + * @param {WebInspector.Resource} resource + * @param {NetworkAgent.Timestamp} finishTime + */ + _finishResource: function(resource, finishTime) + { + resource.endTime = finishTime; + resource.finished = true; + this._dispatchEventToListeners(WebInspector.NetworkManager.EventTypes.ResourceFinished, resource); + delete this._inflightResourcesById[resource.requestId]; + delete this._inflightResourcesByURL[resource.url]; + }, + + /** + * @param {string} eventType + * @param {WebInspector.Resource} resource + */ + _dispatchEventToListeners: function(eventType, resource) + { + this._manager.dispatchEventToListeners(eventType, resource); + }, + + /** + * @param {NetworkAgent.RequestId} requestId + * @param {string} frameId + * @param {NetworkAgent.LoaderId} loaderId + * @param {string} url + * @param {string} documentURL + * @param {NetworkAgent.Initiator} initiator + * @param {ConsoleAgent.StackTrace=} stackTrace + */ + _createResource: function(requestId, frameId, loaderId, url, documentURL, initiator, stackTrace) + { + var resource = new WebInspector.Resource(requestId, url, frameId, loaderId); + resource.documentURL = documentURL; + resource.initiator = initiator; + resource.stackTrace = stackTrace; + return resource; + } +} + +/** + * @type {?WebInspector.NetworkManager} + */ +WebInspector.networkManager = null; +/* NetworkLog.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + */ +WebInspector.NetworkLog = function() +{ + this._resources = []; + WebInspector.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.ResourceStarted, this._onResourceStarted, this); + WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.MainFrameNavigated, this._onMainFrameNavigated, this); + WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.OnLoad, this._onLoad, this); + WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.DOMContentLoaded, this._onDOMContentLoaded, this); +} + +WebInspector.NetworkLog.prototype = { + /** + * @return {Array.} + */ + get resources() + { + return this._resources; + }, + + /** + * @param {WebInspector.Resource} resource + * @return {WebInspector.PageLoad} + */ + pageLoadForResource: function(resource) + { + return resource.__page; + }, + + /** + * @param {WebInspector.Event} event + */ + _onMainFrameNavigated: function(event) + { + var mainFrame = /** type {WebInspector.ResourceTreeFrame} */ event.data; + // Preserve resources from the new session. + this._currentPageLoad = null; + var oldResources = this._resources.splice(0, this._resources.length); + for (var i = 0; i < oldResources.length; ++i) { + var resource = oldResources[i]; + if (resource.loaderId === mainFrame.loaderId) { + if (!this._currentPageLoad) + this._currentPageLoad = new WebInspector.PageLoad(resource); + this._resources.push(resource); + resource.__page = this._currentPageLoad; + } + } + }, + + /** + * @param {WebInspector.Event} event + */ + _onResourceStarted: function(event) + { + var resource = /** @type {WebInspector.Resource} */ event.data; + this._resources.push(resource); + resource.__page = this._currentPageLoad; + }, + + /** + * @param {WebInspector.Event} event + */ + _onDOMContentLoaded: function(event) + { + if (this._currentPageLoad) + this._currentPageLoad.contentLoadTime = event.data; + }, + + /** + * @param {WebInspector.Event} event + */ + _onLoad: function(event) + { + if (this._currentPageLoad) + this._currentPageLoad.loadTime = event.data; + }, + +} + +/** + * @type {WebInspector.NetworkLog} + */ +WebInspector.networkLog = null; + +/** + * @constructor + * @param {WebInspector.Resource} mainResource + */ +WebInspector.PageLoad = function(mainResource) +{ + this.id = ++WebInspector.PageLoad._lastIdentifier; + this.url = mainResource.url; + this.startTime = mainResource.startTime; +} + +WebInspector.PageLoad._lastIdentifier = 0; +/* ResourceTreeModel.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.Object} + * @param {WebInspector.NetworkManager} networkManager + */ +WebInspector.ResourceTreeModel = function(networkManager) +{ + networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.ResourceTrackingEnabled, this._onResourceTrackingEnabled, this); + networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.ResourceUpdated, this._onResourceUpdated, this); + networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.ResourceFinished, this._onResourceUpdated, this); + networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.ResourceUpdateDropped, this._onResourceUpdateDropped, this); + + WebInspector.console.addEventListener(WebInspector.ConsoleModel.Events.MessageAdded, this._consoleMessageAdded, this); + WebInspector.console.addEventListener(WebInspector.ConsoleModel.Events.RepeatCountUpdated, this._consoleMessageAdded, this); + WebInspector.console.addEventListener(WebInspector.ConsoleModel.Events.ConsoleCleared, this._consoleCleared, this); + + PageAgent.enable(); + + NetworkAgent.enable(); + this._fetchResourceTree(); + + InspectorBackend.registerPageDispatcher(new WebInspector.PageDispatcher(this)); + + this._pendingConsoleMessages = {}; +} + +WebInspector.ResourceTreeModel.EventTypes = { + FrameAdded: "FrameAdded", + FrameNavigated: "FrameNavigated", + FrameDetached: "FrameDetached", + MainFrameNavigated: "MainFrameNavigated", + ResourceAdded: "ResourceAdded", + ResourceContentCommitted: "resource-content-committed", + WillLoadCachedResources: "WillLoadCachedResources", + CachedResourcesLoaded: "CachedResourcesLoaded", + DOMContentLoaded: "DOMContentLoaded", + OnLoad: "OnLoad", + InspectedURLChanged: "InspectedURLChanged" +} + +WebInspector.ResourceTreeModel.prototype = { + _onResourceTrackingEnabled: function() + { + this._fetchResourceTree(); + }, + + _fetchResourceTree: function() + { + this._frames = {}; + delete this._cachedResourcesProcessed; + PageAgent.getResourceTree(this._processCachedResources.bind(this)); + }, + + _processCachedResources: function(error, mainFramePayload) + { + if (error) { + console.error(JSON.stringify(error)); + return; + } + + this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.WillLoadCachedResources); + WebInspector.inspectedPageURL = mainFramePayload.frame.url; + this._addFramesRecursively(null, mainFramePayload); + this._dispatchInspectedURLChanged(); + this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.CachedResourcesLoaded); + WebInspector.Resource.restoreRevisions(); + + this._cachedResourcesProcessed = true; + }, + + _dispatchInspectedURLChanged: function() + { + InspectorFrontendHost.inspectedURLChanged(WebInspector.inspectedPageURL); + this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.InspectedURLChanged, WebInspector.inspectedPageURL); + }, + + /** + * @param {WebInspector.ResourceTreeFrame} frame + */ + _addFrame: function(frame) + { + this._frames[frame.id] = frame; + if (frame.isMainFrame()) + this.mainFrame = frame; + this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.FrameAdded, frame); + }, + + /** + * @param {PageAgent.Frame} framePayload + */ + _frameNavigated: function(framePayload) + { + if (this._frontendReused(framePayload)) + return; + + // Do nothing unless cached resource tree is processed - it will overwrite everything. + if (!this._cachedResourcesProcessed) + return; + var frame = this._frames[framePayload.id]; + if (frame) { + // Navigation within existing frame. + frame._navigate(framePayload); + } else { + // Either a new frame or a main frame navigation to the new backend process. + var parentFrame = this._frames[framePayload.parentId]; + frame = new WebInspector.ResourceTreeFrame(this, parentFrame, framePayload); + if (frame.isMainFrame() && this.mainFrame) { + // Definitely a navigation to the new backend process. + this._frameDetached(this.mainFrame.id); + } + this._addFrame(frame); + } + + if (frame.isMainFrame()) + WebInspector.inspectedPageURL = frame.url; + + this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.FrameNavigated, frame); + if (frame.isMainFrame()) + this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.MainFrameNavigated, frame); + + // Fill frame with retained resources (the ones loaded using new loader). + var resources = frame.resources(); + for (var i = 0; i < resources.length; ++i) + this.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.ResourceAdded, resources[i]); + + if (frame.isMainFrame()) + this._dispatchInspectedURLChanged(); + }, + + /** + * @param {PageAgent.Frame} framePayload + * @return {boolean} + */ + _frontendReused: function(framePayload) + { + if (!framePayload.parentId && !WebInspector.networkLog.resources.length) { + // We are navigating main frame to the existing loaded backend (no provisioual loaded resources are there). + this._fetchResourceTree(); + return true; + } + return false; + }, + + /** + * @param {NetworkAgent.FrameId} frameId + */ + _frameDetached: function(frameId) + { + // Do nothing unless cached resource tree is processed - it will overwrite everything. + if (!this._cachedResourcesProcessed) + return; + + var frame = this._frames[frameId]; + if (!frame) + return; + + if (frame.parentFrame) + frame.parentFrame._removeChildFrame(frame); + else + frame._remove(); + }, + + /** + * @param {WebInspector.Event} event + */ + _onResourceUpdated: function(event) + { + if (!this._cachedResourcesProcessed) + return; + + var resource = /** @type {WebInspector.Resource} */ event.data; + this._addPendingConsoleMessagesToResource(resource); + + if (resource.failed || resource.type === WebInspector.Resource.Type.XHR) + return; + + var frame = this._frames[resource.frameId]; + if (frame) + frame._addResource(resource); + }, + + /** + * @param {WebInspector.Event} event + */ + _onResourceUpdateDropped: function(event) + { + if (!this._cachedResourcesProcessed) + return; + + var frameId = event.data.frameId; + var frame = this._frames[frameId]; + if (!frame) + return; + + var url = event.data.url; + if (frame._resourcesMap[url]) + return; + + var resource = this._createResource(url, frame.url, frameId, event.data.loaderId); + resource.type = WebInspector.Resource.Type[event.data.resourceType]; + resource.mimeType = event.data.mimeType; + resource.finished = true; + frame._addResource(resource); + }, + + /** + * @param {NetworkAgent.FrameId} frameId + */ + frameForId: function(frameId) + { + return this._frames[frameId]; + }, + + /** + * @param {function(WebInspector.Resource)} callback + * @return {boolean} + */ + forAllResources: function(callback) + { + if (this.mainFrame) + return this.mainFrame._callForFrameResources(callback); + return false; + }, + + /** + * @param {WebInspector.Event} event + */ + _consoleMessageAdded: function(event) + { + var msg = /** @type {WebInspector.ConsoleMessage} */ event.data; + var resource = msg.url ? this.resourceForURL(msg.url) : null; + if (resource) + this._addConsoleMessageToResource(msg, resource); + else + this._addPendingConsoleMessage(msg); + }, + + /** + * @param {WebInspector.ConsoleMessage} msg + */ + _addPendingConsoleMessage: function(msg) + { + if (!msg.url) + return; + if (!this._pendingConsoleMessages[msg.url]) + this._pendingConsoleMessages[msg.url] = []; + this._pendingConsoleMessages[msg.url].push(msg); + }, + + /** + * @param {WebInspector.Resource} resource + */ + _addPendingConsoleMessagesToResource: function(resource) + { + var messages = this._pendingConsoleMessages[resource.url]; + if (messages) { + for (var i = 0; i < messages.length; i++) + this._addConsoleMessageToResource(messages[i], resource); + delete this._pendingConsoleMessages[resource.url]; + } + }, + + /** + * @param {WebInspector.ConsoleMessage} msg + * @param {WebInspector.Resource} resource + */ + _addConsoleMessageToResource: function(msg, resource) + { + switch (msg.level) { + case WebInspector.ConsoleMessage.MessageLevel.Warning: + resource.warnings += msg.repeatDelta; + break; + case WebInspector.ConsoleMessage.MessageLevel.Error: + resource.errors += msg.repeatDelta; + break; + } + resource.addMessage(msg); + }, + + _consoleCleared: function() + { + function callback(resource) + { + resource.clearErrorsAndWarnings(); + } + + this._pendingConsoleMessages = {}; + this.forAllResources(callback); + }, + + /** + * @param {string} url + * @return {WebInspector.Resource} + */ + resourceForURL: function(url) + { + // Workers call into this with no frames available. + return this.mainFrame ? this.mainFrame.resourceForURL(url) : null; + }, + + /** + * @param {WebInspector.ResourceTreeFrame} parentFrame + * @param {PageAgent.FrameResourceTree} frameTreePayload + */ + _addFramesRecursively: function(parentFrame, frameTreePayload) + { + var framePayload = frameTreePayload.frame; + var frame = new WebInspector.ResourceTreeFrame(this, parentFrame, framePayload); + + // Create frame resource. + var frameResource = this._createResourceFromFramePayload(framePayload, framePayload.url); + frameResource.mimeType = framePayload.mimeType; + frameResource.type = WebInspector.Resource.Type.Document; + frameResource.finished = true; + + if (frame.isMainFrame()) + WebInspector.inspectedPageURL = frameResource.url; + + this._addFrame(frame); + frame._addResource(frameResource); + + for (var i = 0; frameTreePayload.childFrames && i < frameTreePayload.childFrames.length; ++i) + this._addFramesRecursively(frame, frameTreePayload.childFrames[i]); + + if (!frameTreePayload.resources) + return; + + // Create frame subresources. + for (var i = 0; i < frameTreePayload.resources.length; ++i) { + var subresource = frameTreePayload.resources[i]; + var resource = this._createResourceFromFramePayload(framePayload, subresource.url); + resource.type = WebInspector.Resource.Type[subresource.type]; + resource.mimeType = subresource.mimeType; + resource.finished = true; + frame._addResource(resource); + } + }, + + /** + * @param {PageAgent.Frame} frame + * @param {string} url + * @return {WebInspector.Resource} + */ + _createResourceFromFramePayload: function(frame, url) + { + return this._createResource(url, frame.url, frame.id, frame.loaderId); + }, + + /** + * @param {string} url + * @param {string} documentURL + * @param {NetworkAgent.FrameId} frameId + * @param {NetworkAgent.LoaderId} loaderId + * @return {WebInspector.Resource} + */ + _createResource: function(url, documentURL, frameId, loaderId) + { + var resource = new WebInspector.Resource("", url, frameId, loaderId); + resource.documentURL = documentURL; + return resource; + } +} + +WebInspector.ResourceTreeModel.prototype.__proto__ = WebInspector.Object.prototype; + +/** + * @constructor + * @param {WebInspector.ResourceTreeModel} model + * @param {?WebInspector.ResourceTreeFrame} parentFrame + * @param {PageAgent.Frame} payload + */ +WebInspector.ResourceTreeFrame = function(model, parentFrame, payload) +{ + this._model = model; + this._parentFrame = parentFrame; + + this._id = payload.id; + this._loaderId = payload.loaderId; + this._name = payload.name; + this._url = payload.url; + this._securityOrigin = payload.securityOrigin; + this._mimeType = payload.mimeType; + + /** + * @type {Array.} + */ + this._childFrames = []; + + /** + * @type {Object.} + */ + this._resourcesMap = {}; + + if (this._parentFrame) + this._parentFrame._childFrames.push(this); +} + +WebInspector.ResourceTreeFrame.prototype = { + /** + * @type {string} + */ + get id() + { + return this._id; + }, + + /** + * @type {string} + */ + get name() + { + return this._name || ""; + }, + + /** + * @type {string} + */ + get url() + { + return this._url; + }, + + /** + * @type {string} + */ + get securityOrigin() + { + return this._securityOrigin; + }, + + /** + * @type {string} + */ + get loaderId() + { + return this._loaderId; + }, + + /** + * @type {WebInspector.ResourceTreeFrame} + */ + get parentFrame() + { + return this._parentFrame; + }, + + /** + * @type {Array.} + */ + get childFrames() + { + return this._childFrames; + }, + + /** + * @return {boolean} + */ + isMainFrame: function() + { + return !this._parentFrame; + }, + + /** + * @param {PageAgent.Frame} framePayload + */ + _navigate: function(framePayload) + { + this._loaderId = framePayload.loaderId; + this._name = framePayload.name; + this._url = framePayload.url; + this._securityOrigin = framePayload.securityOrigin; + this._mimeType = framePayload.mimeType; + + var mainResource = this._resourcesMap[this._url]; + this._resourcesMap = {}; + this._removeChildFrames(); + if (mainResource && mainResource.loaderId === this._loaderId) + this._addResource(mainResource); + }, + + /** + * @type {WebInspector.Resource} + */ + get mainResource() + { + return this._resourcesMap[this._url]; + }, + + /** + * @param {WebInspector.ResourceTreeFrame} frame + */ + _removeChildFrame: function(frame) + { + this._childFrames.remove(frame); + frame._remove(); + }, + + _removeChildFrames: function() + { + var copy = this._childFrames.slice(); + for (var i = 0; i < copy.length; ++i) + this._removeChildFrame(copy[i]); + }, + + _remove: function() + { + this._removeChildFrames(); + delete this._model._frames[this.id]; + this._model.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.FrameDetached, this); + }, + + /** + * @param {WebInspector.Resource} resource + */ + _addResource: function(resource) + { + if (this._resourcesMap[resource.url] === resource) { + // Already in the tree, we just got an extra update. + return; + } + this._resourcesMap[resource.url] = resource; + this._model.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.ResourceAdded, resource); + }, + + /** + * @return {Array.} + */ + resources: function() + { + var result = []; + for (var url in this._resourcesMap) + result.push(this._resourcesMap[url]); + return result; + }, + + /** + * @param {string} url + * @return {?WebInspector.Resource} + */ + resourceForURL: function(url) + { + var result; + function filter(resource) + { + if (resource.url === url) { + result = resource; + return true; + } + } + this._callForFrameResources(filter); + return result; + }, + + /** + * @param {function(WebInspector.Resource)} callback + * @return {boolean} + */ + _callForFrameResources: function(callback) + { + for (var url in this._resourcesMap) { + if (callback(this._resourcesMap[url])) + return true; + } + + for (var i = 0; i < this._childFrames.length; ++i) { + if (this._childFrames[i]._callForFrameResources(callback)) + return true; + } + return false; + } +} + +/** + * @constructor + * @implements {PageAgent.Dispatcher} + */ +WebInspector.PageDispatcher = function(resourceTreeModel) +{ + this._resourceTreeModel = resourceTreeModel; +} + +WebInspector.PageDispatcher.prototype = { + domContentEventFired: function(time) + { + this._resourceTreeModel.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.DOMContentLoaded, time); + }, + + loadEventFired: function(time) + { + this._resourceTreeModel.dispatchEventToListeners(WebInspector.ResourceTreeModel.EventTypes.OnLoad, time); + }, + + frameNavigated: function(frame) + { + this._resourceTreeModel._frameNavigated(frame); + }, + + frameDetached: function(frameId) + { + this._resourceTreeModel._frameDetached(frameId); + } +} + +/** + * @type {WebInspector.ResourceTreeModel} + */ +WebInspector.resourceTreeModel = null; +/* ResourceUtils.js */ + +/* + * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2007 Matt Lilek (pewtermoose@gmail.com). + * Copyright (C) 2009 Joseph Pecoraro + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @param {string} url + * @return {?WebInspector.Resource} + */ +WebInspector.resourceForURL = function(url) +{ + return WebInspector.resourceTreeModel.resourceForURL(url); +} + +/** + * @param {function(WebInspector.Resource)} callback + */ +WebInspector.forAllResources = function(callback) +{ + WebInspector.resourceTreeModel.forAllResources(callback); +} + +/** + * @param {string} url + * @return {string} + */ +WebInspector.displayNameForURL = function(url) +{ + if (!url) + return ""; + + var resource = WebInspector.resourceForURL(url); + if (resource) + return resource.displayName; + + if (!WebInspector.inspectedPageURL) + return url.trimURL(""); + + var parsedURL = WebInspector.inspectedPageURL.asParsedURL(); + var lastPathComponent = parsedURL.lastPathComponent; + var index = WebInspector.inspectedPageURL.indexOf(lastPathComponent); + if (index !== -1 && index + lastPathComponent.length === WebInspector.inspectedPageURL.length) { + var baseURL = WebInspector.inspectedPageURL.substring(0, index); + if (url.indexOf(baseURL) === 0) + return url.substring(index); + } + + return url.trimURL(parsedURL.host); +} + +/** + * @param {string} string + * @param {function(string,string,string=):Node} linkifier + * @return {DocumentFragment} + */ +WebInspector.linkifyStringAsFragmentWithCustomLinkifier = function(string, linkifier) +{ + var container = document.createDocumentFragment(); + var linkStringRegEx = /(?:[a-zA-Z][a-zA-Z0-9+.-]{2,}:\/\/|www\.)[\w$\-_+*'=\|\/\\(){}[\]%@&#~,:;.!?]{2,}[\w$\-_+*=\|\/\\({%@&#~]/; + var lineColumnRegEx = /:(\d+)(:(\d+))?$/; + + while (string) { + var linkString = linkStringRegEx.exec(string); + if (!linkString) + break; + + linkString = linkString[0]; + var linkIndex = string.indexOf(linkString); + var nonLink = string.substring(0, linkIndex); + container.appendChild(document.createTextNode(nonLink)); + + var title = linkString; + var realURL = (linkString.indexOf("www.") === 0 ? "http://" + linkString : linkString); + var lineColumnMatch = lineColumnRegEx.exec(realURL); + if (lineColumnMatch) + realURL = realURL.substring(0, realURL.length - lineColumnMatch[0].length); + + var linkNode = linkifier(title, realURL, lineColumnMatch ? lineColumnMatch[1] : undefined); + container.appendChild(linkNode); + string = string.substring(linkIndex + linkString.length, string.length); + } + + if (string) + container.appendChild(document.createTextNode(string)); + + return container; +} + +WebInspector._linkifierPlugins = []; + +/** + * @param {function(string):string} plugin + */ +WebInspector.registerLinkifierPlugin = function(plugin) +{ + WebInspector._linkifierPlugins.push(plugin); +} + +/** + * @param {string} string + * @return {DocumentFragment} + */ +WebInspector.linkifyStringAsFragment = function(string) +{ + function linkifier(title, url, lineNumber) + { + for (var i = 0; i < WebInspector._linkifierPlugins.length; ++i) + title = WebInspector._linkifierPlugins[i](title); + + var isExternal = !WebInspector.resourceForURL(url); + var urlNode = WebInspector.linkifyURLAsNode(url, title, undefined, isExternal); + if (typeof(lineNumber) !== "undefined") { + urlNode.lineNumber = lineNumber; + urlNode.preferredPanel = "scripts"; + } + + return urlNode; + } + + return WebInspector.linkifyStringAsFragmentWithCustomLinkifier(string, linkifier); +} + +/** + * @param {string} url + * @param {string=} linkText + * @param {string=} classes + * @param {boolean=} isExternal + * @param {string=} tooltipText + * @return {Element} + */ +WebInspector.linkifyURLAsNode = function(url, linkText, classes, isExternal, tooltipText) +{ + if (!linkText) + linkText = url; + classes = (classes ? classes + " " : ""); + classes += isExternal ? "webkit-html-external-link" : "webkit-html-resource-link"; + + var a = document.createElement("a"); + a.href = url; + a.className = classes; + if (typeof tooltipText === "undefined") + a.title = url; + else if (typeof tooltipText !== "string" || tooltipText.length) + a.title = tooltipText; + a.textContent = linkText; + a.style.maxWidth = "100%"; + if (isExternal) + a.setAttribute("target", "_blank"); + + return a; +} + +/** + * @param {string} url + * @param {number=} lineNumber + * @return {string} + */ +WebInspector.formatLinkText = function(url, lineNumber) +{ + var text = WebInspector.displayNameForURL(url); + if (typeof lineNumber === "number") + text += ":" + (lineNumber + 1); + return text; +} + +/** + * @param {string} url + * @param {number} lineNumber + * @param {string=} classes + * @param {string=} tooltipText + * @return {Element} + */ +WebInspector.linkifyResourceAsNode = function(url, lineNumber, classes, tooltipText) +{ + var linkText = WebInspector.formatLinkText(url, lineNumber); + var anchor = WebInspector.linkifyURLAsNode(url, linkText, classes, false, tooltipText); + anchor.preferredPanel = "resources"; + anchor.lineNumber = lineNumber; + return anchor; +} + +/** + * @param {WebInspector.Resource} request + * @param {string=} classes + * @return {Element} + */ +WebInspector.linkifyRequestAsNode = function(request, classes) +{ + var anchor = WebInspector.linkifyURLAsNode(request.url); + anchor.preferredPanel = "network"; + anchor.requestId = request.requestId; + return anchor; +} + +/** + * @return {?string} null if the specified resource MUST NOT have a URL (e.g. "javascript:...") + */ +WebInspector.resourceURLForRelatedNode = function(node, url) +{ + if (!url || url.indexOf("://") > 0) + return url; + + if (url.trim().indexOf("javascript:") === 0) + return null; // Do not provide a resource URL for security. + + 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 + var resourceURL = url; + function callback(resource) + { + if (resource.path === url) { + resourceURL = resource.url; + return true; + } + } + WebInspector.forAllResources(callback); + return resourceURL; +} + +/** + * @param {string} baseURL + * @param {string} href + * @return {?string} + */ +WebInspector.completeURL = function(baseURL, href) +{ + if (href) { + // Return absolute URLs as-is. + var parsedHref = href.asParsedURL(); + if (parsedHref && parsedHref.scheme) + return href; + + // Return special URLs as-is. + var trimmedHref = href.trim(); + if (trimmedHref.indexOf("data:") === 0 || trimmedHref.indexOf("javascript:") === 0) + return href; + } + + var parsedURL = baseURL.asParsedURL(); + if (parsedURL) { + var path = href; + if (path.charAt(0) !== "/") { + var basePath = parsedURL.path; + // A href of "?foo=bar" implies "basePath?foo=bar". + // With "basePath?a=b" and "?foo=bar" we should get "basePath?foo=bar". + var prefix; + if (path.charAt(0) === "?") { + var basePathCutIndex = basePath.indexOf("?"); + if (basePathCutIndex !== -1) + prefix = basePath.substring(0, basePathCutIndex); + else + prefix = basePath; + } else + prefix = basePath.substring(0, basePath.lastIndexOf("/")) + "/"; + + path = prefix + path; + } else if (path.length > 1 && path.charAt(1) === "/") { + // href starts with "//" which is a full URL with the protocol dropped (use the baseURL protocol). + return parsedURL.scheme + ":" + path; + } + return parsedURL.scheme + "://" + parsedURL.host + (parsedURL.port ? (":" + parsedURL.port) : "") + path; + } + return null; +} +/* ResourceCategory.js */ + +/* + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + */ +WebInspector.ResourceCategory = function(name, title, color) +{ + this.name = name; + this._title = title; + this.color = color; +} + +WebInspector.ResourceCategory.prototype = { + /** + * @return {string} + */ + toString: function() + { + return this.title; + }, + + /** + * @return {string} + */ + get title() + { + return WebInspector.UIString(this._title); + } +} + +WebInspector.resourceCategories = { + documents: new WebInspector.ResourceCategory("documents", "Documents", "rgb(47,102,236)"), + stylesheets: new WebInspector.ResourceCategory("stylesheets", "Stylesheets", "rgb(157,231,119)"), + images: new WebInspector.ResourceCategory("images", "Images", "rgb(164,60,255)"), + scripts: new WebInspector.ResourceCategory("scripts", "Scripts", "rgb(255,121,0)"), + xhr: new WebInspector.ResourceCategory("xhr", "XHR", "rgb(231,231,10)"), + fonts: new WebInspector.ResourceCategory("fonts", "Fonts", "rgb(255,82,62)"), + websockets: new WebInspector.ResourceCategory("websockets", "WebSockets", "rgb(186,186,186)"), // FIXME: Decide the color. + other: new WebInspector.ResourceCategory("other", "Other", "rgb(186,186,186)") +} +/* TimelineManager.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.Object} + */ +WebInspector.TimelineManager = function() +{ + WebInspector.Object.call(this); + this._dispatcher = new WebInspector.TimelineDispatcher(this); + this._enablementCount = 0; +} + +WebInspector.TimelineManager.EventTypes = { + TimelineStarted: "TimelineStarted", + TimelineStopped: "TimelineStopped", + TimelineEventRecorded: "TimelineEventRecorded" +} + +WebInspector.TimelineManager.prototype = { + /** + * @param {number=} maxCallStackDepth + */ + start: function(maxCallStackDepth) + { + this._enablementCount++; + if (this._enablementCount === 1) + TimelineAgent.start(maxCallStackDepth, this._started.bind(this)); + }, + + stop: function() + { + if (!this._enablementCount) { + console.error("WebInspector.TimelineManager start/stop calls are unbalanced"); + return; + } + this._enablementCount--; + if (!this._enablementCount) + TimelineAgent.stop(this._stopped.bind(this)); + }, + + _started: function() + { + this.dispatchEventToListeners(WebInspector.TimelineManager.EventTypes.TimelineStarted); + }, + + _stopped: function() + { + this.dispatchEventToListeners(WebInspector.TimelineManager.EventTypes.TimelineStopped); + } +} + +WebInspector.TimelineManager.prototype.__proto__ = WebInspector.Object.prototype; + +/** + * @constructor + * @implements {TimelineAgent.Dispatcher} + */ +WebInspector.TimelineDispatcher = function(manager) +{ + this._manager = manager; + InspectorBackend.registerTimelineDispatcher(this); +} + +WebInspector.TimelineDispatcher.prototype = { + eventRecorded: function(record) + { + this._manager.dispatchEventToListeners(WebInspector.TimelineManager.EventTypes.TimelineEventRecorded, record); + } +} + +/** + * @type {WebInspector.TimelineManager} + */ +WebInspector.timelineManager; +/* Database.js */ + +/* + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + */ +WebInspector.Database = function(id, domain, name, version) +{ + this._id = id; + this._domain = domain; + this._name = name; + this._version = version; +} + +WebInspector.Database.prototype = { + /** @return {string} */ + get id() + { + return this._id; + }, + + /** @return {string} */ + get name() + { + return this._name; + }, + + set name(x) + { + this._name = x; + }, + + /** @return {string} */ + get version() + { + return this._version; + }, + + set version(x) + { + this._version = x; + }, + + /** @return {string} */ + get domain() + { + return this._domain; + }, + + set domain(x) + { + this._domain = x; + }, + + /** @return {string} */ + get displayDomain() + { + return WebInspector.Resource.prototype.__lookupGetter__("displayDomain").call(this); + }, + + /** + * @param {function(Array.)} callback + */ + getTableNames: function(callback) + { + function sortingCallback(error, names) + { + if (!error) + callback(names.sort()); + } + DatabaseAgent.getDatabaseTableNames(this._id, sortingCallback); + }, + + /** + * @param {string} query + * @param {function(Array., Array.<*>)} onSuccess + * @param {function(DatabaseAgent.Error)} onError + */ + executeSql: function(query, onSuccess, onError) + { + function callback(error, success, transactionId) + { + if (error) { + onError(error); + return; + } + if (!success) { + onError(WebInspector.UIString("Database not found.")); + return; + } + WebInspector.DatabaseDispatcher._callbacks[transactionId] = {"onSuccess": onSuccess, "onError": onError}; + } + DatabaseAgent.executeSQL(this._id, query, callback); + } +} + +/** + * @constructor + * @implements {DatabaseAgent.Dispatcher} + */ +WebInspector.DatabaseDispatcher = function() +{ +} + +WebInspector.DatabaseDispatcher._callbacks = {}; + +WebInspector.DatabaseDispatcher.prototype = { + /** + * @param {DatabaseAgent.Database} payload + */ + addDatabase: function(payload) + { + var database = new WebInspector.Database( + payload.id, + payload.domain, + payload.name, + payload.version); + WebInspector.panels.resources.addDatabase(database); + }, + + /** + * @param {number} transactionId + * @param {Array.} columnNames + * @param {Array.<*>} values + */ + sqlTransactionSucceeded: function(transactionId, columnNames, values) + { + if (!WebInspector.DatabaseDispatcher._callbacks[transactionId]) + return; + + var callback = WebInspector.DatabaseDispatcher._callbacks[transactionId]["onSuccess"]; + delete WebInspector.DatabaseDispatcher._callbacks[transactionId]; + if (callback) + callback(columnNames, values); + }, + + /** + * @param {number} transactionId + * @param {?DatabaseAgent.Error} errorObj + */ + sqlTransactionFailed: function(transactionId, errorObj) + { + if (!WebInspector.DatabaseDispatcher._callbacks[transactionId]) + return; + + var callback = WebInspector.DatabaseDispatcher._callbacks[transactionId]["onError"]; + delete WebInspector.DatabaseDispatcher._callbacks[transactionId]; + if (callback) + callback(errorObj); + } +} +/* DOMStorage.js */ + +/* + * Copyright (C) 2008 Nokia Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + */ +WebInspector.DOMStorage = function(id, domain, isLocalStorage) +{ + this._id = id; + this._domain = domain; + this._isLocalStorage = isLocalStorage; +} + +WebInspector.DOMStorage.prototype = { + /** @return {string} */ + get id() + { + return this._id; + }, + + /** @return {string} */ + get domain() + { + return this._domain; + }, + + /** @return {boolean} */ + get isLocalStorage() + { + return this._isLocalStorage; + }, + + /** + * @param {function(?Protocol.Error, Array.):void=} callback + */ + getEntries: function(callback) + { + DOMStorageAgent.getDOMStorageEntries(this._id, callback); + }, + + /** + * @param {string} key + * @param {string} value + * @param {function(?Protocol.Error, boolean):void=} callback + */ + setItem: function(key, value, callback) + { + DOMStorageAgent.setDOMStorageItem(this._id, key, value, callback); + }, + + /** + * @param {string} key + * @param {function(?Protocol.Error, boolean):void=} callback + */ + removeItem: function(key, callback) + { + DOMStorageAgent.removeDOMStorageItem(this._id, key, callback); + } +} + +/** + * @constructor + * @implements {DOMStorageAgent.Dispatcher} + */ +WebInspector.DOMStorageDispatcher = function() +{ +} + +WebInspector.DOMStorageDispatcher.prototype = { + + /** + * @param {DOMStorageAgent.Entry} payload + */ + addDOMStorage: function(payload) + { + var domStorage = new WebInspector.DOMStorage( + payload.id, + payload.host, + payload.isLocalStorage); + WebInspector.panels.resources.addDOMStorage(domStorage); + }, + + /** + * @param {number} storageId + */ + updateDOMStorage: function(storageId) + { + WebInspector.panels.resources.updateDOMStorage(storageId); + } +} +/* DOMStorageItemsView.js */ + +/* + * Copyright (C) 2008 Nokia Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.View} + */ +WebInspector.DOMStorageItemsView = function(domStorage) +{ + WebInspector.View.call(this); + + this.domStorage = domStorage; + + this.element.addStyleClass("storage-view"); + this.element.addStyleClass("table"); + + this.deleteButton = new WebInspector.StatusBarButton(WebInspector.UIString("Delete"), "delete-storage-status-bar-item"); + this.deleteButton.visible = false; + this.deleteButton.addEventListener("click", this._deleteButtonClicked, this); + + this.refreshButton = new WebInspector.StatusBarButton(WebInspector.UIString("Refresh"), "refresh-storage-status-bar-item"); + this.refreshButton.addEventListener("click", this._refreshButtonClicked, this); +} + +WebInspector.DOMStorageItemsView.prototype = { + get statusBarItems() + { + return [this.refreshButton.element, this.deleteButton.element]; + }, + + wasShown: function() + { + this.update(); + }, + + willHide: function() + { + this.deleteButton.visible = false; + }, + + update: function() + { + this.detachChildViews(); + this.domStorage.getEntries(this._showDOMStorageEntries.bind(this)); + }, + + _showDOMStorageEntries: function(error, entries) + { + if (error) + return; + + this._dataGrid = this._dataGridForDOMStorageEntries(entries); + this._dataGrid.show(this.element); + this._dataGrid.autoSizeColumns(10); + this.deleteButton.visible = true; + }, + + _dataGridForDOMStorageEntries: function(entries) + { + var columns = {}; + columns[0] = {}; + columns[1] = {}; + columns[0].title = WebInspector.UIString("Key"); + columns[1].title = WebInspector.UIString("Value"); + + var nodes = []; + + var keys = []; + var length = entries.length; + for (var i = 0; i < entries.length; i++) { + var data = {}; + + var key = entries[i][0]; + data[0] = key; + var value = entries[i][1]; + data[1] = value; + var node = new WebInspector.DataGridNode(data, false); + node.selectable = true; + nodes.push(node); + keys.push(key); + } + + var dataGrid = new WebInspector.DataGrid(columns, this._editingCallback.bind(this), this._deleteCallback.bind(this)); + length = nodes.length; + for (var i = 0; i < length; ++i) + dataGrid.appendChild(nodes[i]); + dataGrid.addCreationNode(false); + if (length > 0) + nodes[0].selected = true; + return dataGrid; + }, + + _deleteButtonClicked: function(event) + { + if (!this._dataGrid || !this._dataGrid.selectedNode) + return; + + this._deleteCallback(this._dataGrid.selectedNode); + }, + + _refreshButtonClicked: function(event) + { + this.update(); + }, + + _editingCallback: function(editingNode, columnIdentifier, oldText, newText) + { + var domStorage = this.domStorage; + if (columnIdentifier === 0) { + if (oldText) + domStorage.removeItem(oldText); + + domStorage.setItem(newText, editingNode.data[1]); + } else { + domStorage.setItem(editingNode.data[0], newText); + } + + this.update(); + }, + + _deleteCallback: function(node) + { + if (!node || node.isCreationNode) + return; + + if (this.domStorage) + this.domStorage.removeItem(node.data[0]); + + this.update(); + } +} + +WebInspector.DOMStorageItemsView.prototype.__proto__ = WebInspector.View.prototype; +/* DataGrid.js */ + +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.View} + * @param {function(WebInspector.DataGridNode, number, string, string)=} editCallback + * @param {function(WebInspector.DataGridNode)=} deleteCallback + */ +WebInspector.DataGrid = function(columns, editCallback, deleteCallback) +{ + WebInspector.View.call(this); + this.registerRequiredCSS("dataGrid.css"); + + this.element.className = "data-grid"; + this.element.tabIndex = 0; + this.element.addEventListener("keydown", this._keyDown.bind(this), false); + + this._headerTable = document.createElement("table"); + this._headerTable.className = "header"; + this._headerTableHeaders = {}; + + this._dataTable = document.createElement("table"); + this._dataTable.className = "data"; + + this._dataTable.addEventListener("mousedown", this._mouseDownInDataTable.bind(this), true); + this._dataTable.addEventListener("click", this._clickInDataTable.bind(this), true); + + this._dataTable.addEventListener("contextmenu", this._contextMenuInDataTable.bind(this), true); + + // FIXME: Add a createCallback which is different from editCallback and has different + // behavior when creating a new node. + if (editCallback) { + this._dataTable.addEventListener("dblclick", this._ondblclick.bind(this), false); + this._editCallback = editCallback; + } + if (deleteCallback) + this._deleteCallback = deleteCallback; + + this.aligned = {}; + + this._scrollContainer = document.createElement("div"); + this._scrollContainer.className = "data-container"; + this._scrollContainer.appendChild(this._dataTable); + + this.element.appendChild(this._headerTable); + this.element.appendChild(this._scrollContainer); + + var headerRow = document.createElement("tr"); + var columnGroup = document.createElement("colgroup"); + this._columnCount = 0; + + for (var columnIdentifier in columns) { + var column = columns[columnIdentifier]; + if (column.disclosure) + this.disclosureColumnIdentifier = columnIdentifier; + + var col = document.createElement("col"); + if (column.width) + col.style.width = column.width; + column.element = col; + columnGroup.appendChild(col); + + var cell = document.createElement("th"); + cell.className = columnIdentifier + "-column"; + cell.columnIdentifier = columnIdentifier; + this._headerTableHeaders[columnIdentifier] = cell; + + var div = document.createElement("div"); + if (column.titleDOMFragment) + div.appendChild(column.titleDOMFragment); + else + div.textContent = column.title; + cell.appendChild(div); + + if (column.sort) { + cell.addStyleClass("sort-" + column.sort); + this._sortColumnCell = cell; + } + + if (column.sortable) { + cell.addEventListener("click", this._clickInHeaderCell.bind(this), false); + cell.addStyleClass("sortable"); + } + + if (column.aligned) + this.aligned[columnIdentifier] = column.aligned; + + headerRow.appendChild(cell); + + ++this._columnCount; + } + + columnGroup.span = this._columnCount; + + var cell = document.createElement("th"); + cell.className = "corner"; + headerRow.appendChild(cell); + + this._headerTableColumnGroup = columnGroup; + this._headerTable.appendChild(this._headerTableColumnGroup); + this.headerTableBody.appendChild(headerRow); + + var fillerRow = document.createElement("tr"); + fillerRow.className = "filler"; + + for (var columnIdentifier in columns) { + var column = columns[columnIdentifier]; + var td = document.createElement("td"); + td.className = columnIdentifier + "-column"; + fillerRow.appendChild(td); + } + + this._dataTableColumnGroup = columnGroup.cloneNode(true); + this._dataTable.appendChild(this._dataTableColumnGroup); + this.dataTableBody.appendChild(fillerRow); + + this.columns = columns || {}; + this._columnsArray = []; + for (var columnIdentifier in columns) { + columns[columnIdentifier].ordinal = this._columnsArray.length; + columns[columnIdentifier].identifier = columnIdentifier; + this._columnsArray.push(columns[columnIdentifier]); + } + + for (var i = 0; i < this._columnsArray.length; ++i) + this._columnsArray[i].bodyElement = this._dataTableColumnGroup.children[i]; + + this.children = []; + this.selectedNode = null; + this.expandNodesWhenArrowing = false; + this.root = true; + this.hasChildren = false; + this.expanded = true; + this.revealed = true; + this.selected = false; + this.dataGrid = this; + this.indentWidth = 15; + this.resizers = []; + this._columnWidthsInitialized = false; +} + +/** + * @param {Array.} columnNames + * @param {Array.} values + */ +WebInspector.DataGrid.createSortableDataGrid = function(columnNames, values) +{ + var numColumns = columnNames.length; + if (!numColumns) + return null; + + var columns = {}; + + for (var i = 0; i < columnNames.length; ++i) { + var column = {}; + column.width = columnNames[i].length; + column.title = columnNames[i]; + column.sortable = true; + + columns[columnNames[i]] = column; + } + + var nodes = []; + for (var i = 0; i < values.length / numColumns; ++i) { + var data = {}; + for (var j = 0; j < columnNames.length; ++j) + data[columnNames[j]] = values[numColumns * i + j]; + + var node = new WebInspector.DataGridNode(data, false); + node.selectable = false; + nodes.push(node); + } + + var dataGrid = new WebInspector.DataGrid(columns); + var length = nodes.length; + for (var i = 0; i < length; ++i) + dataGrid.appendChild(nodes[i]); + + dataGrid.addEventListener("sorting changed", sortDataGrid, this); + + function sortDataGrid() + { + var nodes = dataGrid.children.slice(); + var sortColumnIdentifier = dataGrid.sortColumnIdentifier; + var sortDirection = dataGrid.sortOrder === "ascending" ? 1 : -1; + var columnIsNumeric = true; + + for (var i = 0; i < nodes.length; i++) { + if (isNaN(Number(nodes[i].data[sortColumnIdentifier]))) + columnIsNumeric = false; + } + + function comparator(dataGridNode1, dataGridNode2) + { + var item1 = dataGridNode1.data[sortColumnIdentifier]; + var item2 = dataGridNode2.data[sortColumnIdentifier]; + + var comparison; + if (columnIsNumeric) { + // Sort numbers based on comparing their values rather than a lexicographical comparison. + var number1 = parseFloat(item1); + var number2 = parseFloat(item2); + comparison = number1 < number2 ? -1 : (number1 > number2 ? 1 : 0); + } else + comparison = item1 < item2 ? -1 : (item1 > item2 ? 1 : 0); + + return sortDirection * comparison; + } + + nodes.sort(comparator); + dataGrid.removeChildren(); + for (var i = 0; i < nodes.length; i++) + dataGrid.appendChild(nodes[i]); + } + return dataGrid; +} + +WebInspector.DataGrid.prototype = { + get refreshCallback() + { + return this._refreshCallback; + }, + + set refreshCallback(refreshCallback) + { + this._refreshCallback = refreshCallback; + }, + + _ondblclick: function(event) + { + if (this._editing || this._editingNode) + return; + + this._startEditing(event.target); + }, + + _startEditingColumnOfDataGridNode: function(node, column) + { + this._editing = true; + this._editingNode = node; + this._editingNode.select(); + + var element = this._editingNode._element.children[column]; + WebInspector.startEditing(element, this._startEditingConfig(element)); + window.getSelection().setBaseAndExtent(element, 0, element, 1); + }, + + _startEditing: function(target) + { + var element = target.enclosingNodeOrSelfWithNodeName("td"); + if (!element) + return; + + this._editingNode = this.dataGridNodeFromNode(target); + if (!this._editingNode) { + if (!this.creationNode) + return; + this._editingNode = this.creationNode; + } + + // Force editing the 1st column when editing the creation node + if (this._editingNode.isCreationNode) + return this._startEditingColumnOfDataGridNode(this._editingNode, 0); + + this._editing = true; + WebInspector.startEditing(element, this._startEditingConfig(element)); + + window.getSelection().setBaseAndExtent(element, 0, element, 1); + }, + + + _startEditingConfig: function(element) + { + return new WebInspector.EditingConfig(this._editingCommitted.bind(this), this._editingCancelled.bind(this), element.textContent); + }, + + _editingCommitted: function(element, newText, oldText, context, moveDirection) + { + // FIXME: We need more column identifiers here throughout this function. + // Not needed yet since only editable DataGrid is DOM Storage, which is Key - Value. + + // FIXME: Better way to do this than regular expressions? + var columnIdentifier = parseInt(element.className.match(/\b(\d+)-column\b/)[1], 10); + + var textBeforeEditing = this._editingNode.data[columnIdentifier]; + var currentEditingNode = this._editingNode; + + function moveToNextIfNeeded(wasChange) { + if (!moveDirection) + return; + + if (moveDirection === "forward") { + if (currentEditingNode.isCreationNode && columnIdentifier === 0 && !wasChange) + return; + + if (columnIdentifier === 0) + return this._startEditingColumnOfDataGridNode(currentEditingNode, 1); + + var nextDataGridNode = currentEditingNode.traverseNextNode(true, null, true); + if (nextDataGridNode) + return this._startEditingColumnOfDataGridNode(nextDataGridNode, 0); + if (currentEditingNode.isCreationNode && wasChange) { + this.addCreationNode(false); + return this._startEditingColumnOfDataGridNode(this.creationNode, 0); + } + return; + } + + if (moveDirection === "backward") { + if (columnIdentifier === 1) + return this._startEditingColumnOfDataGridNode(currentEditingNode, 0); + var nextDataGridNode = currentEditingNode.traversePreviousNode(true, null, true); + + if (nextDataGridNode) + return this._startEditingColumnOfDataGridNode(nextDataGridNode, 1); + return; + } + } + + if (textBeforeEditing == newText) { + this._editingCancelled(element); + moveToNextIfNeeded.call(this, false); + return; + } + + // Update the text in the datagrid that we typed + this._editingNode.data[columnIdentifier] = newText; + + // Make the callback - expects an editing node (table row), the column number that is being edited, + // the text that used to be there, and the new text. + this._editCallback(this._editingNode, columnIdentifier, textBeforeEditing, newText); + + if (this._editingNode.isCreationNode) + this.addCreationNode(false); + + this._editingCancelled(element); + moveToNextIfNeeded.call(this, true); + }, + + _editingCancelled: function(element) + { + delete this._editing; + this._editingNode = null; + }, + + get sortColumnIdentifier() + { + if (!this._sortColumnCell) + return null; + return this._sortColumnCell.columnIdentifier; + }, + + get sortOrder() + { + if (!this._sortColumnCell || this._sortColumnCell.hasStyleClass("sort-ascending")) + return "ascending"; + if (this._sortColumnCell.hasStyleClass("sort-descending")) + return "descending"; + return null; + }, + + get headerTableBody() + { + if ("_headerTableBody" in this) + return this._headerTableBody; + + this._headerTableBody = this._headerTable.getElementsByTagName("tbody")[0]; + if (!this._headerTableBody) { + this._headerTableBody = this.element.ownerDocument.createElement("tbody"); + this._headerTable.insertBefore(this._headerTableBody, this._headerTable.tFoot); + } + + return this._headerTableBody; + }, + + get dataTableBody() + { + if ("_dataTableBody" in this) + return this._dataTableBody; + + this._dataTableBody = this._dataTable.getElementsByTagName("tbody")[0]; + if (!this._dataTableBody) { + this._dataTableBody = this.element.ownerDocument.createElement("tbody"); + this._dataTable.insertBefore(this._dataTableBody, this._dataTable.tFoot); + } + + return this._dataTableBody; + }, + + /** + * @param {number=} maxDescentLevel + */ + autoSizeColumns: function(minPercent, maxPercent, maxDescentLevel) + { + if (minPercent) + minPercent = Math.min(minPercent, Math.floor(100 / this._columnCount)); + var widths = {}; + var columns = this.columns; + for (var columnIdentifier in columns) + widths[columnIdentifier] = (columns[columnIdentifier].title || "").length; + + var children = maxDescentLevel ? this._enumerateChildren(this, [], maxDescentLevel + 1) : this.children; + for (var i = 0; i < children.length; ++i) { + var node = children[i]; + for (var columnIdentifier in columns) { + var text = node.data[columnIdentifier] || ""; + if (text.length > widths[columnIdentifier]) + widths[columnIdentifier] = text.length; + } + } + + var totalColumnWidths = 0; + for (var columnIdentifier in columns) + totalColumnWidths += widths[columnIdentifier]; + + var recoupPercent = 0; + for (var columnIdentifier in columns) { + var width = Math.round(100 * widths[columnIdentifier] / totalColumnWidths); + if (minPercent && width < minPercent) { + recoupPercent += (minPercent - width); + width = minPercent; + } else if (maxPercent && width > maxPercent) { + recoupPercent -= (width - maxPercent); + width = maxPercent; + } + widths[columnIdentifier] = width; + } + + while (minPercent && recoupPercent > 0) { + for (var columnIdentifier in columns) { + if (widths[columnIdentifier] > minPercent) { + --widths[columnIdentifier]; + --recoupPercent; + if (!recoupPercent) + break; + } + } + } + + while (maxPercent && recoupPercent < 0) { + for (var columnIdentifier in columns) { + if (widths[columnIdentifier] < maxPercent) { + ++widths[columnIdentifier]; + ++recoupPercent; + if (!recoupPercent) + break; + } + } + } + + for (var columnIdentifier in columns) + columns[columnIdentifier].element.style.width = widths[columnIdentifier] + "%"; + this._columnWidthsInitialized = false; + this.updateWidths(); + }, + + _enumerateChildren: function(rootNode, result, maxLevel) + { + if (!rootNode.root) + result.push(rootNode); + if (!maxLevel) + return; + for (var i = 0; i < rootNode.children.length; ++i) + this._enumerateChildren(rootNode.children[i], result, maxLevel - 1); + return result; + }, + + onResize: function() + { + this.updateWidths(); + }, + + // Updates the widths of the table, including the positions of the column + // resizers. + // + // IMPORTANT: This function MUST be called once after the element of the + // DataGrid is attached to its parent element and every subsequent time the + // width of the parent element is changed in order to make it possible to + // resize the columns. + // + // If this function is not called after the DataGrid is attached to its + // parent element, then the DataGrid's columns will not be resizable. + updateWidths: function() + { + var headerTableColumns = this._headerTableColumnGroup.children; + + var tableWidth = this._dataTable.offsetWidth; + var numColumns = headerTableColumns.length; + + // Do not attempt to use offsetes if we're not attached to the document tree yet. + if (!this._columnWidthsInitialized && this.element.offsetWidth) { + // Give all the columns initial widths now so that during a resize, + // when the two columns that get resized get a percent value for + // their widths, all the other columns already have percent values + // for their widths. + for (var i = 0; i < numColumns; i++) { + var columnWidth = this.headerTableBody.rows[0].cells[i].offsetWidth; + var percentWidth = ((columnWidth / tableWidth) * 100) + "%"; + this._headerTableColumnGroup.children[i].style.width = percentWidth; + this._dataTableColumnGroup.children[i].style.width = percentWidth; + } + this._columnWidthsInitialized = true; + } + this._positionResizers(); + this.dispatchEventToListeners("width changed"); + }, + + columnWidthsMap: function() + { + var result = {}; + for (var i = 0; i < this._columnsArray.length; ++i) { + var width = this._headerTableColumnGroup.children[i].style.width; + result[this._columnsArray[i].columnIdentifier] = parseFloat(width); + } + return result; + }, + + applyColumnWidthsMap: function(columnWidthsMap) + { + for (var columnIdentifier in this.columns) { + var column = this.columns[columnIdentifier]; + var width = (columnWidthsMap[columnIdentifier] || 0) + "%"; + this._headerTableColumnGroup.children[column.ordinal].style.width = width; + this._dataTableColumnGroup.children[column.ordinal].style.width = width; + } + + // Normalize widths + delete this._columnWidthsInitialized; + this.updateWidths(); + }, + + isColumnVisible: function(columnIdentifier) + { + var column = this.columns[columnIdentifier]; + var columnElement = column.element; + return !columnElement.hidden; + }, + + showColumn: function(columnIdentifier) + { + var column = this.columns[columnIdentifier]; + var columnElement = column.element; + if (!columnElement.hidden) + return; + + columnElement.hidden = false; + columnElement.removeStyleClass("hidden"); + + var columnBodyElement = column.bodyElement; + columnBodyElement.hidden = false; + columnBodyElement.removeStyleClass("hidden"); + }, + + hideColumn: function(columnIdentifier) + { + var column = this.columns[columnIdentifier]; + var columnElement = column.element; + if (columnElement.hidden) + return; + + var oldWidth = parseFloat(columnElement.style.width); + + columnElement.hidden = true; + columnElement.addStyleClass("hidden"); + columnElement.style.width = 0; + + var columnBodyElement = column.bodyElement; + columnBodyElement.hidden = true; + columnBodyElement.addStyleClass("hidden"); + columnBodyElement.style.width = 0; + + this._columnWidthsInitialized = false; + }, + + get scrollContainer() + { + return this._scrollContainer; + }, + + isScrolledToLastRow: function() + { + return this._scrollContainer.isScrolledToBottom(); + }, + + scrollToLastRow: function() + { + this._scrollContainer.scrollTop = this._scrollContainer.scrollHeight - this._scrollContainer.offsetHeight; + }, + + _positionResizers: function() + { + var headerTableColumns = this._headerTableColumnGroup.children; + var numColumns = headerTableColumns.length; + var left = 0; + var previousResizer = null; + + // Make n - 1 resizers for n columns. + for (var i = 0; i < numColumns - 1; i++) { + var resizer = this.resizers[i]; + + if (!resizer) { + // This is the first call to updateWidth, so the resizers need + // to be created. + resizer = document.createElement("div"); + resizer.addStyleClass("data-grid-resizer"); + // This resizer is associated with the column to its right. + resizer.addEventListener("mousedown", this._startResizerDragging.bind(this), false); + this.element.appendChild(resizer); + this.resizers[i] = resizer; + } + + // Get the width of the cell in the first (and only) row of the + // header table in order to determine the width of the column, since + // it is not possible to query a column for its width. + left += this.headerTableBody.rows[0].cells[i].offsetWidth; + + var columnIsVisible = !this._headerTableColumnGroup.children[i].hidden; + if (columnIsVisible) { + resizer.style.removeProperty("display"); + resizer.style.left = left + "px"; + resizer.leftNeighboringColumnID = i; + if (previousResizer) + previousResizer.rightNeighboringColumnID = i; + previousResizer = resizer; + } else { + resizer.style.setProperty("display", "none"); + resizer.leftNeighboringColumnID = 0; + resizer.rightNeighboringColumnID = 0; + } + } + if (previousResizer) + previousResizer.rightNeighboringColumnID = numColumns - 1; + }, + + addCreationNode: function(hasChildren) + { + if (this.creationNode) + this.creationNode.makeNormal(); + + var emptyData = {}; + for (var column in this.columns) + emptyData[column] = ''; + this.creationNode = new WebInspector.CreationDataGridNode(emptyData, hasChildren); + this.appendChild(this.creationNode); + }, + + appendChild: function(child) + { + this.insertChild(child, this.children.length); + }, + + insertChild: function(child, index) + { + if (!child) + throw("insertChild: Node can't be undefined or null."); + if (child.parent === this) + throw("insertChild: Node is already a child of this node."); + + if (child.parent) + child.parent.removeChild(child); + + this.children.splice(index, 0, child); + this.hasChildren = true; + + child.parent = this; + child.dataGrid = this.dataGrid; + child._recalculateSiblings(index); + + delete child._depth; + delete child._revealed; + delete child._attached; + child._shouldRefreshChildren = true; + + var current = child.children[0]; + while (current) { + current.dataGrid = this.dataGrid; + delete current._depth; + delete current._revealed; + delete current._attached; + current._shouldRefreshChildren = true; + current = current.traverseNextNode(false, child, true); + } + + if (this.expanded) + child._attach(); + }, + + removeChild: function(child) + { + if (!child) + throw("removeChild: Node can't be undefined or null."); + if (child.parent !== this) + throw("removeChild: Node is not a child of this node."); + + child.deselect(); + child._detach(); + + this.children.remove(child, true); + + if (child.previousSibling) + child.previousSibling.nextSibling = child.nextSibling; + if (child.nextSibling) + child.nextSibling.previousSibling = child.previousSibling; + + child.dataGrid = null; + child.parent = null; + child.nextSibling = null; + child.previousSibling = null; + + if (this.children.length <= 0) + this.hasChildren = false; + }, + + removeChildren: function() + { + for (var i = 0; i < this.children.length; ++i) { + var child = this.children[i]; + child.deselect(); + child._detach(); + + child.dataGrid = null; + child.parent = null; + child.nextSibling = null; + child.previousSibling = null; + } + + this.children = []; + this.hasChildren = false; + }, + + removeChildrenRecursive: function() + { + var childrenToRemove = this.children; + + var child = this.children[0]; + while (child) { + if (child.children.length) + childrenToRemove = childrenToRemove.concat(child.children); + child = child.traverseNextNode(false, this, true); + } + + for (var i = 0; i < childrenToRemove.length; ++i) { + child = childrenToRemove[i]; + child.deselect(); + child._detach(); + + child.children = []; + child.dataGrid = null; + child.parent = null; + child.nextSibling = null; + child.previousSibling = null; + } + + this.children = []; + }, + + sortNodes: function(comparator, reverseMode) + { + function comparatorWrapper(a, b) + { + if (a._dataGridNode._data.summaryRow) + return 1; + if (b._dataGridNode._data.summaryRow) + return -1; + + var aDataGirdNode = a._dataGridNode; + var bDataGirdNode = b._dataGridNode; + return reverseMode ? comparator(bDataGirdNode, aDataGirdNode) : comparator(aDataGirdNode, bDataGirdNode); + } + + var tbody = this.dataTableBody; + var tbodyParent = tbody.parentElement; + tbodyParent.removeChild(tbody); + + var childNodes = tbody.childNodes; + var fillerRow = childNodes[childNodes.length - 1]; + + var sortedRows = Array.prototype.slice.call(childNodes, 0, childNodes.length - 1); + sortedRows.sort(comparatorWrapper); + var sortedRowsLength = sortedRows.length; + + tbody.removeChildren(); + var previousSiblingNode = null; + for (var i = 0; i < sortedRowsLength; ++i) { + var row = sortedRows[i]; + var node = row._dataGridNode; + node.previousSibling = previousSiblingNode; + if (previousSiblingNode) + previousSiblingNode.nextSibling = node; + tbody.appendChild(row); + previousSiblingNode = node; + } + if (previousSiblingNode) + previousSiblingNode.nextSibling = null; + + tbody.appendChild(fillerRow); + tbodyParent.appendChild(tbody); + }, + + _keyDown: function(event) + { + if (!this.selectedNode || event.shiftKey || event.metaKey || event.ctrlKey || this._editing) + return; + + var handled = false; + var nextSelectedNode; + if (event.keyIdentifier === "Up" && !event.altKey) { + nextSelectedNode = this.selectedNode.traversePreviousNode(true); + while (nextSelectedNode && !nextSelectedNode.selectable) + nextSelectedNode = nextSelectedNode.traversePreviousNode(true); + handled = nextSelectedNode ? true : false; + } else if (event.keyIdentifier === "Down" && !event.altKey) { + nextSelectedNode = this.selectedNode.traverseNextNode(true); + while (nextSelectedNode && !nextSelectedNode.selectable) + nextSelectedNode = nextSelectedNode.traverseNextNode(true); + handled = nextSelectedNode ? true : false; + } else if (event.keyIdentifier === "Left") { + if (this.selectedNode.expanded) { + if (event.altKey) + this.selectedNode.collapseRecursively(); + else + this.selectedNode.collapse(); + handled = true; + } else if (this.selectedNode.parent && !this.selectedNode.parent.root) { + handled = true; + if (this.selectedNode.parent.selectable) { + nextSelectedNode = this.selectedNode.parent; + handled = nextSelectedNode ? true : false; + } else if (this.selectedNode.parent) + this.selectedNode.parent.collapse(); + } + } else if (event.keyIdentifier === "Right") { + if (!this.selectedNode.revealed) { + this.selectedNode.reveal(); + handled = true; + } else if (this.selectedNode.hasChildren) { + handled = true; + if (this.selectedNode.expanded) { + nextSelectedNode = this.selectedNode.children[0]; + handled = nextSelectedNode ? true : false; + } else { + if (event.altKey) + this.selectedNode.expandRecursively(); + else + this.selectedNode.expand(); + } + } + } else if (event.keyCode === 8 || event.keyCode === 46) { + if (this._deleteCallback) { + handled = true; + this._deleteCallback(this.selectedNode); + } + } else if (isEnterKey(event)) { + if (this._editCallback) { + handled = true; + // The first child of the selected element is the , + // and that's what we want to edit. + this._startEditing(this.selectedNode._element.children[0]); + } + } + + if (nextSelectedNode) { + nextSelectedNode.reveal(); + nextSelectedNode.select(); + } + + if (handled) { + event.preventDefault(); + event.stopPropagation(); + } + }, + + expand: function() + { + // This is the root, do nothing. + }, + + collapse: function() + { + // This is the root, do nothing. + }, + + reveal: function() + { + // This is the root, do nothing. + }, + + revealAndSelect: function() + { + // This is the root, do nothing. + }, + + dataGridNodeFromNode: function(target) + { + var rowElement = target.enclosingNodeOrSelfWithNodeName("tr"); + return rowElement && rowElement._dataGridNode; + }, + + dataGridNodeFromPoint: function(x, y) + { + var node = this._dataTable.ownerDocument.elementFromPoint(x, y); + var rowElement = node.enclosingNodeOrSelfWithNodeName("tr"); + return rowElement && rowElement._dataGridNode; + }, + + _clickInHeaderCell: function(event) + { + var cell = event.target.enclosingNodeOrSelfWithNodeName("th"); + if (!cell || !cell.columnIdentifier || !cell.hasStyleClass("sortable")) + return; + + var sortOrder = this.sortOrder; + + if (this._sortColumnCell) + this._sortColumnCell.removeMatchingStyleClasses("sort-\\w+"); + + if (cell == this._sortColumnCell) { + if (sortOrder === "ascending") + sortOrder = "descending"; + else + sortOrder = "ascending"; + } + + this._sortColumnCell = cell; + + cell.addStyleClass("sort-" + sortOrder); + + this.dispatchEventToListeners("sorting changed"); + }, + + markColumnAsSortedBy: function(columnIdentifier, sortOrder) + { + if (this._sortColumnCell) + this._sortColumnCell.removeMatchingStyleClasses("sort-\\w+"); + this._sortColumnCell = this._headerTableHeaders[columnIdentifier]; + this._sortColumnCell.addStyleClass("sort-" + sortOrder); + }, + + headerTableHeader: function(columnIdentifier) + { + return this._headerTableHeaders[columnIdentifier]; + }, + + _mouseDownInDataTable: function(event) + { + var gridNode = this.dataGridNodeFromNode(event.target); + if (!gridNode || !gridNode.selectable) + return; + + if (gridNode.isEventWithinDisclosureTriangle(event)) + return; + + if (event.metaKey) { + if (gridNode.selected) + gridNode.deselect(); + else + gridNode.select(); + } else + gridNode.select(); + }, + + _contextMenuInDataTable: function(event) + { + var contextMenu = new WebInspector.ContextMenu(); + + var gridNode = this.dataGridNodeFromNode(event.target); + if (this.dataGrid._refreshCallback && (!gridNode || gridNode !== this.creationNode)) + contextMenu.appendItem(WebInspector.UIString("Refresh"), this._refreshCallback.bind(this)); + + if (gridNode && gridNode.selectable && !gridNode.isEventWithinDisclosureTriangle(event)) { + // FIXME: Use the column names for Editing, instead of just "Edit". + if (this.dataGrid._editCallback) { + if (gridNode === this.creationNode) + contextMenu.appendItem(WebInspector.UIString("Add New"), this._startEditing.bind(this, event.target)); + else + contextMenu.appendItem(WebInspector.UIString("Edit"), this._startEditing.bind(this, event.target)); + } + if (this.dataGrid._deleteCallback && gridNode !== this.creationNode) + contextMenu.appendItem(WebInspector.UIString("Delete"), this._deleteCallback.bind(this, gridNode)); + } + + contextMenu.show(event); + }, + + _clickInDataTable: function(event) + { + var gridNode = this.dataGridNodeFromNode(event.target); + if (!gridNode || !gridNode.hasChildren) + return; + + if (!gridNode.isEventWithinDisclosureTriangle(event)) + return; + + if (gridNode.expanded) { + if (event.altKey) + gridNode.collapseRecursively(); + else + gridNode.collapse(); + } else { + if (event.altKey) + gridNode.expandRecursively(); + else + gridNode.expand(); + } + }, + + get resizeMethod() + { + if (typeof this._resizeMethod === "undefined") + return WebInspector.DataGrid.ResizeMethod.Nearest; + return this._resizeMethod; + }, + + set resizeMethod(method) + { + this._resizeMethod = method; + }, + + _startResizerDragging: function(event) + { + this._currentResizer = event.target; + if (!this._currentResizer.rightNeighboringColumnID) + return; + WebInspector.elementDragStart(this._currentResizer, this._resizerDragging.bind(this), + this._endResizerDragging.bind(this), event, "col-resize"); + }, + + _resizerDragging: function(event) + { + var resizer = this._currentResizer; + if (!resizer) + return; + + // Constrain the dragpoint to be within the containing div of the + // datagrid. + var dragPoint = event.clientX - this.element.totalOffsetLeft(); + // Constrain the dragpoint to be within the space made up by the + // column directly to the left and the column directly to the right. + var leftCellIndex = resizer.leftNeighboringColumnID; + var rightCellIndex = resizer.rightNeighboringColumnID; + var firstRowCells = this.headerTableBody.rows[0].cells; + var leftEdgeOfPreviousColumn = 0; + for (var i = 0; i < leftCellIndex; i++) + leftEdgeOfPreviousColumn += firstRowCells[i].offsetWidth; + + // Differences for other resize methods + if (this.resizeMethod == WebInspector.DataGrid.ResizeMethod.Last) { + rightCellIndex = this.resizers.length; + } else if (this.resizeMethod == WebInspector.DataGrid.ResizeMethod.First) { + leftEdgeOfPreviousColumn += firstRowCells[leftCellIndex].offsetWidth - firstRowCells[0].offsetWidth; + leftCellIndex = 0; + } + + var rightEdgeOfNextColumn = leftEdgeOfPreviousColumn + firstRowCells[leftCellIndex].offsetWidth + firstRowCells[rightCellIndex].offsetWidth; + + // Give each column some padding so that they don't disappear. + var leftMinimum = leftEdgeOfPreviousColumn + this.ColumnResizePadding; + var rightMaximum = rightEdgeOfNextColumn - this.ColumnResizePadding; + + dragPoint = Number.constrain(dragPoint, leftMinimum, rightMaximum); + + resizer.style.left = (dragPoint - this.CenterResizerOverBorderAdjustment) + "px"; + + var percentLeftColumn = (((dragPoint - leftEdgeOfPreviousColumn) / this._dataTable.offsetWidth) * 100) + "%"; + this._headerTableColumnGroup.children[leftCellIndex].style.width = percentLeftColumn; + this._dataTableColumnGroup.children[leftCellIndex].style.width = percentLeftColumn; + + var percentRightColumn = (((rightEdgeOfNextColumn - dragPoint) / this._dataTable.offsetWidth) * 100) + "%"; + this._headerTableColumnGroup.children[rightCellIndex].style.width = percentRightColumn; + this._dataTableColumnGroup.children[rightCellIndex].style.width = percentRightColumn; + + this._positionResizers(); + event.preventDefault(); + this.dispatchEventToListeners("width changed"); + }, + + _endResizerDragging: function(event) + { + WebInspector.elementDragEnd(event); + this._currentResizer = null; + this.dispatchEventToListeners("width changed"); + }, + + ColumnResizePadding: 10, + + CenterResizerOverBorderAdjustment: 3, +} + +WebInspector.DataGrid.ResizeMethod = { + Nearest: "nearest", + First: "first", + Last: "last" +} + +WebInspector.DataGrid.prototype.__proto__ = WebInspector.View.prototype; + +/** + * @constructor + * @extends {WebInspector.Object} + * @param {boolean=} hasChildren + */ +WebInspector.DataGridNode = function(data, hasChildren) +{ + this._expanded = false; + this._selected = false; + this._shouldRefreshChildren = true; + this._data = data || {}; + this.hasChildren = hasChildren || false; + this.children = []; + this.dataGrid = null; + this.parent = null; + this.previousSibling = null; + this.nextSibling = null; + this.disclosureToggleWidth = 10; +} + +WebInspector.DataGridNode.prototype = { + selectable: true, + + get element() + { + if (this._element) + return this._element; + + if (!this.dataGrid) + return null; + + this._element = document.createElement("tr"); + this._element._dataGridNode = this; + + if (this.hasChildren) + this._element.addStyleClass("parent"); + if (this.expanded) + this._element.addStyleClass("expanded"); + if (this.selected) + this._element.addStyleClass("selected"); + if (this.revealed) + this._element.addStyleClass("revealed"); + + this.createCells(); + return this._element; + }, + + createCells: function() + { + for (var columnIdentifier in this.dataGrid.columns) { + var cell = this.createCell(columnIdentifier); + this._element.appendChild(cell); + } + }, + + get data() + { + return this._data; + }, + + set data(x) + { + this._data = x || {}; + this.refresh(); + }, + + get revealed() + { + if ("_revealed" in this) + return this._revealed; + + var currentAncestor = this.parent; + while (currentAncestor && !currentAncestor.root) { + if (!currentAncestor.expanded) { + this._revealed = false; + return false; + } + + currentAncestor = currentAncestor.parent; + } + + this._revealed = true; + return true; + }, + + set hasChildren(x) + { + if (this._hasChildren === x) + return; + + this._hasChildren = x; + + if (!this._element) + return; + + if (this._hasChildren) + { + this._element.addStyleClass("parent"); + if (this.expanded) + this._element.addStyleClass("expanded"); + } + else + { + this._element.removeStyleClass("parent"); + this._element.removeStyleClass("expanded"); + } + }, + + get hasChildren() + { + return this._hasChildren; + }, + + set revealed(x) + { + if (this._revealed === x) + return; + + this._revealed = x; + + if (this._element) { + if (this._revealed) + this._element.addStyleClass("revealed"); + else + this._element.removeStyleClass("revealed"); + } + + for (var i = 0; i < this.children.length; ++i) + this.children[i].revealed = x && this.expanded; + }, + + get depth() + { + if ("_depth" in this) + return this._depth; + if (this.parent && !this.parent.root) + this._depth = this.parent.depth + 1; + else + this._depth = 0; + return this._depth; + }, + + get leftPadding() + { + if (typeof(this._leftPadding) === "number") + return this._leftPadding; + + this._leftPadding = this.depth * this.dataGrid.indentWidth; + return this._leftPadding; + }, + + get shouldRefreshChildren() + { + return this._shouldRefreshChildren; + }, + + set shouldRefreshChildren(x) + { + this._shouldRefreshChildren = x; + if (x && this.expanded) + this.expand(); + }, + + get selected() + { + return this._selected; + }, + + set selected(x) + { + if (x) + this.select(); + else + this.deselect(); + }, + + get expanded() + { + return this._expanded; + }, + + set expanded(x) + { + if (x) + this.expand(); + else + this.collapse(); + }, + + refresh: function() + { + if (!this._element || !this.dataGrid) + return; + + this._element.removeChildren(); + this.createCells(); + }, + + createCell: function(columnIdentifier) + { + var cell = document.createElement("td"); + cell.className = columnIdentifier + "-column"; + + var alignment = this.dataGrid.aligned[columnIdentifier]; + if (alignment) + cell.addStyleClass(alignment); + + var div = document.createElement("div"); + div.textContent = this.data[columnIdentifier]; + cell.appendChild(div); + + if (columnIdentifier === this.dataGrid.disclosureColumnIdentifier) { + cell.addStyleClass("disclosure"); + if (this.leftPadding) + cell.style.setProperty("padding-left", this.leftPadding + "px"); + } + + return cell; + }, + + // Share these functions with DataGrid. They are written to work with a DataGridNode this object. + appendChild: WebInspector.DataGrid.prototype.appendChild, + insertChild: WebInspector.DataGrid.prototype.insertChild, + removeChild: WebInspector.DataGrid.prototype.removeChild, + removeChildren: WebInspector.DataGrid.prototype.removeChildren, + removeChildrenRecursive: WebInspector.DataGrid.prototype.removeChildrenRecursive, + + _recalculateSiblings: function(myIndex) + { + if (!this.parent) + return; + + var previousChild = (myIndex > 0 ? this.parent.children[myIndex - 1] : null); + + if (previousChild) { + previousChild.nextSibling = this; + this.previousSibling = previousChild; + } else + this.previousSibling = null; + + var nextChild = this.parent.children[myIndex + 1]; + + if (nextChild) { + nextChild.previousSibling = this; + this.nextSibling = nextChild; + } else + this.nextSibling = null; + }, + + collapse: function() + { + if (this._element) + this._element.removeStyleClass("expanded"); + + this._expanded = false; + + for (var i = 0; i < this.children.length; ++i) + this.children[i].revealed = false; + + this.dispatchEventToListeners("collapsed"); + }, + + collapseRecursively: function() + { + var item = this; + while (item) { + if (item.expanded) + item.collapse(); + item = item.traverseNextNode(false, this, true); + } + }, + + expand: function() + { + if (!this.hasChildren || this.expanded) + return; + + if (this.revealed && !this._shouldRefreshChildren) + for (var i = 0; i < this.children.length; ++i) + this.children[i].revealed = true; + + if (this._shouldRefreshChildren) { + for (var i = 0; i < this.children.length; ++i) + this.children[i]._detach(); + + this.dispatchEventToListeners("populate"); + + if (this._attached) { + for (var i = 0; i < this.children.length; ++i) { + var child = this.children[i]; + if (this.revealed) + child.revealed = true; + child._attach(); + } + } + + delete this._shouldRefreshChildren; + } + + if (this._element) + this._element.addStyleClass("expanded"); + + this._expanded = true; + + this.dispatchEventToListeners("expanded"); + }, + + expandRecursively: function() + { + var item = this; + while (item) { + item.expand(); + item = item.traverseNextNode(false, this); + } + }, + + reveal: function() + { + var currentAncestor = this.parent; + while (currentAncestor && !currentAncestor.root) { + if (!currentAncestor.expanded) + currentAncestor.expand(); + currentAncestor = currentAncestor.parent; + } + + this.element.scrollIntoViewIfNeeded(false); + + this.dispatchEventToListeners("revealed"); + }, + + /** + * @param {boolean=} supressSelectedEvent + */ + select: function(supressSelectedEvent) + { + if (!this.dataGrid || !this.selectable || this.selected) + return; + + if (this.dataGrid.selectedNode) + this.dataGrid.selectedNode.deselect(); + + this._selected = true; + this.dataGrid.selectedNode = this; + + if (this._element) + this._element.addStyleClass("selected"); + + if (!supressSelectedEvent) + this.dispatchEventToListeners("selected"); + }, + + revealAndSelect: function() + { + this.reveal(); + this.select(); + }, + + /** + * @param {boolean=} supressDeselectedEvent + */ + deselect: function(supressDeselectedEvent) + { + if (!this.dataGrid || this.dataGrid.selectedNode !== this || !this.selected) + return; + + this._selected = false; + this.dataGrid.selectedNode = null; + + if (this._element) + this._element.removeStyleClass("selected"); + + if (!supressDeselectedEvent) + this.dispatchEventToListeners("deselected"); + }, + + traverseNextNode: function(skipHidden, stayWithin, dontPopulate, info) + { + if (!dontPopulate && this.hasChildren) + this.dispatchEventToListeners("populate"); + + if (info) + info.depthChange = 0; + + var node = (!skipHidden || this.revealed) ? this.children[0] : null; + if (node && (!skipHidden || this.expanded)) { + if (info) + info.depthChange = 1; + return node; + } + + if (this === stayWithin) + return null; + + node = (!skipHidden || this.revealed) ? this.nextSibling : null; + if (node) + return node; + + node = this; + while (node && !node.root && !((!skipHidden || node.revealed) ? node.nextSibling : null) && node.parent !== stayWithin) { + if (info) + info.depthChange -= 1; + node = node.parent; + } + + if (!node) + return null; + + return (!skipHidden || node.revealed) ? node.nextSibling : null; + }, + + traversePreviousNode: function(skipHidden, dontPopulate) + { + var node = (!skipHidden || this.revealed) ? this.previousSibling : null; + if (!dontPopulate && node && node.hasChildren) + node.dispatchEventToListeners("populate"); + + while (node && ((!skipHidden || (node.revealed && node.expanded)) ? node.children[node.children.length - 1] : null)) { + if (!dontPopulate && node.hasChildren) + node.dispatchEventToListeners("populate"); + node = ((!skipHidden || (node.revealed && node.expanded)) ? node.children[node.children.length - 1] : null); + } + + if (node) + return node; + + if (!this.parent || this.parent.root) + return null; + + return this.parent; + }, + + isEventWithinDisclosureTriangle: function(event) + { + if (!this.hasChildren) + return false; + var cell = event.target.enclosingNodeOrSelfWithNodeName("td"); + if (!cell.hasStyleClass("disclosure")) + return false; + + var left = cell.totalOffsetLeft() + this.leftPadding; + return event.pageX >= left && event.pageX <= left + this.disclosureToggleWidth; + }, + + _attach: function() + { + if (!this.dataGrid || this._attached) + return; + + this._attached = true; + + var nextNode = null; + var previousNode = this.traversePreviousNode(true, true); + if (previousNode && previousNode.element.parentNode && previousNode.element.nextSibling) + nextNode = previousNode.element.nextSibling; + if (!nextNode) + nextNode = this.dataGrid.dataTableBody.lastChild; + this.dataGrid.dataTableBody.insertBefore(this.element, nextNode); + + if (this.expanded) + for (var i = 0; i < this.children.length; ++i) + this.children[i]._attach(); + }, + + _detach: function() + { + if (!this._attached) + return; + + this._attached = false; + + if (this._element && this._element.parentNode) + this._element.parentNode.removeChild(this._element); + + for (var i = 0; i < this.children.length; ++i) + this.children[i]._detach(); + }, + + savePosition: function() + { + if (this._savedPosition) + return; + + if (!this.parent) + throw("savePosition: Node must have a parent."); + this._savedPosition = { + parent: this.parent, + index: this.parent.children.indexOf(this) + }; + }, + + restorePosition: function() + { + if (!this._savedPosition) + return; + + if (this.parent !== this._savedPosition.parent) + this._savedPosition.parent.insertChild(this, this._savedPosition.index); + + delete this._savedPosition; + } +} + +WebInspector.DataGridNode.prototype.__proto__ = WebInspector.Object.prototype; + +/** + * @constructor + * @extends {WebInspector.DataGridNode} + */ +WebInspector.CreationDataGridNode = function(data, hasChildren) +{ + WebInspector.DataGridNode.call(this, data, hasChildren); + this.isCreationNode = true; +} + +WebInspector.CreationDataGridNode.prototype = { + makeNormal: function() + { + delete this.isCreationNode; + delete this.makeNormal; + } +} + +WebInspector.CreationDataGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype; +/* ShowMoreDataGridNode.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.DataGridNode} + */ +WebInspector.ShowMoreDataGridNode = function(callback, nextCount, allCount) +{ + function populate(count) + { + var index = this.parent.children.indexOf(this); + this.parent.removeChild(this); + callback(count, index); + } + + this.showNext = document.createElement("button"); + this.showNext.setAttribute("type", "button"); + this.showNext.textContent = WebInspector.UIString("Show next %d", nextCount); + this.showNext.addEventListener("click", populate.bind(this, nextCount), false); + + if (allCount) { + this.showAll = document.createElement("button"); + this.showAll.setAttribute("type", "button"); + this.showAll.textContent = WebInspector.UIString("Show all %d", allCount); + this.showAll.addEventListener("click", populate.bind(this, allCount), false); + } + + WebInspector.DataGridNode.call(this, {summaryRow:true}, false); + this.selectable = false; +} + +WebInspector.ShowMoreDataGridNode.prototype = { + createCells: function() + { + var cell = document.createElement("td"); + if (this.depth) + cell.style.setProperty("padding-left", (this.depth * this.dataGrid.indentWidth) + "px"); + cell.appendChild(this.showNext); + if (this.showAll) + cell.appendChild(this.showAll); + this._element.appendChild(cell); + + var columns = this.dataGrid.columns; + var count = 0; + for (var c in columns) + ++count; + while (--count > 0) { + cell = document.createElement("td"); + this._element.appendChild(cell); + } + } +}; + +WebInspector.ShowMoreDataGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype; +/* CookiesTable.js */ + +/* + * Copyright (C) 2009 Apple Inc. All rights reserved. + * Copyright (C) 2009 Joseph Pecoraro + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.View} + * @param {function(PageAgent.Cookie)=} deleteCallback + * @param {function()=} refreshCallback + */ +WebInspector.CookiesTable = function(cookieDomain, expandable, deleteCallback, refreshCallback) +{ + WebInspector.View.call(this); + this.element.className = "fill"; + + this._cookieDomain = cookieDomain; + + var columns = { 0: {}, 1: {}, 2: {}, 3: {}, 4: {}, 5: {}, 6: {}, 7: {} }; + columns[0].title = WebInspector.UIString("Name"); + columns[0].sortable = true; + columns[0].disclosure = expandable; + columns[0].width = "24%"; + columns[1].title = WebInspector.UIString("Value"); + columns[1].sortable = true; + columns[1].width = "34%"; + columns[2].title = WebInspector.UIString("Domain"); + columns[2].sortable = true; + columns[2].width = "7%"; + columns[3].title = WebInspector.UIString("Path"); + columns[3].sortable = true; + columns[3].width = "7%"; + columns[4].title = WebInspector.UIString("Expires"); + columns[4].sortable = true; + columns[4].width = "7%"; + columns[5].title = WebInspector.UIString("Size"); + columns[5].aligned = "right"; + columns[5].sortable = true; + columns[5].width = "7%"; + columns[6].title = WebInspector.UIString("HTTP"); + columns[6].aligned = "centered"; + columns[6].sortable = true; + columns[6].width = "7%"; + columns[7].title = WebInspector.UIString("Secure"); + columns[7].aligned = "centered"; + columns[7].sortable = true; + columns[7].width = "7%"; + + this._dataGrid = new WebInspector.DataGrid(columns, undefined, deleteCallback ? this._onDeleteFromGrid.bind(this, deleteCallback) : undefined); + this._dataGrid.addEventListener("sorting changed", this._rebuildTable, this); + this._dataGrid.refreshCallback = refreshCallback; + + this._dataGrid.show(this.element); + this._data = []; +} + +WebInspector.CookiesTable.prototype = { + updateWidths: function() + { + if (this._dataGrid) + this._dataGrid.updateWidths(); + }, + + setCookies: function(cookies) + { + this._data = [{cookies: cookies}]; + this._rebuildTable(); + }, + + addCookiesFolder: function(folderName, cookies) + { + this._data.push({cookies: cookies, folderName: folderName}); + this._rebuildTable(); + }, + + get selectedCookie() + { + var node = this._dataGrid.selectedNode; + return node ? node.cookie : null; + }, + + _rebuildTable: function() + { + this._dataGrid.removeChildren(); + for (var i = 0; i < this._data.length; ++i) { + var item = this._data[i]; + if (item.folderName) { + var groupData = [ item.folderName, "", "", "", "", this._totalSize(item.cookies), "", "" ]; + var groupNode = new WebInspector.DataGridNode(groupData); + groupNode.selectable = true; + this._dataGrid.appendChild(groupNode); + groupNode.element.addStyleClass("row-group"); + this._populateNode(groupNode, item.cookies); + groupNode.expand(); + } else + this._populateNode(this._dataGrid, item.cookies); + } + }, + + _populateNode: function(parentNode, cookies) + { + var selectedCookie = this.selectedCookie; + parentNode.removeChildren(); + if (!cookies) + return; + + this._sortCookies(cookies); + for (var i = 0; i < cookies.length; ++i) { + var cookieNode = this._createGridNode(cookies[i]); + parentNode.appendChild(cookieNode); + if (selectedCookie === cookies[i]) + cookieNode.selected = true; + } + }, + + _totalSize: function(cookies) + { + var totalSize = 0; + for (var i = 0; cookies && i < cookies.length; ++i) + totalSize += cookies[i].size; + return totalSize; + }, + + _sortCookies: function(cookies) + { + var sortDirection = this._dataGrid.sortOrder === "ascending" ? 1 : -1; + + function localeCompare(field, cookie1, cookie2) + { + return sortDirection * (cookie1[field] + "").localeCompare(cookie2[field] + "") + } + + function numberCompare(field, cookie1, cookie2) + { + return sortDirection * (cookie1[field] - cookie2[field]); + } + + function expiresCompare(cookie1, cookie2) + { + if (cookie1.session !== cookie2.session) + return sortDirection * (cookie1.session ? 1 : -1); + + if (cookie1.session) + return 0; + + return sortDirection * (cookie1.expires - cookie2.expires); + } + + var comparator; + switch (parseInt(this._dataGrid.sortColumnIdentifier, 10)) { + case 0: comparator = localeCompare.bind(this, "name"); break; + case 1: comparator = localeCompare.bind(this, "value"); break; + case 2: comparator = localeCompare.bind(this, "domain"); break; + case 3: comparator = localeCompare.bind(this, "path"); break; + case 4: comparator = expiresCompare; break; + case 5: comparator = numberCompare.bind(this, "size"); break; + case 6: comparator = localeCompare.bind(this, "httpOnly"); break; + case 7: comparator = localeCompare.bind(this, "secure"); break; + default: localeCompare.bind(this, "name"); + } + + cookies.sort(comparator); + }, + + /** + * @param {PageAgent.Cookie} cookie + */ + _createGridNode: function(cookie) + { + var data = {}; + data[0] = cookie.name; + data[1] = cookie.value; + data[2] = cookie.domain || ""; + data[3] = cookie.path || ""; + data[4] = cookie.type === WebInspector.Cookie.Type.Request ? "" : + (cookie.session ? WebInspector.UIString("Session") : new Date(cookie.expires).toGMTString()); + data[5] = cookie.size; + const checkmark = "\u2713"; + data[6] = (cookie.httpOnly ? checkmark : ""); + data[7] = (cookie.secure ? checkmark : ""); + + var node = new WebInspector.DataGridNode(data); + node.cookie = cookie; + node.selectable = true; + return node; + }, + + _onDeleteFromGrid: function(deleteCallback, node) + { + deleteCallback(node.cookie); + } +} + +WebInspector.CookiesTable.prototype.__proto__ = WebInspector.View.prototype; +/* CookieItemsView.js */ + +/* + * Copyright (C) 2009 Apple Inc. All rights reserved. + * Copyright (C) 2009 Joseph Pecoraro + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.View} + */ +WebInspector.CookieItemsView = function(treeElement, cookieDomain) +{ + WebInspector.View.call(this); + + this.element.addStyleClass("storage-view"); + + this._deleteButton = new WebInspector.StatusBarButton(WebInspector.UIString("Delete"), "delete-storage-status-bar-item"); + this._deleteButton.visible = false; + this._deleteButton.addEventListener("click", this._deleteButtonClicked, this); + + this._refreshButton = new WebInspector.StatusBarButton(WebInspector.UIString("Refresh"), "refresh-storage-status-bar-item"); + this._refreshButton.addEventListener("click", this._refreshButtonClicked, this); + + this._treeElement = treeElement; + this._cookieDomain = cookieDomain; + + this._emptyView = new WebInspector.EmptyView(WebInspector.UIString("This site has no cookies.")); + this._emptyView.show(this.element); + + this.element.addEventListener("contextmenu", this._contextMenu.bind(this), true); +} + +WebInspector.CookieItemsView.prototype = { + get statusBarItems() + { + return [this._refreshButton.element, this._deleteButton.element]; + }, + + wasShown: function() + { + this._update(); + }, + + willHide: function() + { + this._deleteButton.visible = false; + }, + + _update: function() + { + WebInspector.Cookies.getCookiesAsync(this._updateWithCookies.bind(this)); + }, + + _updateWithCookies: function(allCookies, isAdvanced) + { + this._cookies = isAdvanced ? this._filterCookiesForDomain(allCookies) : allCookies; + + if (!this._cookies.length) { + // Nothing to show. + this._emptyView.show(this.element); + this._deleteButton.visible = false; + if (this._cookiesTable) + this._cookiesTable.detach(); + return; + } + + if (!this._cookiesTable) + this._cookiesTable = isAdvanced ? new WebInspector.CookiesTable(this._cookieDomain, false, this._deleteCookie.bind(this), this._update.bind(this)) : new WebInspector.SimpleCookiesTable(); + + this._cookiesTable.setCookies(this._cookies); + this._emptyView.detach(); + this._cookiesTable.show(this.element); + if (isAdvanced) { + this._treeElement.subtitle = String.sprintf(WebInspector.UIString("%d cookies (%s)"), this._cookies.length, + Number.bytesToString(this._totalSize)); + this._deleteButton.visible = true; + } + }, + + _filterCookiesForDomain: function(allCookies) + { + var cookies = []; + var resourceURLsForDocumentURL = []; + this._totalSize = 0; + + function populateResourcesForDocuments(resource) + { + var url = resource.documentURL.asParsedURL(); + if (url && url.host == this._cookieDomain) + resourceURLsForDocumentURL.push(resource.url); + } + WebInspector.forAllResources(populateResourcesForDocuments.bind(this)); + + for (var i = 0; i < allCookies.length; ++i) { + var pushed = false; + var size = allCookies[i].size; + for (var j = 0; j < resourceURLsForDocumentURL.length; ++j) { + var resourceURL = resourceURLsForDocumentURL[j]; + if (WebInspector.Cookies.cookieMatchesResourceURL(allCookies[i], resourceURL)) { + this._totalSize += size; + if (!pushed) { + pushed = true; + cookies.push(allCookies[i]); + } + } + } + } + return cookies; + }, + + _deleteCookie: function(cookie) + { + PageAgent.deleteCookie(cookie.name, this._cookieDomain); + this._update(); + }, + + _deleteButtonClicked: function() + { + if (this._cookiesTable.selectedCookie) + this._deleteCookie(this._cookiesTable.selectedCookie); + }, + + _refreshButtonClicked: function(event) + { + this._update(); + }, + + _contextMenu: function(event) + { + if (!this._cookies.length) { + var contextMenu = new WebInspector.ContextMenu(); + contextMenu.appendItem(WebInspector.UIString("Refresh"), this._update.bind(this)); + contextMenu.show(event); + } + } +} + +WebInspector.CookieItemsView.prototype.__proto__ = WebInspector.View.prototype; + +/** + * @constructor + * @extends {WebInspector.View} + */ +WebInspector.SimpleCookiesTable = function() +{ + WebInspector.View.call(this); + + var columns = {}; + columns[0] = {}; + columns[1] = {}; + columns[0].title = WebInspector.UIString("Name"); + columns[1].title = WebInspector.UIString("Value"); + + this._dataGrid = new WebInspector.DataGrid(columns); + this._dataGrid.autoSizeColumns(20, 80); + this._dataGrid.show(this.element); +} + +WebInspector.SimpleCookiesTable.prototype = { + setCookies: function(cookies) + { + this._dataGrid.removeChildren(); + var addedCookies = {}; + for (var i = 0; i < cookies.length; ++i) { + if (addedCookies[cookies[i].name]) + continue; + addedCookies[cookies[i].name] = true; + var data = {}; + data[0] = cookies[i].name; + data[1] = cookies[i].value; + + var node = new WebInspector.DataGridNode(data, false); + node.selectable = true; + this._dataGrid.appendChild(node); + } + this._dataGrid.children[0].selected = true; + } +} + +WebInspector.SimpleCookiesTable.prototype.__proto__ = WebInspector.View.prototype; + +WebInspector.Cookies = {} + +WebInspector.Cookies.getCookiesAsync = function(callback) +{ + function mycallback(error, cookies, cookiesString) + { + if (error) + return; + if (cookiesString) + callback(WebInspector.Cookies.buildCookiesFromString(cookiesString), false); + else + callback(cookies, true); + } + + PageAgent.getCookies(mycallback); +} + +WebInspector.Cookies.buildCookiesFromString = function(rawCookieString) +{ + var rawCookies = rawCookieString.split(/;\s*/); + var cookies = []; + + if (!(/^\s*$/.test(rawCookieString))) { + for (var i = 0; i < rawCookies.length; ++i) { + var cookie = rawCookies[i]; + var delimIndex = cookie.indexOf("="); + var name = cookie.substring(0, delimIndex); + var value = cookie.substring(delimIndex + 1); + var size = name.length + value.length; + cookies.push({ name: name, value: value, size: size }); + } + } + + return cookies; +} + +WebInspector.Cookies.cookieMatchesResourceURL = function(cookie, resourceURL) +{ + var url = resourceURL.asParsedURL(); + if (!url || !WebInspector.Cookies.cookieDomainMatchesResourceDomain(cookie.domain, url.host)) + return false; + return (url.path.indexOf(cookie.path) === 0 + && (!cookie.port || url.port == cookie.port) + && (!cookie.secure || url.scheme === "https")); +} + +WebInspector.Cookies.cookieDomainMatchesResourceDomain = function(cookieDomain, resourceDomain) +{ + if (cookieDomain.charAt(0) !== '.') + return resourceDomain === cookieDomain; + return !!resourceDomain.match(new RegExp("^([^\\.]+\\.)?" + cookieDomain.substring(1).escapeForRegExp() + "$"), "i"); +} +/* ApplicationCacheModel.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY GOOGLE INC. AND ITS CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE INC. + * OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.Object} + */ +WebInspector.ApplicationCacheModel = function() +{ + ApplicationCacheAgent.enable(); + InspectorBackend.registerApplicationCacheDispatcher(new WebInspector.ApplicationCacheDispatcher(this)); + + WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameNavigated, this._frameNavigated, this); + WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameDetached, this._frameDetached, this); + + this._statuses = {}; + this._manifestURLsByFrame = {}; + + this._mainFrameNavigated(); + + this._onLine = true; +} + +WebInspector.ApplicationCacheModel.EventTypes = { + FrameManifestStatusUpdated: "FrameManifestStatusUpdated", + FrameManifestAdded: "FrameManifestAdded", + FrameManifestRemoved: "FrameManifestRemoved", + NetworkStateChanged: "NetworkStateChanged" +} + +WebInspector.ApplicationCacheModel.prototype = { + _frameNavigated: function(event) + { + var frame = /** @type {WebInspector.ResourceTreeFrame} */ event.data; + if (frame.isMainFrame()) { + this._mainFrameNavigated(); + return; + } + + ApplicationCacheAgent.getManifestForFrame(frame.id, this._manifestForFrameLoaded.bind(this, frame.id)); + }, + + /** + * @param {WebInspector.Event} event + */ + _frameDetached: function(event) + { + var frame = /** @type {WebInspector.ResourceTreeFrame} */ event.data; + this._frameManifestRemoved(frame.id); + }, + + _mainFrameNavigated: function() + { + ApplicationCacheAgent.getFramesWithManifests(this._framesWithManifestsLoaded.bind(this)); + }, + + /** + * @param {string} frameId + * @param {?Protocol.Error} error + * @param {string} manifestURL + */ + _manifestForFrameLoaded: function(frameId, error, manifestURL) + { + if (error) { + console.error(error); + return; + } + + if (!manifestURL) + this._frameManifestRemoved(frameId); + }, + + /** + * @param {?Protocol.Error} error + * @param {Array.} framesWithManifests + */ + _framesWithManifestsLoaded: function(error, framesWithManifests) + { + if (error) { + console.error(error); + return; + } + + for (var i = 0; i < framesWithManifests.length; ++i) + this._frameManifestUpdated(framesWithManifests[i].frameId, framesWithManifests[i].manifestURL, framesWithManifests[i].status); + }, + + /** + * @param {string} frameId + * @param {string} manifestURL + * @param {number} status + */ + _frameManifestUpdated: function(frameId, manifestURL, status) + { + if (status === applicationCache.UNCACHED) { + this._frameManifestRemoved(frameId); + return; + } + + if (!manifestURL) + return; + + if (this._manifestURLsByFrame[frameId] && manifestURL !== this._manifestURLsByFrame[frameId]) + this._frameManifestRemoved(frameId); + + var statusChanged = this._statuses[frameId] !== status; + this._statuses[frameId] = status; + + if (!this._manifestURLsByFrame[frameId]) { + this._manifestURLsByFrame[frameId] = manifestURL; + this.dispatchEventToListeners(WebInspector.ApplicationCacheModel.EventTypes.FrameManifestAdded, frameId); + } + + if (statusChanged) + this.dispatchEventToListeners(WebInspector.ApplicationCacheModel.EventTypes.FrameManifestStatusUpdated, frameId); + }, + + /** + * @param {string} frameId + */ + _frameManifestRemoved: function(frameId) + { + if (!this._manifestURLsByFrame[frameId]) + return; + + var manifestURL = this._manifestURLsByFrame[frameId]; + delete this._manifestURLsByFrame[frameId]; + delete this._statuses[frameId]; + + this.dispatchEventToListeners(WebInspector.ApplicationCacheModel.EventTypes.FrameManifestRemoved, frameId); + }, + + /** + * @param {string} frameId + * @return {string} + */ + frameManifestURL: function(frameId) + { + return this._manifestURLsByFrame[frameId] || ""; + }, + + /** + * @param {string} frameId + * @return {number} + */ + frameManifestStatus: function(frameId) + { + return this._statuses[frameId] || applicationCache.UNCACHED; + }, + + /** + * @return {boolean} + */ + get onLine() + { + return this._onLine; + }, + + /** + * @param {string} frameId + * @param {string} manifestURL + * @param {number} status + */ + _statusUpdated: function(frameId, manifestURL, status) + { + this._frameManifestUpdated(frameId, manifestURL, status); + }, + + /** + * @param {string} frameId + * @param {function(Object)} callback + */ + requestApplicationCache: function(frameId, callback) + { + function callbackWrapper(error, applicationCache) + { + if (error) { + console.error(error); + callback(null); + return; + } + + callback(applicationCache); + } + + ApplicationCacheAgent.getApplicationCacheForFrame(frameId, callbackWrapper.bind(this)); + }, + + /** + * @param {boolean} isNowOnline + */ + _networkStateUpdated: function(isNowOnline) + { + this._onLine = isNowOnline; + this.dispatchEventToListeners(WebInspector.ApplicationCacheModel.EventTypes.NetworkStateChanged, isNowOnline); + } +} + +WebInspector.ApplicationCacheModel.prototype.__proto__ = WebInspector.Object.prototype; + +/** + * @constructor + * @implements {ApplicationCacheAgent.Dispatcher} + */ +WebInspector.ApplicationCacheDispatcher = function(applicationCacheModel) +{ + this._applicationCacheModel = applicationCacheModel; +} + +WebInspector.ApplicationCacheDispatcher.prototype = { + /** + * @param {string} frameId + * @param {string} manifestURL + * @param {number} status + */ + applicationCacheStatusUpdated: function(frameId, manifestURL, status) + { + this._applicationCacheModel._statusUpdated(frameId, manifestURL, status); + }, + + /** + * @param {boolean} isNowOnline + */ + networkStateUpdated: function(isNowOnline) + { + this._applicationCacheModel._networkStateUpdated(isNowOnline); + } +} +/* ApplicationCacheItemsView.js */ + +/* + * Copyright (C) 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.View} + */ +WebInspector.ApplicationCacheItemsView = function(model, frameId) +{ + WebInspector.View.call(this); + + this._model = model; + + this.element.addStyleClass("storage-view"); + this.element.addStyleClass("table"); + + // FIXME: Needs better tooltip. (Localized) + this.deleteButton = new WebInspector.StatusBarButton(WebInspector.UIString("Delete"), "delete-storage-status-bar-item"); + this.deleteButton.visible = false; + this.deleteButton.addEventListener("click", this._deleteButtonClicked, this); + + this.connectivityIcon = document.createElement("img"); + this.connectivityIcon.className = "storage-application-cache-connectivity-icon"; + this.connectivityIcon.src = ""; + this.connectivityMessage = document.createElement("span"); + this.connectivityMessage.className = "storage-application-cache-connectivity"; + this.connectivityMessage.textContent = ""; + + this.divider = document.createElement("span"); + this.divider.className = "status-bar-item status-bar-divider"; + + this.statusIcon = document.createElement("img"); + this.statusIcon.className = "storage-application-cache-status-icon"; + this.statusIcon.src = ""; + this.statusMessage = document.createElement("span"); + this.statusMessage.className = "storage-application-cache-status"; + this.statusMessage.textContent = ""; + + this._frameId = frameId; + + this._emptyView = new WebInspector.EmptyView(WebInspector.UIString("No Application Cache information available.")); + this._emptyView.show(this.element); + + this._markDirty(); + + var status = this._model.frameManifestStatus(frameId); + this.updateStatus(status); + + this.updateNetworkState(this._model.onLine); + + // FIXME: Status bar items don't work well enough yet, so they are being hidden. + // http://webkit.org/b/41637 Web Inspector: Give Semantics to "Refresh" and "Delete" Buttons in ApplicationCache DataGrid + this.deleteButton.element.style.display = "none"; +} + +WebInspector.ApplicationCacheItemsView.prototype = { + get statusBarItems() + { + return [ + this.deleteButton.element, + this.connectivityIcon, this.connectivityMessage, this.divider, + this.statusIcon, this.statusMessage + ]; + }, + + wasShown: function() + { + this._maybeUpdate(); + }, + + willHide: function() + { + this.deleteButton.visible = false; + }, + + _maybeUpdate: function() + { + if (!this.isShowing() || !this._viewDirty) + return; + + this._update(); + this._viewDirty = false; + }, + + _markDirty: function() + { + this._viewDirty = true; + }, + + /** + * @param {number} status + */ + updateStatus: function(status) + { + var oldStatus = this._status; + this._status = status; + + var statusInformation = {}; + // We should never have UNCACHED status, since we remove frames with UNCACHED application cache status from the tree. + statusInformation[applicationCache.UNCACHED] = { src: "Images/errorRedDot.png", text: "UNCACHED" }; + statusInformation[applicationCache.IDLE] = { src: "Images/successGreenDot.png", text: "IDLE" }; + statusInformation[applicationCache.CHECKING] = { src: "Images/warningOrangeDot.png", text: "CHECKING" }; + statusInformation[applicationCache.DOWNLOADING] = { src: "Images/warningOrangeDot.png", text: "DOWNLOADING" }; + statusInformation[applicationCache.UPDATEREADY] = { src: "Images/successGreenDot.png", text: "UPDATEREADY" }; + statusInformation[applicationCache.OBSOLETE] = { src: "Images/errorRedDot.png", text: "OBSOLETE" }; + + var info = statusInformation[status] || statusInformation[applicationCache.UNCACHED]; + + this.statusIcon.src = info.src; + this.statusMessage.textContent = info.text; + + if (this.isShowing() && this._status === applicationCache.IDLE && (oldStatus === applicationCache.UPDATEREADY || !this._resources)) + this._markDirty(); + this._maybeUpdate(); + }, + + /** + * @param {boolean} isNowOnline + */ + updateNetworkState: function(isNowOnline) + { + if (isNowOnline) { + this.connectivityIcon.src = "Images/successGreenDot.png"; + this.connectivityMessage.textContent = WebInspector.UIString("Online"); + } else { + this.connectivityIcon.src = "Images/errorRedDot.png"; + this.connectivityMessage.textContent = WebInspector.UIString("Offline"); + } + }, + + _update: function() + { + this._model.requestApplicationCache(this._frameId, this._updateCallback.bind(this)); + }, + + /** + * @param {Object} applicationCache + */ + _updateCallback: function(applicationCache) + { + if (!applicationCache || !applicationCache.manifestURL) { + delete this._manifest; + delete this._creationTime; + delete this._updateTime; + delete this._size; + delete this._resources; + + this._emptyView.show(this.element); + this.deleteButton.visible = false; + if (this._dataGrid) + this._dataGrid.element.addStyleClass("hidden"); + return; + } + // FIXME: are these variables needed anywhere else? + this._manifest = applicationCache.manifestURL; + this._creationTime = applicationCache.creationTime; + this._updateTime = applicationCache.updateTime; + this._size = applicationCache.size; + this._resources = applicationCache.resources; + + var lastPathComponent = applicationCache.lastPathComponent; + + if (!this._dataGrid) + this._createDataGrid(); + + this._populateDataGrid(); + this._dataGrid.autoSizeColumns(20, 80); + this._dataGrid.element.removeStyleClass("hidden"); + this._emptyView.detach(); + this.deleteButton.visible = true; + + // FIXME: For Chrome, put creationTime and updateTime somewhere. + // NOTE: localizedString has not yet been added. + // WebInspector.UIString("(%s) Created: %s Updated: %s", this._size, this._creationTime, this._updateTime); + }, + + _createDataGrid: function() + { + var columns = { 0: {}, 1: {}, 2: {} }; + columns[0].title = WebInspector.UIString("Resource"); + columns[0].sort = "ascending"; + columns[0].sortable = true; + columns[1].title = WebInspector.UIString("Type"); + columns[1].sortable = true; + columns[2].title = WebInspector.UIString("Size"); + columns[2].aligned = "right"; + columns[2].sortable = true; + this._dataGrid = new WebInspector.DataGrid(columns); + this._dataGrid.show(this.element); + this._dataGrid.addEventListener("sorting changed", this._populateDataGrid, this); + }, + + _populateDataGrid: function() + { + var selectedResource = this._dataGrid.selectedNode ? this._dataGrid.selectedNode.resource : null; + var sortDirection = this._dataGrid.sortOrder === "ascending" ? 1 : -1; + + function numberCompare(field, resource1, resource2) + { + return sortDirection * (resource1[field] - resource2[field]); + } + function localeCompare(field, resource1, resource2) + { + return sortDirection * (resource1[field] + "").localeCompare(resource2[field] + "") + } + + var comparator; + switch (parseInt(this._dataGrid.sortColumnIdentifier, 10)) { + case 0: comparator = localeCompare.bind(this, "name"); break; + case 1: comparator = localeCompare.bind(this, "type"); break; + case 2: comparator = numberCompare.bind(this, "size"); break; + default: localeCompare.bind(this, "resource"); // FIXME: comparator = ? + } + + this._resources.sort(comparator); + this._dataGrid.removeChildren(); + + var nodeToSelect; + for (var i = 0; i < this._resources.length; ++i) { + var data = {}; + var resource = this._resources[i]; + data[0] = resource.url; + data[1] = resource.type; + data[2] = Number.bytesToString(resource.size); + var node = new WebInspector.DataGridNode(data); + node.resource = resource; + node.selectable = true; + this._dataGrid.appendChild(node); + if (resource === selectedResource) { + nodeToSelect = node; + nodeToSelect.selected = true; + } + } + + if (!nodeToSelect && this._dataGrid.children.length) + this._dataGrid.children[0].selected = true; + }, + + _deleteButtonClicked: function(event) + { + if (!this._dataGrid || !this._dataGrid.selectedNode) + return; + + // FIXME: Delete Button semantics are not yet defined. (Delete a single, or all?) + this._deleteCallback(this._dataGrid.selectedNode); + }, + + _deleteCallback: function(node) + { + // FIXME: Should we delete a single (selected) resource or all resources? + // InspectorBackend.deleteCachedResource(...) + // this._update(); + }, +} + +WebInspector.ApplicationCacheItemsView.prototype.__proto__ = WebInspector.View.prototype; +/* IndexedDBModel.js */ + +/* + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.Object} + */ +WebInspector.IndexedDBModel = function() +{ + this._indexedDBAgent = new WebInspector.IndexedDBRequestManager(); + + WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameNavigated, this._frameNavigated, this); + WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameDetached, this._frameDetached, this); + + this._securityOriginByFrameId = {}; + this._frameIdsBySecurityOrigin = {}; + this._databaseNamesBySecurityOrigin = {}; + + this.refreshDatabaseNames(); +} + +WebInspector.IndexedDBModel.prototype = { + refreshDatabaseNames: function() + { + this._reset(); + this._framesNavigatedRecursively(WebInspector.resourceTreeModel.mainFrame); + }, + + /** + * @param {WebInspector.ResourceTreeFrame} resourceTreeFrame + */ + _framesNavigatedRecursively: function(resourceTreeFrame) + { + this._processFrameNavigated(resourceTreeFrame); + for (var i = 0; i < resourceTreeFrame.childFrames.length; ++i) + this._framesNavigatedRecursively(resourceTreeFrame.childFrames[i]); + }, + + /** + * @param {WebInspector.Event} event + */ + _frameNavigated: function(event) + { + var resourceTreeFrame = /** @type {WebInspector.ResourceTreeFrame} */ event.data; + this._processFrameNavigated(resourceTreeFrame); + }, + + /** + * @param {WebInspector.Event} event + */ + _frameDetached: function(event) + { + var resourceTreeFrame = /** @type {WebInspector.ResourceTreeFrame} */ event.data; + this._originRemovedFromFrame(resourceTreeFrame.id); + this._indexedDBAgent._frameDetached(resourceTreeFrame.id); + }, + + _reset: function() + { + this._securityOriginByFrameId = {}; + this._frameIdsBySecurityOrigin = {}; + this._databaseNamesBySecurityOrigin = {}; + this._indexedDBAgent._reset(); + // FIXME: dispatch events? + }, + + /** + * @param {WebInspector.ResourceTreeFrame} resourceTreeFrame + */ + _processFrameNavigated: function(resourceTreeFrame) + { + if (resourceTreeFrame.securityOrigin === "null") + return; + if (this._frameIdsBySecurityOrigin[resourceTreeFrame.securityOrigin]) + this._originAddedToFrame(resourceTreeFrame.id, resourceTreeFrame.securityOrigin); + else + this._loadDatabaseNamesForFrame(resourceTreeFrame.id); + }, + + /** + * @param {string} frameId + * @param {string} securityOrigin + */ + _originAddedToFrame: function(frameId, securityOrigin) + { + if (!this._frameIdsBySecurityOrigin[securityOrigin]) { + this._frameIdsBySecurityOrigin[securityOrigin] = []; + this._frameIdsBySecurityOrigin[securityOrigin].push(frameId); + this._databaseNamesBySecurityOrigin[securityOrigin] = []; + // FIXME: dispatch origin added event. + } + this._securityOriginByFrameId[frameId] = securityOrigin; + }, + + /** + * @param {string} frameId + */ + _originRemovedFromFrame: function(frameId) + { + var currentSecurityOrigin = this._securityOriginByFrameId[frameId]; + if (!currentSecurityOrigin) + return; + + delete this._securityOriginByFrameId[frameId]; + + var frameIdsForOrigin = this._frameIdsBySecurityOrigin[currentSecurityOrigin]; + for (var i = 0; i < frameIdsForOrigin; ++i) { + if (frameIdsForOrigin[i] === frameId) { + frameIdsForOrigin.splice(i, 1); + break; + } + } + if (!frameIdsForOrigin.length) + this._originRemoved(currentSecurityOrigin); + }, + + /** + * @param {string} securityOrigin + */ + _originRemoved: function(securityOrigin) + { + var frameIdsForOrigin = this._frameIdsBySecurityOrigin[securityOrigin]; + for (var i = 0; i < frameIdsForOrigin; ++i) + delete this._securityOriginByFrameId[frameIdsForOrigin[i]] + delete this._frameIdsBySecurityOrigin[securityOrigin]; + for (var i = 0; i < this._databaseNamesBySecurityOrigin[securityOrigin]; ++i) + this._databaseRemoved(securityOrigin, this._databaseNamesBySecurityOrigin[securityOrigin][i]); + delete this._databaseNamesBySecurityOrigin[securityOrigin]; + // FIXME: dispatch origin removed event. + }, + + /** + * @param {string} securityOrigin + * @param {Array.} databaseNames + */ + _updateOriginDatabaseNames: function(securityOrigin, databaseNames) + { + var newDatabaseNames = {}; + for (var i = 0; i < databaseNames.length; ++i) + newDatabaseNames[databaseNames[i]] = true; + var oldDatabaseNames = {}; + for (var i = 0; i < this._databaseNamesBySecurityOrigin[securityOrigin].length; ++i) + oldDatabaseNames[databaseNames[i]] = true; + + this._databaseNamesBySecurityOrigin[securityOrigin] = databaseNames; + + for (var databaseName in oldDatabaseNames) { + if (!newDatabaseNames[databaseName]) + this._databaseRemoved(securityOrigin, databaseName); + } + for (var databaseName in newDatabaseNames) { + if (!oldDatabaseNames[databaseName]) + this._databaseAdded(securityOrigin, databaseName); + } + + if (!this._databaseNamesBySecurityOrigin[securityOrigin].length) + this._originRemoved(securityOrigin); + }, + + /** + * @param {string} securityOrigin + * @param {string} databaseName + */ + _databaseAdded: function(securityOrigin, databaseName) + { + // FIXME: dispatch database added event. + }, + + /** + * @param {string} securityOrigin + * @param {string} databaseName + */ + _databaseRemoved: function(securityOrigin, databaseName) + { + // FIXME: dispatch database removed event. + }, + + /** + * @param {string} frameId + */ + _loadDatabaseNamesForFrame: function(frameId) + { + /** + * @param {IndexedDBAgent.SecurityOriginWithDatabaseNames} securityOriginWithDatabaseNames + */ + function callback(securityOriginWithDatabaseNames) + { + var databaseNames = securityOriginWithDatabaseNames.databaseNames; + var oldSecurityOrigin = this._securityOriginByFrameId[frameId]; + if (oldSecurityOrigin && oldSecurityOrigin === securityOriginWithDatabaseNames.securityOrigin) + this._updateOriginDatabaseNames(securityOriginWithDatabaseNames.securityOrigin, securityOriginWithDatabaseNames.databaseNames); + else { + this._originRemovedFromFrame(frameId); + this._originAddedToFrame(frameId, securityOriginWithDatabaseNames.securityOrigin); + this._updateOriginDatabaseNames(securityOriginWithDatabaseNames.securityOrigin, securityOriginWithDatabaseNames.databaseNames); + } + } + + this._indexedDBAgent.requestDatabaseNamesForFrame(frameId, callback.bind(this)); + } +} + +WebInspector.IndexedDBModel.prototype.__proto__ = WebInspector.Object.prototype; + +/** + * @constructor + */ +WebInspector.IndexedDBRequestManager = function() +{ + this._lastRequestId = 0; + this._requestDatabaseNamesForFrameCallbacks = {}; + + IndexedDBAgent.enable(); + InspectorBackend.registerIndexedDBDispatcher(new WebInspector.IndexedDBDispatcher(this)); +} + +WebInspector.IndexedDBRequestManager.prototype = { + /** + * @param {string} frameId + * @param {function(IndexedDBAgent.SecurityOriginWithDatabaseNames)} callback + */ + requestDatabaseNamesForFrame: function(frameId, callback) + { + var requestId = this._requestId(); + var request = new WebInspector.IndexedDBRequestManager.DatabasesForFrameRequest(frameId, callback); + this._requestDatabaseNamesForFrameCallbacks[requestId] = request; + + function innerCallback(error) + { + if (error) { + console.error("IndexedDBAgent error: " + error); + return; + } + } + + IndexedDBAgent.requestDatabaseNamesForFrame(requestId, frameId, innerCallback); + }, + + /** + * @param {number} requestId + * @param {IndexedDBAgent.SecurityOriginWithDatabaseNames} securityOriginWithDatabaseNames + */ + _databaseNamesLoaded: function(requestId, securityOriginWithDatabaseNames) + { + var request = this._requestDatabaseNamesForFrameCallbacks[requestId]; + if (!request.callback) + return; + + request.callback(securityOriginWithDatabaseNames); + }, + + /** + * @return {number} + */ + _requestId: function() + { + return ++this._lastRequestId; + }, + + /** + * @param {string} frameId + */ + _frameDetached: function(frameId) + { + for (var requestId in this._requestDatabaseNamesForFrameCallbacks) { + if (this._requestDatabaseNamesForFrameCallbacks[requestId].frameId === frameId) + delete this._requestDatabaseNamesForFrameCallbacks[requestId]; + } + }, + + _reset: function() + { + this._requestDatabaseNamesForFrameCallbacks = {}; + } +} + +/** + * @constructor + */ +WebInspector.IndexedDBRequestManager.DatabasesForFrameRequest = function(frameId, callback) +{ + this.frameId = frameId; + this.callback = callback; +} + +/** + * @constructor + * @implements {IndexedDBAgent.Dispatcher} + * @param {WebInspector.IndexedDBRequestManager} indexedDBRequestManager + */ +WebInspector.IndexedDBDispatcher = function(indexedDBRequestManager) +{ + this._agentWrapper = indexedDBRequestManager; +} + +WebInspector.IndexedDBDispatcher.prototype = { + /** + * @param {number} requestId + * @param {IndexedDBAgent.SecurityOriginWithDatabaseNames} securityOriginWithDatabaseNames + */ + databaseNamesLoaded: function(requestId, securityOriginWithDatabaseNames) + { + this._agentWrapper._databaseNamesLoaded(requestId, securityOriginWithDatabaseNames); + } +} +/* Script.js */ + +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @param {string} scriptId + * @param {string} sourceURL + * @param {number} startLine + * @param {number} startColumn + * @param {number} endLine + * @param {number} endColumn + * @param {boolean} isContentScript + * @param {string=} sourceMapURL + */ +WebInspector.Script = function(scriptId, sourceURL, startLine, startColumn, endLine, endColumn, isContentScript, sourceMapURL) +{ + this.scriptId = scriptId; + this.sourceURL = sourceURL; + this.lineOffset = startLine; + this.columnOffset = startColumn; + this.endLine = endLine; + this.endColumn = endColumn; + this.isContentScript = isContentScript; + this.sourceMapURL = sourceMapURL; +} + +WebInspector.Script.prototype = { + /** + * @param {function(string)} callback + */ + requestSource: function(callback) + { + if (this._source) { + callback(this._source); + return; + } + + /** + * @this {WebInspector.Script} + * @param {?Protocol.Error} error + * @param {string} source + */ + function didGetScriptSource(error, source) + { + this._source = error ? "" : source; + callback(this._source); + } + if (this.scriptId) { + // Script failed to parse. + DebuggerAgent.getScriptSource(this.scriptId, didGetScriptSource.bind(this)); + } else + callback(""); + }, + + /** + * @param {string} query + * @param {boolean} caseSensitive + * @param {boolean} isRegex + * @param {function(Array.)} callback + */ + searchInContent: function(query, caseSensitive, isRegex, callback) + { + /** + * @this {WebInspector.Script} + * @param {?Protocol.Error} error + * @param {Array.} searchMatches + */ + function innerCallback(error, searchMatches) + { + if (error) + console.error(error); + var result = []; + for (var i = 0; i < searchMatches.length; ++i) { + var searchMatch = new WebInspector.ContentProvider.SearchMatch(searchMatches[i].lineNumber, searchMatches[i].lineContent); + result.push(searchMatch); + } + callback(result || []); + } + + if (this.scriptId) { + // Script failed to parse. + DebuggerAgent.searchInContent(this.scriptId, query, caseSensitive, isRegex, innerCallback.bind(this)); + } else + callback([]); + }, + + /** + * @param {string} newSource + * @param {function(?Protocol.Error, Array.=)} callback + */ + editSource: function(newSource, callback) + { + /** + * @this {WebInspector.Script} + * @param {?Protocol.Error} error + * @param {Array.|undefined} callFrames + */ + function didEditScriptSource(error, callFrames) + { + if (!error) + this._source = newSource; + callback(error, callFrames); + } + if (this.scriptId) { + // Script failed to parse. + DebuggerAgent.setScriptSource(this.scriptId, newSource, undefined, didEditScriptSource.bind(this)); + } else + callback("Script failed to parse"); + }, + + /** + * @return {boolean} + */ + isInlineScript: function() + { + return !!this.sourceURL && this.lineOffset !== 0 && this.columnOffset !== 0; + } +} +/* SidebarPane.js */ + +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.Object} + */ +WebInspector.SidebarPane = function(title) +{ + this.element = document.createElement("div"); + this.element.className = "pane"; + + this.titleElement = document.createElement("div"); + this.titleElement.className = "title"; + this.titleElement.tabIndex = 0; + this.titleElement.addEventListener("click", this.toggleExpanded.bind(this), false); + this.titleElement.addEventListener("keydown", this._onTitleKeyDown.bind(this), false); + + this.bodyElement = document.createElement("div"); + this.bodyElement.className = "body"; + + this.element.appendChild(this.titleElement); + this.element.appendChild(this.bodyElement); + + this.title = title; + this.growbarVisible = false; + this.expanded = false; +} + +WebInspector.SidebarPane.prototype = { + get title() + { + return this._title; + }, + + set title(x) + { + if (this._title === x) + return; + this._title = x; + this.titleElement.textContent = x; + }, + + get growbarVisible() + { + return this._growbarVisible; + }, + + set growbarVisible(x) + { + if (this._growbarVisible === x) + return; + + this._growbarVisible = x; + + if (x && !this._growbarElement) { + this._growbarElement = document.createElement("div"); + this._growbarElement.className = "growbar"; + this.element.appendChild(this._growbarElement); + } else if (!x && this._growbarElement) { + if (this._growbarElement.parentNode) + this._growbarElement.parentNode(this._growbarElement); + delete this._growbarElement; + } + }, + + get expanded() + { + return this._expanded; + }, + + set expanded(x) + { + if (x) + this.expand(); + else + this.collapse(); + }, + + expand: function() + { + if (this._expanded) + return; + this._expanded = true; + this.element.addStyleClass("expanded"); + if (this.onexpand) + this.onexpand(this); + }, + + collapse: function() + { + if (!this._expanded) + return; + this._expanded = false; + this.element.removeStyleClass("expanded"); + if (this.oncollapse) + this.oncollapse(this); + }, + + toggleExpanded: function() + { + this.expanded = !this.expanded; + }, + + _onTitleKeyDown: function(event) + { + if (isEnterKey(event) || event.keyCode === WebInspector.KeyboardShortcut.Keys.Space.code) + this.toggleExpanded(); + } +} + +WebInspector.SidebarPane.prototype.__proto__ = WebInspector.Object.prototype; +/* ElementsTreeOutline.js */ + +/* + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2008 Matt Lilek + * Copyright (C) 2009 Joseph Pecoraro + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {TreeOutline} + * @param {boolean=} omitRootDOMNode + * @param {boolean=} selectEnabled + * @param {boolean=} showInElementsPanelEnabled + * @param {function(WebInspector.ContextMenu, WebInspector.DOMNode)=} contextMenuCallback + */ +WebInspector.ElementsTreeOutline = function(omitRootDOMNode, selectEnabled, showInElementsPanelEnabled, contextMenuCallback) +{ + this.element = document.createElement("ol"); + this.element.addEventListener("mousedown", this._onmousedown.bind(this), false); + this.element.addEventListener("mousemove", this._onmousemove.bind(this), false); + this.element.addEventListener("mouseout", this._onmouseout.bind(this), false); + this.element.addEventListener("dragstart", this._ondragstart.bind(this), false); + this.element.addEventListener("dragover", this._ondragover.bind(this), false); + this.element.addEventListener("dragleave", this._ondragleave.bind(this), false); + this.element.addEventListener("drop", this._ondrop.bind(this), false); + this.element.addEventListener("dragend", this._ondragend.bind(this), false); + + TreeOutline.call(this, this.element); + + this._includeRootDOMNode = !omitRootDOMNode; + this._selectEnabled = selectEnabled; + this._showInElementsPanelEnabled = showInElementsPanelEnabled; + this._rootDOMNode = null; + this._selectDOMNode = null; + this._eventSupport = new WebInspector.Object(); + this._editing = false; + + this._visible = false; + + this.element.addEventListener("contextmenu", this._contextMenuEventFired.bind(this), true); + this._contextMenuCallback = contextMenuCallback; +} + +WebInspector.ElementsTreeOutline.Events = { + SelectedNodeChanged: "SelectedNodeChanged" +} + +WebInspector.ElementsTreeOutline.prototype = { + wireToDomAgent: function() + { + this._elementsTreeUpdater = new WebInspector.ElementsTreeUpdater(this); + }, + + setVisible: function(visible) + { + this._visible = visible; + if (!this._visible) + return; + + this._updateModifiedNodes(); + if (this._selectedDOMNode) + this._revealAndSelectNode(this._selectedDOMNode, false); + }, + + addEventListener: function(eventType, listener, thisObject) + { + this._eventSupport.addEventListener(eventType, listener, thisObject); + }, + + removeEventListener: function(eventType, listener, thisObject) + { + this._eventSupport.removeEventListener(eventType, listener, thisObject); + }, + + get rootDOMNode() + { + return this._rootDOMNode; + }, + + set rootDOMNode(x) + { + if (this._rootDOMNode === x) + return; + + this._rootDOMNode = x; + + this._isXMLMimeType = x && x.isXMLNode(); + + this.update(); + }, + + get isXMLMimeType() + { + return this._isXMLMimeType; + }, + + selectedDOMNode: function() + { + return this._selectedDOMNode; + }, + + selectDOMNode: function(node, focus) + { + if (this._selectedDOMNode === node) { + this._revealAndSelectNode(node, !focus); + return; + } + + this._selectedDOMNode = node; + this._revealAndSelectNode(node, !focus); + + // The _revealAndSelectNode() method might find a different element if there is inlined text, + // and the select() call would change the selectedDOMNode and reenter this setter. So to + // avoid calling _selectedNodeChanged() twice, first check if _selectedDOMNode is the same + // node as the one passed in. + if (this._selectedDOMNode === node) + this._selectedNodeChanged(); + }, + + get editing() + { + return this._editing; + }, + + update: function() + { + var selectedNode = this.selectedTreeElement ? this.selectedTreeElement.representedObject : null; + + this.removeChildren(); + + if (!this.rootDOMNode) + return; + + var treeElement; + if (this._includeRootDOMNode) { + treeElement = new WebInspector.ElementsTreeElement(this.rootDOMNode); + treeElement.selectable = this._selectEnabled; + this.appendChild(treeElement); + } else { + // FIXME: this could use findTreeElement to reuse a tree element if it already exists + var node = this.rootDOMNode.firstChild; + while (node) { + treeElement = new WebInspector.ElementsTreeElement(node); + treeElement.selectable = this._selectEnabled; + this.appendChild(treeElement); + node = node.nextSibling; + } + } + + if (selectedNode) + this._revealAndSelectNode(selectedNode, true); + }, + + updateSelection: function() + { + if (!this.selectedTreeElement) + return; + var element = this.treeOutline.selectedTreeElement; + element.updateSelection(); + }, + + _selectedNodeChanged: function() + { + this._eventSupport.dispatchEventToListeners(WebInspector.ElementsTreeOutline.Events.SelectedNodeChanged, this._selectedDOMNode); + }, + + findTreeElement: function(node) + { + function isAncestorNode(ancestor, node) + { + return ancestor.isAncestor(node); + } + + function parentNode(node) + { + return node.parentNode; + } + + var treeElement = TreeOutline.prototype.findTreeElement.call(this, node, isAncestorNode, parentNode); + if (!treeElement && node.nodeType() === Node.TEXT_NODE) { + // The text node might have been inlined if it was short, so try to find the parent element. + treeElement = TreeOutline.prototype.findTreeElement.call(this, node.parentNode, isAncestorNode, parentNode); + } + + return treeElement; + }, + + createTreeElementFor: function(node) + { + var treeElement = this.findTreeElement(node); + if (treeElement) + return treeElement; + if (!node.parentNode) + return null; + + treeElement = this.createTreeElementFor(node.parentNode); + if (treeElement && treeElement.showChild(node.index)) + return treeElement.children[node.index]; + + return null; + }, + + set suppressRevealAndSelect(x) + { + if (this._suppressRevealAndSelect === x) + return; + this._suppressRevealAndSelect = x; + }, + + _revealAndSelectNode: function(node, omitFocus) + { + if (!node || this._suppressRevealAndSelect) + return; + + var treeElement = this.createTreeElementFor(node); + if (!treeElement) + return; + + treeElement.revealAndSelect(omitFocus); + }, + + _treeElementFromEvent: function(event) + { + var scrollContainer = this.element.parentElement; + + // We choose this X coordinate based on the knowledge that our list + // items extend at least to the right edge of the outer
    container. + // In the no-word-wrap mode the outer
      may be wider than the tree container + // (and partially hidden), in which case we are left to use only its right boundary. + var x = scrollContainer.totalOffsetLeft() + scrollContainer.offsetWidth - 36; + + var y = event.pageY; + + // Our list items have 1-pixel cracks between them vertically. We avoid + // the cracks by checking slightly above and slightly below the mouse + // and seeing if we hit the same element each time. + var elementUnderMouse = this.treeElementFromPoint(x, y); + var elementAboveMouse = this.treeElementFromPoint(x, y - 2); + var element; + if (elementUnderMouse === elementAboveMouse) + element = elementUnderMouse; + else + element = this.treeElementFromPoint(x, y + 2); + + return element; + }, + + _onmousedown: function(event) + { + var element = this._treeElementFromEvent(event); + + if (!element || element.isEventWithinDisclosureTriangle(event)) + return; + + element.select(); + }, + + _onmousemove: function(event) + { + var element = this._treeElementFromEvent(event); + if (element && this._previousHoveredElement === element) + return; + + if (this._previousHoveredElement) { + this._previousHoveredElement.hovered = false; + delete this._previousHoveredElement; + } + + if (element) { + element.hovered = true; + this._previousHoveredElement = element; + + // Lazily compute tag-specific tooltips. + if (element.representedObject && !element.tooltip) + element._createTooltipForNode(); + } + + WebInspector.domAgent.highlightDOMNode(element ? element.representedObject.id : 0); + }, + + _onmouseout: function(event) + { + var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY); + if (nodeUnderMouse && nodeUnderMouse.isDescendant(this.element)) + return; + + if (this._previousHoveredElement) { + this._previousHoveredElement.hovered = false; + delete this._previousHoveredElement; + } + + WebInspector.domAgent.hideDOMNodeHighlight(); + }, + + _ondragstart: function(event) + { + var treeElement = this._treeElementFromEvent(event); + if (!treeElement) + return false; + + if (!this._isValidDragSourceOrTarget(treeElement)) + return false; + + if (treeElement.representedObject.nodeName() === "BODY" || treeElement.representedObject.nodeName() === "HEAD") + return false; + + event.dataTransfer.setData("text/plain", treeElement.listItemElement.textContent); + event.dataTransfer.effectAllowed = "copyMove"; + this._nodeBeingDragged = treeElement.representedObject; + + WebInspector.domAgent.hideDOMNodeHighlight(); + + return true; + }, + + _ondragover: function(event) + { + if (!this._nodeBeingDragged) + return false; + + var treeElement = this._treeElementFromEvent(event); + if (!this._isValidDragSourceOrTarget(treeElement)) + return false; + + var node = treeElement.representedObject; + while (node) { + if (node === this._nodeBeingDragged) + return false; + node = node.parentNode; + } + + treeElement.updateSelection(); + treeElement.listItemElement.addStyleClass("elements-drag-over"); + this._dragOverTreeElement = treeElement; + event.preventDefault(); + event.dataTransfer.dropEffect = 'move'; + return false; + }, + + _ondragleave: function(event) + { + this._clearDragOverTreeElementMarker(); + event.preventDefault(); + return false; + }, + + _isValidDragSourceOrTarget: function(treeElement) + { + if (!treeElement) + return false; + + var node = treeElement.representedObject; + if (!(node instanceof WebInspector.DOMNode)) + return false; + + if (!node.parentNode || node.parentNode.nodeType() !== Node.ELEMENT_NODE) + return false; + + return true; + }, + + _ondrop: function(event) + { + event.preventDefault(); + var treeElement = this._treeElementFromEvent(event); + if (this._nodeBeingDragged && treeElement) { + var parentNode; + var anchorNode; + + if (treeElement._elementCloseTag) { + // Drop onto closing tag -> insert as last child. + parentNode = treeElement.representedObject; + } else { + var dragTargetNode = treeElement.representedObject; + parentNode = dragTargetNode.parentNode; + anchorNode = dragTargetNode; + } + + function callback(error, newNodeId) + { + if (error) + return; + + this._updateModifiedNodes(); + var newNode = WebInspector.domAgent.nodeForId(newNodeId); + if (newNode) + this.selectDOMNode(newNode, true); + } + this._nodeBeingDragged.moveTo(parentNode, anchorNode, callback.bind(this)); + } + + delete this._nodeBeingDragged; + }, + + _ondragend: function(event) + { + event.preventDefault(); + this._clearDragOverTreeElementMarker(); + delete this._nodeBeingDragged; + }, + + _clearDragOverTreeElementMarker: function() + { + if (this._dragOverTreeElement) { + this._dragOverTreeElement.updateSelection(); + this._dragOverTreeElement.listItemElement.removeStyleClass("elements-drag-over"); + delete this._dragOverTreeElement; + } + }, + + _contextMenuEventFired: function(event) + { + if (!this._showInElementsPanelEnabled) + return; + + var treeElement = this._treeElementFromEvent(event); + if (!treeElement) + return; + + function focusElement() + { + WebInspector.domAgent.inspectElement(treeElement.representedObject.id); + } + var contextMenu = new WebInspector.ContextMenu(); + contextMenu.appendItem(WebInspector.UIString("Reveal in Elements Panel"), focusElement.bind(this)); + contextMenu.show(event); + }, + + populateContextMenu: function(contextMenu, event) + { + var treeElement = this._treeElementFromEvent(event); + if (!treeElement) + return false; + + var tag = event.target.enclosingNodeOrSelfWithClass("webkit-html-tag"); + var textNode = event.target.enclosingNodeOrSelfWithClass("webkit-html-text-node"); + var commentNode = event.target.enclosingNodeOrSelfWithClass("webkit-html-comment"); + var populated = WebInspector.populateHrefContextMenu(contextMenu, this.selectedDOMNode(), event); + if (tag && treeElement._populateTagContextMenu) { + if (populated) + contextMenu.appendSeparator(); + treeElement._populateTagContextMenu(contextMenu, event); + populated = true; + } else if (textNode && treeElement._populateTextContextMenu) { + if (populated) + contextMenu.appendSeparator(); + treeElement._populateTextContextMenu(contextMenu, textNode); + populated = true; + } else if (commentNode && treeElement._populateNodeContextMenu) { + if (populated) + contextMenu.appendSeparator(); + treeElement._populateNodeContextMenu(contextMenu, textNode); + populated = true; + } + + return populated; + }, + + adjustCollapsedRange: function() + { + }, + + _updateModifiedNodes: function() + { + if (this._elementsTreeUpdater) + this._elementsTreeUpdater._updateModifiedNodes(); + }, + + _populateContextMenu: function(contextMenu, node) + { + if (this._contextMenuCallback) + this._contextMenuCallback(contextMenu, node); + } +} + +WebInspector.ElementsTreeOutline.prototype.__proto__ = TreeOutline.prototype; + +/** + * @constructor + * @extends {TreeElement} + * @param {boolean=} elementCloseTag + */ +WebInspector.ElementsTreeElement = function(node, elementCloseTag) +{ + this._elementCloseTag = elementCloseTag; + var hasChildrenOverride = !elementCloseTag && node.hasChildNodes() && !this._showInlineText(node); + + // The title will be updated in onattach. + TreeElement.call(this, "", node, hasChildrenOverride); + + if (this.representedObject.nodeType() == Node.ELEMENT_NODE && !elementCloseTag) + this._canAddAttributes = true; + this._searchQuery = null; + this._expandedChildrenLimit = WebInspector.ElementsTreeElement.InitialChildrenLimit; +} + +WebInspector.ElementsTreeElement.InitialChildrenLimit = 500; + +// A union of HTML4 and HTML5-Draft elements that explicitly +// or implicitly (for HTML5) forbid the closing tag. +// FIXME: Revise once HTML5 Final is published. +WebInspector.ElementsTreeElement.ForbiddenClosingTagElements = [ + "area", "base", "basefont", "br", "canvas", "col", "command", "embed", "frame", + "hr", "img", "input", "isindex", "keygen", "link", "meta", "param", "source" +].keySet(); + +// These tags we do not allow editing their tag name. +WebInspector.ElementsTreeElement.EditTagBlacklist = [ + "html", "head", "body" +].keySet(); + +WebInspector.ElementsTreeElement.prototype = { + highlightSearchResults: function(searchQuery) + { + if (this._searchQuery !== searchQuery) { + this._updateSearchHighlight(false); + delete this._highlightResult; // A new search query. + } + + this._searchQuery = searchQuery; + this._searchHighlightsVisible = true; + this.updateTitle(true); + }, + + hideSearchHighlights: function() + { + delete this._searchHighlightsVisible; + this._updateSearchHighlight(false); + }, + + _updateSearchHighlight: function(show) + { + if (!this._highlightResult) + return; + + function updateEntryShow(entry) + { + switch (entry.type) { + case "added": + entry.parent.insertBefore(entry.node, entry.nextSibling); + break; + case "changed": + entry.node.textContent = entry.newText; + break; + } + } + + function updateEntryHide(entry) + { + switch (entry.type) { + case "added": + if (entry.node.parentElement) + entry.node.parentElement.removeChild(entry.node); + break; + case "changed": + entry.node.textContent = entry.oldText; + break; + } + } + + var updater = show ? updateEntryShow : updateEntryHide; + + for (var i = 0, size = this._highlightResult.length; i < size; ++i) + updater(this._highlightResult[i]); + }, + + get hovered() + { + return this._hovered; + }, + + set hovered(x) + { + if (this._hovered === x) + return; + + this._hovered = x; + + if (this.listItemElement) { + if (x) { + this.updateSelection(); + this.listItemElement.addStyleClass("hovered"); + } else { + this.listItemElement.removeStyleClass("hovered"); + } + } + }, + + get expandedChildrenLimit() + { + return this._expandedChildrenLimit; + }, + + set expandedChildrenLimit(x) + { + if (this._expandedChildrenLimit === x) + return; + + this._expandedChildrenLimit = x; + if (this.treeOutline && !this._updateChildrenInProgress) + this._updateChildren(true); + }, + + get expandedChildCount() + { + var count = this.children.length; + if (count && this.children[count - 1]._elementCloseTag) + count--; + if (count && this.children[count - 1].expandAllButton) + count--; + return count; + }, + + showChild: function(index) + { + if (this._elementCloseTag) + return; + + if (index >= this.expandedChildrenLimit) { + this._expandedChildrenLimit = index + 1; + this._updateChildren(true); + } + + // Whether index-th child is visible in the children tree + return this.expandedChildCount > index; + }, + + _createTooltipForNode: function() + { + var node = /** @type {WebInspector.DOMNode} */ this.representedObject; + if (!node.nodeName() || node.nodeName().toLowerCase() !== "img") + return; + + function setTooltip(result) + { + if (!result || result.type !== "string") + return; + + try { + var properties = JSON.parse(result.description); + var offsetWidth = properties[0]; + var offsetHeight = properties[1]; + var naturalWidth = properties[2]; + var naturalHeight = properties[3]; + if (offsetHeight === naturalHeight && offsetWidth === naturalWidth) + this.tooltip = WebInspector.UIString("%d \xd7 %d pixels", offsetWidth, offsetHeight); + else + this.tooltip = WebInspector.UIString("%d \xd7 %d pixels (Natural: %d \xd7 %d pixels)", offsetWidth, offsetHeight, naturalWidth, naturalHeight); + } catch (e) { + console.error(e); + } + } + + function resolvedNode(object) + { + if (!object) + return; + + function dimensions() + { + return "[" + this.offsetWidth + "," + this.offsetHeight + "," + this.naturalWidth + "," + this.naturalHeight + "]"; + } + + object.callFunction(dimensions, setTooltip.bind(this)); + object.release(); + } + WebInspector.RemoteObject.resolveNode(node, "", resolvedNode.bind(this)); + }, + + updateSelection: function() + { + var listItemElement = this.listItemElement; + if (!listItemElement) + return; + + if (document.body.offsetWidth <= 0) { + // The stylesheet hasn't loaded yet or the window is closed, + // so we can't calculate what is need. Return early. + return; + } + + if (!this.selectionElement) { + this.selectionElement = document.createElement("div"); + this.selectionElement.className = "selection selected"; + listItemElement.insertBefore(this.selectionElement, listItemElement.firstChild); + } + + this.selectionElement.style.height = listItemElement.offsetHeight + "px"; + }, + + onattach: function() + { + if (this._hovered) { + this.updateSelection(); + this.listItemElement.addStyleClass("hovered"); + } + + this.updateTitle(); + this._preventFollowingLinksOnDoubleClick(); + this.listItemElement.draggable = true; + }, + + _preventFollowingLinksOnDoubleClick: function() + { + var links = this.listItemElement.querySelectorAll("li > .webkit-html-tag > .webkit-html-attribute > .webkit-html-external-link, li > .webkit-html-tag > .webkit-html-attribute > .webkit-html-resource-link"); + if (!links) + return; + + for (var i = 0; i < links.length; ++i) + links[i].preventFollowOnDoubleClick = true; + }, + + onpopulate: function() + { + if (this.children.length || this._showInlineText(this.representedObject) || this._elementCloseTag) + return; + + this.updateChildren(); + }, + + /** + * @param {boolean=} fullRefresh + */ + updateChildren: function(fullRefresh) + { + if (this._elementCloseTag) + return; + this.representedObject.getChildNodes(this._updateChildren.bind(this, fullRefresh)); + }, + + /** + * @param {boolean=} closingTag + */ + insertChildElement: function(child, index, closingTag) + { + var newElement = new WebInspector.ElementsTreeElement(child, closingTag); + newElement.selectable = this.treeOutline._selectEnabled; + this.insertChild(newElement, index); + return newElement; + }, + + moveChild: function(child, targetIndex) + { + var wasSelected = child.selected; + this.removeChild(child); + this.insertChild(child, targetIndex); + if (wasSelected) + child.select(); + }, + + /** + * @param {boolean=} fullRefresh + */ + _updateChildren: function(fullRefresh) + { + if (this._updateChildrenInProgress || !this.treeOutline._visible) + return; + + this._updateChildrenInProgress = true; + var selectedNode = this.treeOutline.selectedDOMNode(); + var originalScrollTop = 0; + if (fullRefresh) { + var treeOutlineContainerElement = this.treeOutline.element.parentNode; + originalScrollTop = treeOutlineContainerElement.scrollTop; + var selectedTreeElement = this.treeOutline.selectedTreeElement; + if (selectedTreeElement && selectedTreeElement.hasAncestor(this)) + this.select(); + this.removeChildren(); + } + + var treeElement = this; + var treeChildIndex = 0; + var elementToSelect; + + function updateChildrenOfNode(node) + { + var treeOutline = treeElement.treeOutline; + var child = node.firstChild; + while (child) { + var currentTreeElement = treeElement.children[treeChildIndex]; + if (!currentTreeElement || currentTreeElement.representedObject !== child) { + // Find any existing element that is later in the children list. + var existingTreeElement = null; + for (var i = (treeChildIndex + 1), size = treeElement.expandedChildCount; i < size; ++i) { + if (treeElement.children[i].representedObject === child) { + existingTreeElement = treeElement.children[i]; + break; + } + } + + if (existingTreeElement && existingTreeElement.parent === treeElement) { + // If an existing element was found and it has the same parent, just move it. + treeElement.moveChild(existingTreeElement, treeChildIndex); + } else { + // No existing element found, insert a new element. + if (treeChildIndex < treeElement.expandedChildrenLimit) { + var newElement = treeElement.insertChildElement(child, treeChildIndex); + if (child === selectedNode) + elementToSelect = newElement; + if (treeElement.expandedChildCount > treeElement.expandedChildrenLimit) + treeElement.expandedChildrenLimit++; + } + } + } + + child = child.nextSibling; + ++treeChildIndex; + } + } + + // Remove any tree elements that no longer have this node (or this node's contentDocument) as their parent. + for (var i = (this.children.length - 1); i >= 0; --i) { + var currentChild = this.children[i]; + var currentNode = currentChild.representedObject; + var currentParentNode = currentNode.parentNode; + + if (currentParentNode === this.representedObject) + continue; + + var selectedTreeElement = this.treeOutline.selectedTreeElement; + if (selectedTreeElement && (selectedTreeElement === currentChild || selectedTreeElement.hasAncestor(currentChild))) + this.select(); + + this.removeChildAtIndex(i); + } + + updateChildrenOfNode(this.representedObject); + this.adjustCollapsedRange(); + + var lastChild = this.children[this.children.length - 1]; + if (this.representedObject.nodeType() == Node.ELEMENT_NODE && (!lastChild || !lastChild._elementCloseTag)) + this.insertChildElement(this.representedObject, this.children.length, true); + + // We want to restore the original selection and tree scroll position after a full refresh, if possible. + if (fullRefresh && elementToSelect) { + elementToSelect.select(); + if (treeOutlineContainerElement && originalScrollTop <= treeOutlineContainerElement.scrollHeight) + treeOutlineContainerElement.scrollTop = originalScrollTop; + } + + delete this._updateChildrenInProgress; + }, + + adjustCollapsedRange: function() + { + // Ensure precondition: only the tree elements for node children are found in the tree + // (not the Expand All button or the closing tag). + if (this.expandAllButtonElement && this.expandAllButtonElement.__treeElement.parent) + this.removeChild(this.expandAllButtonElement.__treeElement); + + const node = this.representedObject; + if (!node.children) + return; + const childNodeCount = node.children.length; + + // In case some nodes from the expanded range were removed, pull some nodes from the collapsed range into the expanded range at the bottom. + for (var i = this.expandedChildCount, limit = Math.min(this.expandedChildrenLimit, childNodeCount); i < limit; ++i) + this.insertChildElement(node.children[i], i); + + const expandedChildCount = this.expandedChildCount; + if (childNodeCount > this.expandedChildCount) { + var targetButtonIndex = expandedChildCount; + if (!this.expandAllButtonElement) { + var button = document.createElement("button"); + button.className = "show-all-nodes"; + button.value = ""; + var item = new TreeElement(button, null, false); + item.selectable = false; + item.expandAllButton = true; + this.insertChild(item, targetButtonIndex); + this.expandAllButtonElement = item.listItemElement.firstChild; + this.expandAllButtonElement.__treeElement = item; + this.expandAllButtonElement.addEventListener("click", this.handleLoadAllChildren.bind(this), false); + } else if (!this.expandAllButtonElement.__treeElement.parent) + this.insertChild(this.expandAllButtonElement.__treeElement, targetButtonIndex); + this.expandAllButtonElement.textContent = WebInspector.UIString("Show All Nodes (%d More)", childNodeCount - expandedChildCount); + } else if (this.expandAllButtonElement) + delete this.expandAllButtonElement; + }, + + handleLoadAllChildren: function() + { + this.expandedChildrenLimit = Math.max(this.representedObject._childNodeCount, this.expandedChildrenLimit + WebInspector.ElementsTreeElement.InitialChildrenLimit); + }, + + onexpand: function() + { + if (this._elementCloseTag) + return; + + this.updateTitle(); + this.treeOutline.updateSelection(); + }, + + oncollapse: function() + { + if (this._elementCloseTag) + return; + + this.updateTitle(); + this.treeOutline.updateSelection(); + }, + + onreveal: function() + { + if (this.listItemElement) { + var tagSpans = this.listItemElement.getElementsByClassName("webkit-html-tag-name"); + if (tagSpans.length) + tagSpans[0].scrollIntoViewIfNeeded(false); + else + this.listItemElement.scrollIntoViewIfNeeded(false); + } + }, + + onselect: function(treeElement, selectedByUser) + { + this.treeOutline.suppressRevealAndSelect = true; + this.treeOutline.selectDOMNode(this.representedObject, selectedByUser); + if (selectedByUser) + WebInspector.domAgent.highlightDOMNode(this.representedObject.id); + this.updateSelection(); + this.treeOutline.suppressRevealAndSelect = false; + }, + + ondelete: function() + { + var startTagTreeElement = this.treeOutline.findTreeElement(this.representedObject); + startTagTreeElement ? startTagTreeElement.remove() : this.remove(); + return true; + }, + + onenter: function() + { + // On Enter or Return start editing the first attribute + // or create a new attribute on the selected element. + if (this.treeOutline.editing) + return false; + + this._startEditing(); + + // prevent a newline from being immediately inserted + return true; + }, + + selectOnMouseDown: function(event) + { + TreeElement.prototype.selectOnMouseDown.call(this, event); + + if (this._editing) + return; + + if (this.treeOutline._showInElementsPanelEnabled) { + WebInspector.showPanel("elements"); + this.treeOutline.selectDOMNode(this.representedObject, true); + } + + // Prevent selecting the nearest word on double click. + if (event.detail >= 2) + event.preventDefault(); + }, + + ondblclick: function(event) + { + if (this._editing || this._elementCloseTag) + return; + + if (this._startEditingTarget(event.target)) + return; + + if (this.hasChildren && !this.expanded) + this.expand(); + }, + + _insertInLastAttributePosition: function(tag, node) + { + if (tag.getElementsByClassName("webkit-html-attribute").length > 0) + tag.insertBefore(node, tag.lastChild); + else { + var nodeName = tag.textContent.match(/^<(.*?)>$/)[1]; + tag.textContent = ''; + tag.appendChild(document.createTextNode('<'+nodeName)); + tag.appendChild(node); + tag.appendChild(document.createTextNode('>')); + } + + this.updateSelection(); + }, + + _startEditingTarget: function(eventTarget) + { + if (this.treeOutline.selectedDOMNode() != this.representedObject) + return; + + if (this.representedObject.nodeType() != Node.ELEMENT_NODE && this.representedObject.nodeType() != Node.TEXT_NODE) + return false; + + var textNode = eventTarget.enclosingNodeOrSelfWithClass("webkit-html-text-node"); + if (textNode) + return this._startEditingTextNode(textNode); + + var attribute = eventTarget.enclosingNodeOrSelfWithClass("webkit-html-attribute"); + if (attribute) + return this._startEditingAttribute(attribute, eventTarget); + + var tagName = eventTarget.enclosingNodeOrSelfWithClass("webkit-html-tag-name"); + if (tagName) + return this._startEditingTagName(tagName); + + var newAttribute = eventTarget.enclosingNodeOrSelfWithClass("add-attribute"); + if (newAttribute) + return this._addNewAttribute(); + + return false; + }, + + _populateTagContextMenu: function(contextMenu, event) + { + var attribute = event.target.enclosingNodeOrSelfWithClass("webkit-html-attribute"); + var newAttribute = event.target.enclosingNodeOrSelfWithClass("add-attribute"); + + // Add attribute-related actions. + contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Add attribute" : "Add Attribute"), this._addNewAttribute.bind(this)); + if (attribute && !newAttribute) + contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Edit attribute" : "Edit Attribute"), this._startEditingAttribute.bind(this, attribute, event.target)); + contextMenu.appendSeparator(); + + this._populateNodeContextMenu(contextMenu); + this.treeOutline._populateContextMenu(contextMenu, this.representedObject); + }, + + _populateTextContextMenu: function(contextMenu, textNode) + { + contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Edit text" : "Edit Text"), this._startEditingTextNode.bind(this, textNode)); + this._populateNodeContextMenu(contextMenu); + }, + + _populateNodeContextMenu: function(contextMenu) + { + // Add free-form node-related actions. + contextMenu.appendItem(WebInspector.UIString("Edit as HTML"), this._editAsHTML.bind(this)); + contextMenu.appendItem(WebInspector.UIString("Copy as HTML"), this._copyHTML.bind(this)); + contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Delete node" : "Delete Node"), this.remove.bind(this)); + }, + + _startEditing: function() + { + if (this.treeOutline.selectedDOMNode() !== this.representedObject) + return; + + var listItem = this._listItemNode; + + if (this._canAddAttributes) { + var attribute = listItem.getElementsByClassName("webkit-html-attribute")[0]; + if (attribute) + return this._startEditingAttribute(attribute, attribute.getElementsByClassName("webkit-html-attribute-value")[0]); + + return this._addNewAttribute(); + } + + if (this.representedObject.nodeType() === Node.TEXT_NODE) { + var textNode = listItem.getElementsByClassName("webkit-html-text-node")[0]; + if (textNode) + return this._startEditingTextNode(textNode); + return; + } + }, + + _addNewAttribute: function() + { + // Cannot just convert the textual html into an element without + // a parent node. Use a temporary span container for the HTML. + var container = document.createElement("span"); + this._buildAttributeDOM(container, " ", ""); + var attr = container.firstChild; + attr.style.marginLeft = "2px"; // overrides the .editing margin rule + attr.style.marginRight = "2px"; // overrides the .editing margin rule + + var tag = this.listItemElement.getElementsByClassName("webkit-html-tag")[0]; + this._insertInLastAttributePosition(tag, attr); + return this._startEditingAttribute(attr, attr); + }, + + _triggerEditAttribute: function(attributeName) + { + var attributeElements = this.listItemElement.getElementsByClassName("webkit-html-attribute-name"); + for (var i = 0, len = attributeElements.length; i < len; ++i) { + if (attributeElements[i].textContent === attributeName) { + for (var elem = attributeElements[i].nextSibling; elem; elem = elem.nextSibling) { + if (elem.nodeType !== Node.ELEMENT_NODE) + continue; + + if (elem.hasStyleClass("webkit-html-attribute-value")) + return this._startEditingAttribute(elem.parentNode, elem); + } + } + } + }, + + _startEditingAttribute: function(attribute, elementForSelection) + { + if (WebInspector.isBeingEdited(attribute)) + return true; + + var attributeNameElement = attribute.getElementsByClassName("webkit-html-attribute-name")[0]; + if (!attributeNameElement) + return false; + + var attributeName = attributeNameElement.textContent; + + function removeZeroWidthSpaceRecursive(node) + { + if (node.nodeType === Node.TEXT_NODE) { + node.nodeValue = node.nodeValue.replace(/\u200B/g, ""); + return; + } + + if (node.nodeType !== Node.ELEMENT_NODE) + return; + + for (var child = node.firstChild; child; child = child.nextSibling) + removeZeroWidthSpaceRecursive(child); + } + + // Remove zero-width spaces that were added by nodeTitleInfo. + removeZeroWidthSpaceRecursive(attribute); + + var config = new WebInspector.EditingConfig(this._attributeEditingCommitted.bind(this), this._editingCancelled.bind(this), attributeName); + this._editing = WebInspector.startEditing(attribute, config); + + window.getSelection().setBaseAndExtent(elementForSelection, 0, elementForSelection, 1); + + return true; + }, + + _startEditingTextNode: function(textNode) + { + if (WebInspector.isBeingEdited(textNode)) + return true; + + var config = new WebInspector.EditingConfig(this._textNodeEditingCommitted.bind(this), this._editingCancelled.bind(this)); + this._editing = WebInspector.startEditing(textNode, config); + window.getSelection().setBaseAndExtent(textNode, 0, textNode, 1); + + return true; + }, + + _startEditingTagName: function(tagNameElement) + { + if (!tagNameElement) { + tagNameElement = this.listItemElement.getElementsByClassName("webkit-html-tag-name")[0]; + if (!tagNameElement) + return false; + } + + var tagName = tagNameElement.textContent; + if (WebInspector.ElementsTreeElement.EditTagBlacklist[tagName.toLowerCase()]) + return false; + + if (WebInspector.isBeingEdited(tagNameElement)) + return true; + + var closingTagElement = this._distinctClosingTagElement(); + + function keyupListener(event) + { + if (closingTagElement) + closingTagElement.textContent = ""; + } + + function editingComitted(element, newTagName) + { + tagNameElement.removeEventListener('keyup', keyupListener, false); + this._tagNameEditingCommitted.apply(this, arguments); + } + + function editingCancelled() + { + tagNameElement.removeEventListener('keyup', keyupListener, false); + this._editingCancelled.apply(this, arguments); + } + + tagNameElement.addEventListener('keyup', keyupListener, false); + + var config = new WebInspector.EditingConfig(editingComitted.bind(this), editingCancelled.bind(this), tagName); + this._editing = WebInspector.startEditing(tagNameElement, config); + window.getSelection().setBaseAndExtent(tagNameElement, 0, tagNameElement, 1); + return true; + }, + + _startEditingAsHTML: function(commitCallback, error, initialValue) + { + if (error) + return; + if (this._htmlEditElement && WebInspector.isBeingEdited(this._htmlEditElement)) + return; + + this._htmlEditElement = document.createElement("div"); + this._htmlEditElement.className = "source-code elements-tree-editor"; + this._htmlEditElement.textContent = initialValue; + + // Hide header items. + var child = this.listItemElement.firstChild; + while (child) { + child.style.display = "none"; + child = child.nextSibling; + } + // Hide children item. + if (this._childrenListNode) + this._childrenListNode.style.display = "none"; + // Append editor. + this.listItemElement.appendChild(this._htmlEditElement); + + this.updateSelection(); + + function commit() + { + commitCallback(this._htmlEditElement.textContent); + dispose.call(this); + } + + function dispose() + { + this._editing = false; + + // Remove editor. + this.listItemElement.removeChild(this._htmlEditElement); + delete this._htmlEditElement; + // Unhide children item. + if (this._childrenListNode) + this._childrenListNode.style.removeProperty("display"); + // Unhide header items. + var child = this.listItemElement.firstChild; + while (child) { + child.style.removeProperty("display"); + child = child.nextSibling; + } + + this.updateSelection(); + } + + var config = new WebInspector.EditingConfig(commit.bind(this), dispose.bind(this)); + config.setMultiline(true); + this._editing = WebInspector.startEditing(this._htmlEditElement, config); + }, + + _attributeEditingCommitted: function(element, newText, oldText, attributeName, moveDirection) + { + this._editing = false; + + var treeOutline = this.treeOutline; + /** + * @param {Protocol.Error=} error + */ + function moveToNextAttributeIfNeeded(error) + { + if (error) + this._editingCancelled(element, attributeName); + + if (!moveDirection) + return; + + treeOutline._updateModifiedNodes(); + + // Search for the attribute's position, and then decide where to move to. + var attributes = this.representedObject.attributes(); + for (var i = 0; i < attributes.length; ++i) { + if (attributes[i].name !== attributeName) + continue; + + if (moveDirection === "backward") { + if (i === 0) + this._startEditingTagName(); + else + this._triggerEditAttribute(attributes[i - 1].name); + } else { + if (i === attributes.length - 1) + this._addNewAttribute(); + else + this._triggerEditAttribute(attributes[i + 1].name); + } + return; + } + + // Moving From the "New Attribute" position. + if (moveDirection === "backward") { + if (newText === " ") { + // Moving from "New Attribute" that was not edited + if (attributes.length > 0) + this._triggerEditAttribute(attributes[attributes.length - 1].name); + } else { + // Moving from "New Attribute" that holds new value + if (attributes.length > 1) + this._triggerEditAttribute(attributes[attributes.length - 2].name); + } + } else if (moveDirection === "forward") { + if (!/^\s*$/.test(newText)) + this._addNewAttribute(); + else + this._startEditingTagName(); + } + } + + if (oldText !== newText) + this.representedObject.setAttribute(attributeName, newText, moveToNextAttributeIfNeeded.bind(this)); + else + moveToNextAttributeIfNeeded.call(this); + }, + + _tagNameEditingCommitted: function(element, newText, oldText, tagName, moveDirection) + { + this._editing = false; + var self = this; + + function cancel() + { + var closingTagElement = self._distinctClosingTagElement(); + if (closingTagElement) + closingTagElement.textContent = ""; + + self._editingCancelled(element, tagName); + moveToNextAttributeIfNeeded.call(self); + } + + function moveToNextAttributeIfNeeded() + { + if (moveDirection !== "forward") { + this._addNewAttribute(); + return; + } + + var attributes = this.representedObject.attributes(); + if (attributes.length > 0) + this._triggerEditAttribute(attributes[0].name); + else + this._addNewAttribute(); + } + + newText = newText.trim(); + if (newText === oldText) { + cancel(); + return; + } + + var treeOutline = this.treeOutline; + var wasExpanded = this.expanded; + + function changeTagNameCallback(error, nodeId) + { + if (error || !nodeId) { + cancel(); + return; + } + + var node = WebInspector.domAgent.nodeForId(nodeId); + + // Select it and expand if necessary. We force tree update so that it processes dom events and is up to date. + treeOutline._updateModifiedNodes(); + treeOutline.selectDOMNode(node, true); + + var newTreeItem = treeOutline.findTreeElement(node); + if (wasExpanded) + newTreeItem.expand(); + + moveToNextAttributeIfNeeded.call(newTreeItem); + } + + this.representedObject.setNodeName(newText, changeTagNameCallback); + }, + + _textNodeEditingCommitted: function(element, newText) + { + this._editing = false; + + var textNode; + if (this.representedObject.nodeType() === Node.ELEMENT_NODE) { + // We only show text nodes inline in elements if the element only + // has a single child, and that child is a text node. + textNode = this.representedObject.firstChild; + } else if (this.representedObject.nodeType() == Node.TEXT_NODE) + textNode = this.representedObject; + + textNode.setNodeValue(newText, this.updateTitle.bind(this)); + }, + + _editingCancelled: function(element, context) + { + this._editing = false; + + // Need to restore attributes structure. + this.updateTitle(); + }, + + _distinctClosingTagElement: function() + { + // FIXME: Improve the Tree Element / Outline Abstraction to prevent crawling the DOM + + // For an expanded element, it will be the last element with class "close" + // in the child element list. + if (this.expanded) { + var closers = this._childrenListNode.querySelectorAll(".close"); + return closers[closers.length-1]; + } + + // Remaining cases are single line non-expanded elements with a closing + // tag, or HTML elements without a closing tag (such as
      ). Return + // null in the case where there isn't a closing tag. + var tags = this.listItemElement.getElementsByClassName("webkit-html-tag"); + return (tags.length === 1 ? null : tags[tags.length-1]); + }, + + /** + * @param {boolean=} onlySearchQueryChanged + */ + updateTitle: function(onlySearchQueryChanged) + { + // If we are editing, return early to prevent canceling the edit. + // After editing is committed updateTitle will be called. + if (this._editing) + return; + + if (onlySearchQueryChanged) { + if (this._highlightResult) + this._updateSearchHighlight(false); + } else { + var highlightElement = document.createElement("span"); + highlightElement.className = "highlight"; + highlightElement.appendChild(this._nodeTitleInfo(WebInspector.linkifyURLAsNode).titleDOM); + this.title = highlightElement; + delete this._highlightResult; + } + + delete this.selectionElement; + this.updateSelection(); + this._preventFollowingLinksOnDoubleClick(); + this._highlightSearchResults(); + }, + + /** + * @param {WebInspector.DOMNode=} node + * @param {function(string, string, string, boolean=, string=)=} linkify + */ + _buildAttributeDOM: function(parentElement, name, value, node, linkify) + { + var hasText = (value.length > 0); + var attrSpanElement = parentElement.createChild("span", "webkit-html-attribute"); + var attrNameElement = attrSpanElement.createChild("span", "webkit-html-attribute-name"); + attrNameElement.textContent = name; + + if (hasText) + attrSpanElement.appendChild(document.createTextNode("=\u200B\"")); + + if (linkify && (name === "src" || name === "href")) { + var rewrittenHref = WebInspector.resourceURLForRelatedNode(node, value); + value = value.replace(/([\/;:\)\]\}])/g, "$1\u200B"); + if (rewrittenHref === null) { + var attrValueElement = attrSpanElement.createChild("span", "webkit-html-attribute-value"); + attrValueElement.textContent = value; + } else { + if (value.indexOf("data:") === 0) + value = value.trimMiddle(60); + attrSpanElement.appendChild(linkify(rewrittenHref, value, "webkit-html-attribute-value", node.nodeName().toLowerCase() === "a")); + } + } else { + value = value.replace(/([\/;:\)\]\}])/g, "$1\u200B"); + var attrValueElement = attrSpanElement.createChild("span", "webkit-html-attribute-value"); + attrValueElement.textContent = value; + } + + if (hasText) + attrSpanElement.appendChild(document.createTextNode("\"")); + }, + + /** + * @param {function(string, string, string, boolean=, string=)=} linkify + */ + _buildTagDOM: function(parentElement, tagName, isClosingTag, isDistinctTreeElement, linkify) + { + var node = /** @type WebInspector.DOMNode */ this.representedObject; + var classes = [ "webkit-html-tag" ]; + if (isClosingTag && isDistinctTreeElement) + classes.push("close"); + var tagElement = parentElement.createChild("span", classes.join(" ")); + tagElement.appendChild(document.createTextNode("<")); + var tagNameElement = tagElement.createChild("span", isClosingTag ? "" : "webkit-html-tag-name"); + tagNameElement.textContent = (isClosingTag ? "/" : "") + tagName; + if (!isClosingTag && node.hasAttributes()) { + var attributes = node.attributes(); + for (var i = 0; i < attributes.length; ++i) { + var attr = attributes[i]; + tagElement.appendChild(document.createTextNode(" ")); + this._buildAttributeDOM(tagElement, attr.name, attr.value, node, linkify); + } + } + tagElement.appendChild(document.createTextNode(">")); + parentElement.appendChild(document.createTextNode("\u200B")); + }, + + _nodeTitleInfo: function(linkify) + { + var node = this.representedObject; + var info = {titleDOM: document.createDocumentFragment(), hasChildren: this.hasChildren}; + + switch (node.nodeType()) { + case Node.DOCUMENT_FRAGMENT_NODE: + info.titleDOM.appendChild(document.createTextNode("Document Fragment")); + break; + + case Node.ATTRIBUTE_NODE: + var value = node.value || "\u200B"; // Zero width space to force showing an empty value. + this._buildAttributeDOM(info.titleDOM, node.name, value); + break; + + case Node.ELEMENT_NODE: + var tagName = node.nodeNameInCorrectCase(); + if (this._elementCloseTag) { + this._buildTagDOM(info.titleDOM, tagName, true, true); + info.hasChildren = false; + break; + } + + this._buildTagDOM(info.titleDOM, tagName, false, false, linkify); + + var textChild = this._singleTextChild(node); + var showInlineText = textChild && textChild.nodeValue().length < Preferences.maxInlineTextChildLength; + + if (!this.expanded && (!showInlineText && (this.treeOutline.isXMLMimeType || !WebInspector.ElementsTreeElement.ForbiddenClosingTagElements[tagName]))) { + if (this.hasChildren) { + var textNodeElement = info.titleDOM.createChild("span", "webkit-html-text-node"); + textNodeElement.textContent = "\u2026"; + info.titleDOM.appendChild(document.createTextNode("\u200B")); + } + this._buildTagDOM(info.titleDOM, tagName, true, false); + } + + // If this element only has a single child that is a text node, + // just show that text and the closing tag inline rather than + // create a subtree for them + if (showInlineText) { + var textNodeElement = info.titleDOM.createChild("span", "webkit-html-text-node"); + textNodeElement.textContent = textChild.nodeValue(); + info.titleDOM.appendChild(document.createTextNode("\u200B")); + this._buildTagDOM(info.titleDOM, tagName, true, false); + info.hasChildren = false; + } + break; + + case Node.TEXT_NODE: + if (node.parentNode && node.parentNode.nodeName().toLowerCase() === "script") { + var newNode = info.titleDOM.createChild("span", "webkit-html-text-node webkit-html-js-node"); + newNode.textContent = node.nodeValue(); + + var javascriptSyntaxHighlighter = new WebInspector.DOMSyntaxHighlighter("text/javascript", true); + javascriptSyntaxHighlighter.syntaxHighlightNode(newNode); + } else if (node.parentNode && node.parentNode.nodeName().toLowerCase() === "style") { + var newNode = info.titleDOM.createChild("span", "webkit-html-text-node webkit-html-css-node"); + newNode.textContent = node.nodeValue(); + + var cssSyntaxHighlighter = new WebInspector.DOMSyntaxHighlighter("text/css", true); + cssSyntaxHighlighter.syntaxHighlightNode(newNode); + } else { + info.titleDOM.appendChild(document.createTextNode("\"")); + var textNodeElement = info.titleDOM.createChild("span", "webkit-html-text-node"); + textNodeElement.textContent = node.nodeValue(); + info.titleDOM.appendChild(document.createTextNode("\"")); + } + break; + + case Node.COMMENT_NODE: + var commentElement = info.titleDOM.createChild("span", "webkit-html-comment"); + commentElement.appendChild(document.createTextNode("")); + break; + + case Node.DOCUMENT_TYPE_NODE: + var docTypeElement = info.titleDOM.createChild("span", "webkit-html-doctype"); + docTypeElement.appendChild(document.createTextNode("")); + break; + + case Node.CDATA_SECTION_NODE: + var cdataElement = info.titleDOM.createChild("span", "webkit-html-text-node"); + cdataElement.appendChild(document.createTextNode("")); + break; + default: + var defaultElement = info.titleDOM.appendChild(document.createTextNode(node.nodeNameInCorrectCase().collapseWhitespace())); + } + + return info; + }, + + _singleTextChild: function(node) + { + if (!node) + return null; + + var firstChild = node.firstChild; + if (!firstChild || firstChild.nodeType() !== Node.TEXT_NODE) + return null; + + var sibling = firstChild.nextSibling; + return sibling ? null : firstChild; + }, + + _showInlineText: function(node) + { + if (node.nodeType() === Node.ELEMENT_NODE) { + var textChild = this._singleTextChild(node); + if (textChild && textChild.nodeValue().length < Preferences.maxInlineTextChildLength) + return true; + } + return false; + }, + + remove: function() + { + var parentElement = this.parent; + if (!parentElement) + return; + + var self = this; + function removeNodeCallback(error, removedNodeId) + { + if (error) + return; + + parentElement.removeChild(self); + parentElement.adjustCollapsedRange(); + } + + this.representedObject.removeNode(removeNodeCallback); + }, + + _editAsHTML: function() + { + var treeOutline = this.treeOutline; + var node = this.representedObject; + var parentNode = node.parentNode; + var index = node.index; + var wasExpanded = this.expanded; + + function selectNode(error, nodeId) + { + if (error) + return; + + // Select it and expand if necessary. We force tree update so that it processes dom events and is up to date. + treeOutline._updateModifiedNodes(); + + var newNode = parentNode ? parentNode.children[index] || parentNode : null; + if (!newNode) + return; + + treeOutline.selectDOMNode(newNode, true); + + if (wasExpanded) { + var newTreeItem = treeOutline.findTreeElement(newNode); + if (newTreeItem) + newTreeItem.expand(); + } + } + + function commitChange(value) + { + node.setOuterHTML(value, selectNode); + } + + node.getOuterHTML(this._startEditingAsHTML.bind(this, commitChange)); + }, + + _copyHTML: function() + { + this.representedObject.copyNode(); + }, + + _highlightSearchResults: function() + { + if (!this._searchQuery || !this._searchHighlightsVisible) + return; + if (this._highlightResult) { + this._updateSearchHighlight(true); + return; + } + + var text = this.listItemElement.textContent; + var regexObject = createPlainTextSearchRegex(this._searchQuery, "gi"); + + var offset = 0; + var match = regexObject.exec(text); + var matchRanges = []; + while (match) { + matchRanges.push({ offset: match.index, length: match[0].length }); + match = regexObject.exec(text); + } + + // Fall back for XPath, etc. matches. + if (!matchRanges.length) + matchRanges.push({ offset: 0, length: text.length }); + + this._highlightResult = []; + highlightSearchResults(this.listItemElement, matchRanges, this._highlightResult); + } +} + +WebInspector.ElementsTreeElement.prototype.__proto__ = TreeElement.prototype; + +/** + * @constructor + */ +WebInspector.ElementsTreeUpdater = function(treeOutline) +{ + WebInspector.domAgent.addEventListener(WebInspector.DOMAgent.Events.NodeInserted, this._nodeInserted, this); + WebInspector.domAgent.addEventListener(WebInspector.DOMAgent.Events.NodeRemoved, this._nodeRemoved, this); + WebInspector.domAgent.addEventListener(WebInspector.DOMAgent.Events.AttrModified, this._attributesUpdated, this); + WebInspector.domAgent.addEventListener(WebInspector.DOMAgent.Events.AttrRemoved, this._attributesUpdated, this); + WebInspector.domAgent.addEventListener(WebInspector.DOMAgent.Events.CharacterDataModified, this._characterDataModified, this); + WebInspector.domAgent.addEventListener(WebInspector.DOMAgent.Events.DocumentUpdated, this._documentUpdated, this); + WebInspector.domAgent.addEventListener(WebInspector.DOMAgent.Events.ChildNodeCountUpdated, this._childNodeCountUpdated, this); + + this._treeOutline = treeOutline; + this._recentlyModifiedNodes = []; +} + +WebInspector.ElementsTreeUpdater.prototype = { + _documentUpdated: function(event) + { + var inspectedRootDocument = event.data; + + this._reset(); + + if (!inspectedRootDocument) + return; + + this._treeOutline.rootDOMNode = inspectedRootDocument; + }, + + _attributesUpdated: function(event) + { + this._recentlyModifiedNodes.push({node: event.data.node, updated: true}); + if (this._treeOutline._visible) + this._updateModifiedNodesSoon(); + }, + + _characterDataModified: function(event) + { + this._recentlyModifiedNodes.push({node: event.data, updated: true}); + if (this._treeOutline._visible) + this._updateModifiedNodesSoon(); + }, + + _nodeInserted: function(event) + { + this._recentlyModifiedNodes.push({node: event.data, parent: event.data.parentNode, inserted: true}); + if (this._treeOutline._visible) + this._updateModifiedNodesSoon(); + }, + + _nodeRemoved: function(event) + { + this._recentlyModifiedNodes.push({node: event.data.node, parent: event.data.parent, removed: true}); + if (this._treeOutline._visible) + this._updateModifiedNodesSoon(); + }, + + _childNodeCountUpdated: function(event) + { + var treeElement = this._treeOutline.findTreeElement(event.data); + if (treeElement) + treeElement.hasChildren = event.data.hasChildNodes(); + }, + + _updateModifiedNodesSoon: function() + { + if (this._updateModifiedNodesTimeout) + return; + this._updateModifiedNodesTimeout = setTimeout(this._updateModifiedNodes.bind(this), 0); + }, + + _updateModifiedNodes: function() + { + if (this._updateModifiedNodesTimeout) { + clearTimeout(this._updateModifiedNodesTimeout); + delete this._updateModifiedNodesTimeout; + } + + var updatedParentTreeElements = []; + + for (var i = 0; i < this._recentlyModifiedNodes.length; ++i) { + var parent = this._recentlyModifiedNodes[i].parent; + var node = this._recentlyModifiedNodes[i].node; + + if (this._recentlyModifiedNodes[i].updated) { + var nodeItem = this._treeOutline.findTreeElement(node); + if (nodeItem) + nodeItem.updateTitle(); + continue; + } + + if (!parent) + continue; + + var parentNodeItem = this._treeOutline.findTreeElement(parent); + if (parentNodeItem && !parentNodeItem.alreadyUpdatedChildren) { + parentNodeItem.updateChildren(); + parentNodeItem.alreadyUpdatedChildren = true; + updatedParentTreeElements.push(parentNodeItem); + } + } + + for (var i = 0; i < updatedParentTreeElements.length; ++i) + delete updatedParentTreeElements[i].alreadyUpdatedChildren; + + this._recentlyModifiedNodes = []; + }, + + _reset: function() + { + this._treeOutline.rootDOMNode = null; + this._treeOutline.selectDOMNode(null, false); + WebInspector.domAgent.hideDOMNodeHighlight(); + this._recentlyModifiedNodes = []; + } +} +/* DOMPresentationUtils.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2008 Matt Lilek + * Copyright (C) 2009 Joseph Pecoraro + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.DOMPresentationUtils = {} + +WebInspector.DOMPresentationUtils.decorateNodeLabel = function(node, parentElement) +{ + var title = node.nodeNameInCorrectCase(); + + var nameElement = document.createElement("span"); + nameElement.textContent = title; + parentElement.appendChild(nameElement); + + var idAttribute = node.getAttribute("id"); + if (idAttribute) { + var idElement = document.createElement("span"); + parentElement.appendChild(idElement); + + var part = "#" + idAttribute; + title += part; + idElement.appendChild(document.createTextNode(part)); + + // Mark the name as extra, since the ID is more important. + nameElement.className = "extra"; + } + + var classAttribute = node.getAttribute("class"); + if (classAttribute) { + var classes = classAttribute.split(/\s+/); + var foundClasses = {}; + + if (classes.length) { + var classesElement = document.createElement("span"); + classesElement.className = "extra"; + parentElement.appendChild(classesElement); + + for (var i = 0; i < classes.length; ++i) { + var className = classes[i]; + if (className && !(className in foundClasses)) { + var part = "." + className; + title += part; + classesElement.appendChild(document.createTextNode(part)); + foundClasses[className] = true; + } + } + } + } + parentElement.title = title; +} + +WebInspector.DOMPresentationUtils.linkifyNodeReference = function(node) +{ + var link = document.createElement("span"); + link.className = "node-link"; + WebInspector.DOMPresentationUtils.decorateNodeLabel(node, link); + + link.addEventListener("click", WebInspector.domAgent.inspectElement.bind(WebInspector.domAgent, node.id), false); + link.addEventListener("mouseover", WebInspector.domAgent.highlightDOMNode.bind(WebInspector.domAgent, node.id, ""), false); + link.addEventListener("mouseout", WebInspector.domAgent.hideDOMNodeHighlight.bind(WebInspector.domAgent), false); + + return link; +} + +WebInspector.DOMPresentationUtils.linkifyNodeById = function(nodeId) +{ + var node = WebInspector.domAgent.nodeForId(nodeId); + if (!node) + return document.createTextNode(WebInspector.UIString("")); + return WebInspector.DOMPresentationUtils.linkifyNodeReference(node); +} +/* SidebarTreeElement.js */ + +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {TreeElement} + */ +WebInspector.SidebarSectionTreeElement = function(title, representedObject, hasChildren) +{ + TreeElement.call(this, title.escapeHTML(), representedObject || {}, hasChildren); + this.expand(); +} + +WebInspector.SidebarSectionTreeElement.prototype = { + selectable: false, + + collapse: function() + { + // Should not collapse as it is not selectable. + }, + + get smallChildren() + { + return this._smallChildren; + }, + + set smallChildren(x) + { + if (this._smallChildren === x) + return; + + this._smallChildren = x; + + if (this._smallChildren) + this._childrenListNode.addStyleClass("small"); + else + this._childrenListNode.removeStyleClass("small"); + }, + + onattach: function() + { + this._listItemNode.addStyleClass("sidebar-tree-section"); + }, + + onreveal: function() + { + if (this.listItemElement) + this.listItemElement.scrollIntoViewIfNeeded(false); + } +} + +WebInspector.SidebarSectionTreeElement.prototype.__proto__ = TreeElement.prototype; + +/** + * @constructor + * @extends {TreeElement} + * @param {string=} subtitle + * @param {Object=} representedObject + * @param {boolean=} hasChildren + */ +WebInspector.SidebarTreeElement = function(className, title, subtitle, representedObject, hasChildren) +{ + TreeElement.call(this, "", representedObject, hasChildren); + + if (hasChildren) { + this.disclosureButton = document.createElement("button"); + this.disclosureButton.className = "disclosure-button"; + } + + if (!this.iconElement) { + this.iconElement = document.createElement("img"); + this.iconElement.className = "icon"; + } + + this.statusElement = document.createElement("div"); + this.statusElement.className = "status"; + + this.titlesElement = document.createElement("div"); + this.titlesElement.className = "titles"; + + this.titleElement = document.createElement("span"); + this.titleElement.className = "title"; + this.titlesElement.appendChild(this.titleElement); + + this.subtitleElement = document.createElement("span"); + this.subtitleElement.className = "subtitle"; + this.titlesElement.appendChild(this.subtitleElement); + + this.className = className; + this.mainTitle = title; + this.subtitle = subtitle; +} + +WebInspector.SidebarTreeElement.prototype = { + get small() + { + return this._small; + }, + + set small(x) + { + this._small = x; + + if (this._listItemNode) { + if (this._small) + this._listItemNode.addStyleClass("small"); + else + this._listItemNode.removeStyleClass("small"); + } + }, + + get mainTitle() + { + return this._mainTitle; + }, + + set mainTitle(x) + { + this._mainTitle = x; + this.refreshTitles(); + }, + + get subtitle() + { + return this._subtitle; + }, + + set subtitle(x) + { + this._subtitle = x; + this.refreshTitles(); + }, + + get bubbleText() + { + return this._bubbleText; + }, + + set bubbleText(x) + { + if (!this.bubbleElement) { + this.bubbleElement = document.createElement("div"); + this.bubbleElement.className = "bubble"; + this.statusElement.appendChild(this.bubbleElement); + } + + this._bubbleText = x; + this.bubbleElement.textContent = x; + }, + + set wait(x) + { + if (x) + this._listItemNode.addStyleClass("wait"); + else + this._listItemNode.removeStyleClass("wait"); + }, + + refreshTitles: function() + { + var mainTitle = this.mainTitle; + if (this.titleElement.textContent !== mainTitle) + this.titleElement.textContent = mainTitle; + + var subtitle = this.subtitle; + if (subtitle) { + if (this.subtitleElement.textContent !== subtitle) + this.subtitleElement.textContent = subtitle; + this.titlesElement.removeStyleClass("no-subtitle"); + } else { + this.subtitleElement.textContent = ""; + this.titlesElement.addStyleClass("no-subtitle"); + } + }, + + isEventWithinDisclosureTriangle: function(event) + { + return event.target === this.disclosureButton; + }, + + onattach: function() + { + this._listItemNode.addStyleClass("sidebar-tree-item"); + + if (this.className) + this._listItemNode.addStyleClass(this.className); + + if (this.small) + this._listItemNode.addStyleClass("small"); + + if (this.hasChildren && this.disclosureButton) + this._listItemNode.appendChild(this.disclosureButton); + + this._listItemNode.appendChild(this.iconElement); + this._listItemNode.appendChild(this.statusElement); + this._listItemNode.appendChild(this.titlesElement); + }, + + onreveal: function() + { + if (this._listItemNode) + this._listItemNode.scrollIntoViewIfNeeded(false); + } +} + +WebInspector.SidebarTreeElement.prototype.__proto__ = TreeElement.prototype; +/* Section.js */ + +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * Copyright (C) 2009 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @param {string=} subtitle + */ +WebInspector.Section = function(title, subtitle) +{ + this.element = document.createElement("div"); + this.element.className = "section"; + this.element._section = this; + + this.headerElement = document.createElement("div"); + this.headerElement.className = "header"; + + this.titleElement = document.createElement("div"); + this.titleElement.className = "title"; + + this.subtitleElement = document.createElement("div"); + this.subtitleElement.className = "subtitle"; + + this.headerElement.appendChild(this.subtitleElement); + this.headerElement.appendChild(this.titleElement); + + this.headerElement.addEventListener("click", this.handleClick.bind(this), false); + this.element.appendChild(this.headerElement); + + this.title = title; + this.subtitle = subtitle; + this._expanded = false; +} + +WebInspector.Section.prototype = { + get title() + { + return this._title; + }, + + set title(x) + { + if (this._title === x) + return; + this._title = x; + + if (x instanceof Node) { + this.titleElement.removeChildren(); + this.titleElement.appendChild(x); + } else + this.titleElement.textContent = x; + }, + + get subtitle() + { + return this._subtitle; + }, + + set subtitle(x) + { + if (this._subtitle === x) + return; + this._subtitle = x; + this.subtitleElement.textContent = x; + }, + + get subtitleAsTextForTest() + { + var result = this.subtitleElement.textContent; + var child = this.subtitleElement.querySelector("[data-uncopyable]"); + if (child) { + var linkData = child.getAttribute("data-uncopyable"); + if (linkData) + result += linkData; + } + return result; + }, + + get expanded() + { + return this._expanded; + }, + + set expanded(x) + { + if (x) + this.expand(); + else + this.collapse(); + }, + + get populated() + { + return this._populated; + }, + + set populated(x) + { + this._populated = x; + if (!x && this._expanded) { + this.onpopulate(); + this._populated = true; + } + }, + + onpopulate: function() + { + // Overriden by subclasses. + }, + + get firstSibling() + { + var parent = this.element.parentElement; + if (!parent) + return null; + + var childElement = parent.firstChild; + while (childElement) { + if (childElement._section) + return childElement._section; + childElement = childElement.nextSibling; + } + + return null; + }, + + get lastSibling() + { + var parent = this.element.parentElement; + if (!parent) + return null; + + var childElement = parent.lastChild; + while (childElement) { + if (childElement._section) + return childElement._section; + childElement = childElement.previousSibling; + } + + return null; + }, + + get nextSibling() + { + var curElement = this.element; + do { + curElement = curElement.nextSibling; + } while (curElement && !curElement._section); + + return curElement ? curElement._section : null; + }, + + get previousSibling() + { + var curElement = this.element; + do { + curElement = curElement.previousSibling; + } while (curElement && !curElement._section); + + return curElement ? curElement._section : null; + }, + + expand: function() + { + if (this._expanded) + return; + this._expanded = true; + this.element.addStyleClass("expanded"); + + if (!this._populated) { + this.onpopulate(); + this._populated = true; + } + }, + + collapse: function() + { + if (!this._expanded) + return; + this._expanded = false; + this.element.removeStyleClass("expanded"); + }, + + toggleExpanded: function() + { + this.expanded = !this.expanded; + }, + + handleClick: function(e) + { + this.toggleExpanded(); + e.stopPropagation(); + } +} +/* PropertiesSection.js */ + +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * Copyright (C) 2009 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.Section} + * @param {string=} subtitle + */ +WebInspector.PropertiesSection = function(title, subtitle) +{ + WebInspector.Section.call(this, title, subtitle); + + this.headerElement.addStyleClass("monospace"); + this.propertiesElement = document.createElement("ol"); + this.propertiesElement.className = "properties properties-tree monospace"; + this.propertiesElement.tabIndex = 0; + this.propertiesTreeOutline = new TreeOutline(this.propertiesElement); + this.propertiesTreeOutline.section = this; + + this.element.appendChild(this.propertiesElement); +} + +WebInspector.PropertiesSection.prototype.__proto__ = WebInspector.Section.prototype; +/* RemoteObject.js */ + +/* + * Copyright (C) 2009 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @param {string|undefined} objectId + * @param {string} type + * @param {string|undefined} subtype + * @param {*} value + * @param {string=} description + */ +WebInspector.RemoteObject = function(objectId, type, subtype, value, description) +{ + this._type = type; + this._subtype = subtype; + if (objectId) { + // handle + this._objectId = objectId; + this._description = description; + this._hasChildren = true; + } else { + // Primitive or null object. + console.assert(type !== "object" || value === null); + this._description = description || (value + ""); + this._hasChildren = false; + this.value = value; + } +} + +/** + * @param {number|string|boolean} value + * @return {WebInspector.RemoteObject} + */ +WebInspector.RemoteObject.fromPrimitiveValue = function(value) +{ + return new WebInspector.RemoteObject(undefined, typeof value, undefined, value); +} + +/** + * @param {Object} value + * @return {WebInspector.RemoteObject} + */ +WebInspector.RemoteObject.fromLocalObject = function(value) +{ + return new WebInspector.LocalJSONObject(value); +} + +/** + * @param {WebInspector.DOMNode} node + * @param {string} objectGroup + * @param {function(?WebInspector.RemoteObject)} callback + */ +WebInspector.RemoteObject.resolveNode = function(node, objectGroup, callback) +{ + /** + * @param {?Protocol.Error} error + * @param {RuntimeAgent.RemoteObject} object + */ + function mycallback(error, object) + { + if (!callback) + return; + + if (error || !object) + callback(null); + else + callback(WebInspector.RemoteObject.fromPayload(object)); + } + DOMAgent.resolveNode(node.id, objectGroup, mycallback); +} + +/** + * @param {RuntimeAgent.RemoteObject} payload + * @return {WebInspector.RemoteObject} + */ +WebInspector.RemoteObject.fromPayload = function(payload) +{ + console.assert(typeof payload === "object", "Remote object payload should only be an object"); + + return new WebInspector.RemoteObject(payload.objectId, payload.type, payload.subtype, payload.value, payload.description); +} + +/** + * @param {WebInspector.RemoteObject} remoteObject + * @return {string} + */ +WebInspector.RemoteObject.type = function(remoteObject) +{ + if (remoteObject === null) + return "null"; + + var type = typeof remoteObject; + if (type !== "object" && type !== "function") + return type; + + return remoteObject.type; +} + +WebInspector.RemoteObject.prototype = { + /** @return {RuntimeAgent.RemoteObjectId} */ + get objectId() + { + return this._objectId; + }, + + /** @return {string} */ + get type() + { + return this._type; + }, + + /** @return {string|undefined} */ + get subtype() + { + return this._subtype; + }, + + /** @return {string|undefined} */ + get description() + { + return this._description; + }, + + /** @return {boolean} */ + get hasChildren() + { + return this._hasChildren; + }, + + /** + * @param {function(Array.)} callback + */ + getOwnProperties: function(callback) + { + this._getProperties(true, callback); + }, + + /** + * @param {function(Array.)} callback + */ + getAllProperties: function(callback) + { + this._getProperties(false, callback); + }, + + /** + * @param {boolean} ownProperties + * @param {function(Array.)} callback + */ + _getProperties: function(ownProperties, callback) + { + if (!this._objectId) { + callback([]); + return; + } + + /** + * @param {?Protocol.Error} error + * @param {Array.} properties + */ + function remoteObjectBinder(error, properties) + { + if (error) { + callback(null); + return; + } + var result = []; + for (var i = 0; properties && i < properties.length; ++i) { + var property = properties[i]; + if (property.get || property.set) { + if (property.get) + result.push(new WebInspector.RemoteObjectProperty("get " + property.name, WebInspector.RemoteObject.fromPayload(property.get), property)); + if (property.set) + result.push(new WebInspector.RemoteObjectProperty("set " + property.name, WebInspector.RemoteObject.fromPayload(property.set), property)); + } else + result.push(new WebInspector.RemoteObjectProperty(property.name, WebInspector.RemoteObject.fromPayload(property.value), property)); + } + callback(result); + } + RuntimeAgent.getProperties(this._objectId, ownProperties, remoteObjectBinder); + }, + + /** + * @param {string} name + * @param {string} value + * @param {function(string=)} callback + */ + setPropertyValue: function(name, value, callback) + { + if (!this._objectId) { + callback("Can't set a property of non-object."); + return; + } + + RuntimeAgent.evaluate.invoke({expression:value, doNotPauseOnExceptions:true}, evaluatedCallback.bind(this)); + + /** + * @param {?Protocol.Error} error + * @param {RuntimeAgent.RemoteObject} result + * @param {boolean=} wasThrown + */ + function evaluatedCallback(error, result, wasThrown) + { + if (error || wasThrown) { + callback(error || result.description); + return; + } + + function setPropertyValue(propertyName, propertyValue) + { + this[propertyName] = propertyValue; + } + + delete result.description; // Optimize on traffic. + RuntimeAgent.callFunctionOn(this._objectId, setPropertyValue.toString(), [{ value:name }, result], undefined, propertySetCallback.bind(this)); + if (result._objectId) + RuntimeAgent.releaseObject(result._objectId); + } + + /** + * @param {?Protocol.Error} error + * @param {RuntimeAgent.RemoteObject} result + * @param {boolean=} wasThrown + */ + function propertySetCallback(error, result, wasThrown) + { + if (error || wasThrown) { + callback(error || result.description); + return; + } + callback(); + } + }, + + /** + * @param {function(DOMAgent.NodeId)} callback + */ + pushNodeToFrontend: function(callback) + { + if (this._objectId) + WebInspector.domAgent.pushNodeToFrontend(this._objectId, callback); + else + callback(0); + }, + + /** + * @param {string} functionDeclaration + * @param {function(?WebInspector.RemoteObject)} callback + */ + callFunction: function(functionDeclaration, callback) + { + function mycallback(error, result, wasThrown) + { + callback((error || wasThrown) ? null : WebInspector.RemoteObject.fromPayload(result)); + } + + RuntimeAgent.callFunctionOn(this._objectId, functionDeclaration.toString(), undefined, undefined, mycallback); + }, + + /** + * @param {string} functionDeclaration + * @param {function(*)} callback + */ + callFunctionJSON: function(functionDeclaration, callback) + { + function mycallback(error, result, wasThrown) + { + callback((error || wasThrown) ? null : result.value); + } + + RuntimeAgent.callFunctionOn(this._objectId, functionDeclaration.toString(), undefined, true, mycallback); + }, + + release: function() + { + RuntimeAgent.releaseObject(this._objectId); + } +} + +/** + * @constructor + * @param {string} name + * @param {WebInspector.RemoteObject} value + * @param {Object=} descriptor + */ +WebInspector.RemoteObjectProperty = function(name, value, descriptor) +{ + this.name = name; + this.value = value; + this.enumerable = descriptor ? !!descriptor.enumerable : true; + this.writable = descriptor ? !!descriptor.writable : true; + if (descriptor && descriptor.wasThrown) + this.wasThrown = true; +} + +/** + * @param {string} name + * @param {string} value + * @return {WebInspector.RemoteObjectProperty} + */ +WebInspector.RemoteObjectProperty.fromPrimitiveValue = function(name, value) +{ + return new WebInspector.RemoteObjectProperty(name, WebInspector.RemoteObject.fromPrimitiveValue(value)); +} + +// The below is a wrapper around a local object that provides an interface comaptible +// with RemoteObject, to be used by the UI code (primarily ObjectPropertiesSection). +// Note that only JSON-compliant objects are currently supported, as there's no provision +// for traversing prototypes, extracting class names via constuctor, handling properties +// or functions. + +/** + * @constructor + * @extends {WebInspector.RemoteObject} + * @param {Object} value + */ +WebInspector.LocalJSONObject = function(value) +{ + this._value = value; +} + +WebInspector.LocalJSONObject.prototype = { + /** + * @return {string} + */ + get description() + { + if (this._cachedDescription) + return this._cachedDescription; + + if (this.type === "object") { + switch (this.subtype) { + case "array": + function formatArrayItem(property) + { + return property.value.description; + } + this._cachedDescription = this._concatenate("[", "]", formatArrayItem); + break; + case "null": + this._cachedDescription = "null"; + break; + default: + function formatObjectItem(property) + { + return property.name + ":" + property.value.description; + } + this._cachedDescription = this._concatenate("{", "}", formatObjectItem); + } + } else + this._cachedDescription = String(this._value); + + return this._cachedDescription; + }, + + /** + * @param {string} prefix + * @param {string} suffix + * @return {string} + */ + _concatenate: function(prefix, suffix, formatProperty) + { + const previewChars = 100; + + var buffer = prefix; + var children = this._children(); + for (var i = 0; i < children.length; ++i) { + var itemDescription = formatProperty(children[i]); + if (buffer.length + itemDescription.length > previewChars) { + buffer += ",\u2026"; + break; + } + if (i) + buffer += ", "; + buffer += itemDescription; + } + buffer += suffix; + return buffer; + }, + + /** + * @return {string} + */ + get type() + { + return typeof this._value; + }, + + /** + * @return {string|undefined} + */ + get subtype() + { + if (this._value === null) + return "null"; + + if (this._value instanceof Array) + return "array"; + + return undefined; + }, + + /** + * @return {boolean} + */ + get hasChildren() + { + return typeof this._value === "object" && this._value !== null && !!Object.keys(this._value).length; + }, + + /** + * @param {function(Array.)} callback + */ + getOwnProperties: function(callback) + { + callback(this._children()); + }, + + /** + * @param {function(Array.)} callback + */ + getAllProperties: function(callback) + { + callback(this._children()); + }, + + /** + * @return {Array.} + */ + _children: function() + { + if (!this.hasChildren) + return []; + + function buildProperty(propName) + { + return new WebInspector.RemoteObjectProperty(propName, new WebInspector.LocalJSONObject(this._value[propName])); + } + if (!this._cachedChildren) + this._cachedChildren = Object.keys(this._value || {}).map(buildProperty.bind(this)); + return this._cachedChildren; + }, + + /** + * @return {boolean} + */ + isError: function() + { + return false; + } +} +/* ObjectPropertiesSection.js */ + +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * Copyright (C) 2009 Joseph Pecoraro + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.PropertiesSection} + * @param {*=} object + * @param {string=} title + * @param {string=} subtitle + * @param {string=} emptyPlaceholder + * @param {boolean=} ignoreHasOwnProperty + * @param {Array.=} extraProperties + * @param {function()=} treeElementConstructor + */ +WebInspector.ObjectPropertiesSection = function(object, title, subtitle, emptyPlaceholder, ignoreHasOwnProperty, extraProperties, treeElementConstructor) +{ + this.emptyPlaceholder = (emptyPlaceholder || WebInspector.UIString("No Properties")); + this.object = object; + this.ignoreHasOwnProperty = ignoreHasOwnProperty; + this.extraProperties = extraProperties; + this.treeElementConstructor = treeElementConstructor || WebInspector.ObjectPropertyTreeElement; + this.editable = true; + + WebInspector.PropertiesSection.call(this, title, subtitle); +} + +WebInspector.ObjectPropertiesSection.prototype = { + onpopulate: function() + { + this.update(); + }, + + update: function() + { + var self = this; + function callback(properties) + { + if (!properties) + return; + self.updateProperties(properties); + } + if (this.ignoreHasOwnProperty) + this.object.getAllProperties(callback); + else + this.object.getOwnProperties(callback); + }, + + updateProperties: function(properties, rootTreeElementConstructor, rootPropertyComparer) + { + if (!rootTreeElementConstructor) + rootTreeElementConstructor = this.treeElementConstructor; + + if (!rootPropertyComparer) + rootPropertyComparer = WebInspector.ObjectPropertiesSection.CompareProperties; + + if (this.extraProperties) + for (var i = 0; i < this.extraProperties.length; ++i) + properties.push(this.extraProperties[i]); + + properties.sort(rootPropertyComparer); + + this.propertiesTreeOutline.removeChildren(); + + for (var i = 0; i < properties.length; ++i) { + properties[i].parentObject = this.object; + this.propertiesTreeOutline.appendChild(new rootTreeElementConstructor(properties[i])); + } + + if (!this.propertiesTreeOutline.children.length) { + var title = document.createElement("div"); + title.className = "info"; + title.textContent = this.emptyPlaceholder; + var infoElement = new TreeElement(title, null, false); + this.propertiesTreeOutline.appendChild(infoElement); + } + this.propertiesForTest = properties; + } +} + +WebInspector.ObjectPropertiesSection.prototype.__proto__ = WebInspector.PropertiesSection.prototype; + +WebInspector.ObjectPropertiesSection.CompareProperties = function(propertyA, propertyB) +{ + var a = propertyA.name; + var b = propertyB.name; + if (a === "__proto__") + return 1; + if (b === "__proto__") + return -1; + + // if used elsewhere make sure to + // - convert a and b to strings (not needed here, properties are all strings) + // - check if a == b (not needed here, no two properties can be the same) + + var diff = 0; + var chunk = /^\d+|^\D+/; + var chunka, chunkb, anum, bnum; + while (diff === 0) { + if (!a && b) + return -1; + if (!b && a) + return 1; + chunka = a.match(chunk)[0]; + chunkb = b.match(chunk)[0]; + anum = !isNaN(chunka); + bnum = !isNaN(chunkb); + if (anum && !bnum) + return -1; + if (bnum && !anum) + return 1; + if (anum && bnum) { + diff = chunka - chunkb; + if (diff === 0 && chunka.length !== chunkb.length) { + if (!+chunka && !+chunkb) // chunks are strings of all 0s (special case) + return chunka.length - chunkb.length; + else + return chunkb.length - chunka.length; + } + } else if (chunka !== chunkb) + return (chunka < chunkb) ? -1 : 1; + a = a.substring(chunka.length); + b = b.substring(chunkb.length); + } + return diff; +} + +/** + * @constructor + * @extends {TreeElement} + */ +WebInspector.ObjectPropertyTreeElement = function(property) +{ + this.property = property; + + // Pass an empty title, the title gets made later in onattach. + TreeElement.call(this, "", null, false); + this.toggleOnClick = true; + this.selectable = false; +} + +WebInspector.ObjectPropertyTreeElement.prototype = { + onpopulate: function() + { + if (this.children.length && !this.shouldRefreshChildren) + return; + + var callback = function(properties) { + this.removeChildren(); + if (!properties) + return; + + properties.sort(WebInspector.ObjectPropertiesSection.CompareProperties); + for (var i = 0; i < properties.length; ++i) { + this.appendChild(new this.treeOutline.section.treeElementConstructor(properties[i])); + } + }; + this.property.value.getOwnProperties(callback.bind(this)); + }, + + ondblclick: function(event) + { + if (this.property.writable) + this.startEditing(); + }, + + onattach: function() + { + this.update(); + }, + + update: function() + { + this.nameElement = document.createElement("span"); + this.nameElement.className = "name"; + this.nameElement.textContent = this.property.name; + if (!this.property.enumerable) + this.nameElement.addStyleClass("dimmed"); + + var separatorElement = document.createElement("span"); + separatorElement.className = "separator"; + separatorElement.textContent = ": "; + + this.valueElement = document.createElement("span"); + this.valueElement.className = "value"; + + var description = this.property.value.description; + // Render \n as a nice unicode cr symbol. + if (this.property.wasThrown) + this.valueElement.textContent = "[Exception: " + description + "]"; + else if (this.property.value.type === "string" && typeof description === "string") { + this.valueElement.textContent = "\"" + description.replace(/\n/g, "\u21B5") + "\""; + this.valueElement._originalTextContent = "\"" + description + "\""; + } else if (this.property.value.type === "function" && typeof description === "string") { + this.valueElement.textContent = /.*/.exec(description)[0].replace(/ +$/g, ""); + this.valueElement._originalTextContent = description; + } else + this.valueElement.textContent = description; + + if (this.property.value.type === "function") + this.valueElement.addEventListener("contextmenu", this._functionContextMenuEventFired.bind(this), false); + + if (this.property.wasThrown) + this.valueElement.addStyleClass("error"); + if (this.property.value.subtype) + this.valueElement.addStyleClass("console-formatted-" + this.property.value.subtype); + else if (this.property.value.type) + this.valueElement.addStyleClass("console-formatted-" + this.property.value.type); + if (this.property.value.subtype === "node") + this.valueElement.addEventListener("contextmenu", this._contextMenuEventFired.bind(this), false); + + this.listItemElement.removeChildren(); + + this.listItemElement.appendChild(this.nameElement); + this.listItemElement.appendChild(separatorElement); + this.listItemElement.appendChild(this.valueElement); + this.hasChildren = this.property.value.hasChildren && !this.property.wasThrown; + }, + + _contextMenuEventFired: function(event) + { + function selectNode(nodeId) + { + if (nodeId) + WebInspector.domAgent.inspectElement(nodeId); + } + + function revealElement() + { + this.property.value.pushNodeToFrontend(selectNode); + } + + var contextMenu = new WebInspector.ContextMenu(); + contextMenu.appendItem(WebInspector.UIString("Reveal in Elements Panel"), revealElement.bind(this)); + contextMenu.show(event); + }, + + _functionContextMenuEventFired: function(event) + { + function didGetLocation(error, response) + { + if (error) { + console.error(error); + return; + } + WebInspector.panels.scripts.showFunctionDefinition(response); + } + + function revealFunction() + { + DebuggerAgent.getFunctionLocation(this.property.value.objectId, didGetLocation.bind(this)); + } + + var contextMenu = new WebInspector.ContextMenu(); + contextMenu.appendItem(WebInspector.UIString("Show function definition"), revealFunction.bind(this)); + contextMenu.show(event); + }, + + updateSiblings: function() + { + if (this.parent.root) + this.treeOutline.section.update(); + else + this.parent.shouldRefreshChildren = true; + }, + + startEditing: function() + { + if (WebInspector.isBeingEdited(this.valueElement) || !this.treeOutline.section.editable) + return; + + var context = { expanded: this.expanded }; + + // Lie about our children to prevent expanding on double click and to collapse subproperties. + this.hasChildren = false; + + this.listItemElement.addStyleClass("editing-sub-part"); + + // Edit original source. + if (typeof this.valueElement._originalTextContent === "string") + this.valueElement.textContent = this.valueElement._originalTextContent; + + var config = new WebInspector.EditingConfig(this.editingCommitted.bind(this), this.editingCancelled.bind(this), context); + WebInspector.startEditing(this.valueElement, config); + }, + + editingEnded: function(context) + { + this.listItemElement.scrollLeft = 0; + this.listItemElement.removeStyleClass("editing-sub-part"); + if (context.expanded) + this.expand(); + }, + + editingCancelled: function(element, context) + { + this.update(); + this.editingEnded(context); + }, + + editingCommitted: function(element, userInput, previousContent, context) + { + if (userInput === previousContent) + return this.editingCancelled(element, context); // nothing changed, so cancel + + this.applyExpression(userInput, true); + + this.editingEnded(context); + }, + + applyExpression: function(expression, updateInterface) + { + expression = expression.trim(); + var expressionLength = expression.length; + function callback(error) + { + if (!updateInterface) + return; + + if (error) + this.update(); + + if (!expressionLength) { + // The property was deleted, so remove this tree element. + this.parent.removeChild(this); + } else { + // Call updateSiblings since their value might be based on the value that just changed. + this.updateSiblings(); + } + }; + this.property.parentObject.setPropertyValue(this.property.name, expression.trim(), callback.bind(this)); + } +} + +WebInspector.ObjectPropertyTreeElement.prototype.__proto__ = TreeElement.prototype; +/* ObjectPopoverHelper.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.PopoverHelper} + */ +WebInspector.ObjectPopoverHelper = function(panelElement, getAnchor, queryObject, onHide, disableOnClick) +{ + WebInspector.PopoverHelper.call(this, panelElement, getAnchor, this._showObjectPopover.bind(this), onHide, disableOnClick); + this._queryObject = queryObject; + panelElement.addEventListener("scroll", this.hidePopover.bind(this), true); +}; + +WebInspector.ObjectPopoverHelper.prototype = { + _showObjectPopover: function(element, popover) + { + function showObjectPopover(result, wasThrown) + { + if (popover.disposed) + return; + if (wasThrown) { + this.hidePopover(); + return; + } + var popoverContentElement = null; + if (result.type !== "object") { + popoverContentElement = document.createElement("span"); + popoverContentElement.className = "monospace console-formatted-" + result.type; + popoverContentElement.style.whiteSpace = "pre"; + popoverContentElement.textContent = result.description; + if (result.type === "string") + popoverContentElement.textContent = "\"" + popoverContentElement.textContent + "\""; + popover.show(popoverContentElement, element); + } else { + popoverContentElement = document.createElement("div"); + + this._titleElement = document.createElement("div"); + this._titleElement.className = "source-frame-popover-title monospace"; + this._titleElement.textContent = result.description; + popoverContentElement.appendChild(this._titleElement); + + var section = new WebInspector.ObjectPropertiesSection(result); + // For HTML DOM wrappers, append "#id" to title, if not empty. + if (result.description.substr(0, 4) === "HTML") { + this._sectionUpdateProperties = section.updateProperties.bind(section); + section.updateProperties = this._updateHTMLId.bind(this); + } + section.expanded = true; + section.element.addStyleClass("source-frame-popover-tree"); + section.headerElement.addStyleClass("hidden"); + popoverContentElement.appendChild(section.element); + + const popoverWidth = 300; + const popoverHeight = 250; + popover.show(popoverContentElement, element, popoverWidth, popoverHeight); + } + } + this._queryObject(element, showObjectPopover.bind(this)); + }, + + _updateHTMLId: function(properties, rootTreeElementConstructor, rootPropertyComparer) + { + for (var i = 0; i < properties.length; ++i) { + if (properties[i].name === "id") { + if (properties[i].value.description) + this._titleElement.textContent += "#" + properties[i].value.description; + break; + } + } + this._sectionUpdateProperties(properties, rootTreeElementConstructor, rootPropertyComparer); + } +} + +WebInspector.ObjectPopoverHelper.prototype.__proto__ = WebInspector.PopoverHelper.prototype; +/* BreakpointsSidebarPane.js */ + +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.SidebarPane} + */ +WebInspector.JavaScriptBreakpointsSidebarPane = function(model, showSourceLineDelegate) +{ + WebInspector.SidebarPane.call(this, WebInspector.UIString("Breakpoints")); + + this._model = model; + this._showSourceLineDelegate = showSourceLineDelegate; + + this.listElement = document.createElement("ol"); + this.listElement.className = "breakpoint-list"; + + this.emptyElement = document.createElement("div"); + this.emptyElement.className = "info"; + this.emptyElement.textContent = WebInspector.UIString("No Breakpoints"); + + this.bodyElement.appendChild(this.emptyElement); + this.bodyElement.addEventListener("contextmenu", this._contextMenu.bind(this), false); + + this._items = {}; +} + +WebInspector.JavaScriptBreakpointsSidebarPane.prototype = { + addBreakpoint: function(breakpoint) + { + var breakpointItemId = this._createBreakpointItemId(breakpoint.uiSourceCode, breakpoint.lineNumber); + if (breakpointItemId in this._items) + return; + + var element = document.createElement("li"); + element.addStyleClass("cursor-pointer"); + element.addEventListener("contextmenu", this._breakpointContextMenu.bind(this, breakpoint), true); + element.addEventListener("click", this._breakpointClicked.bind(this, breakpoint), false); + + var checkbox = document.createElement("input"); + checkbox.className = "checkbox-elem"; + checkbox.type = "checkbox"; + checkbox.checked = breakpoint.enabled; + checkbox.addEventListener("click", this._breakpointCheckboxClicked.bind(this, breakpoint), false); + element.appendChild(checkbox); + + var url = breakpoint.uiSourceCode.url; + var displayName = url ? WebInspector.displayNameForURL(url) : WebInspector.UIString("(program)"); + var labelElement = document.createTextNode(displayName + ":" + (breakpoint.lineNumber + 1)); + element.appendChild(labelElement); + + var snippetElement = document.createElement("div"); + snippetElement.className = "source-text monospace"; + element.appendChild(snippetElement); + function didRequestContent(mimeType, content) + { + var lineEndings = content.lineEndings(); + if (breakpoint.lineNumber < lineEndings.length) + snippetElement.textContent = content.substring(lineEndings[breakpoint.lineNumber - 1], lineEndings[breakpoint.lineNumber]); + } + breakpoint.uiSourceCode.requestContent(didRequestContent.bind(this)); + + element._data = breakpoint; + var currentElement = this.listElement.firstChild; + while (currentElement) { + if (currentElement._data && this._compareBreakpoints(currentElement._data, element._data) > 0) + break; + currentElement = currentElement.nextSibling; + } + this._addListElement(element, currentElement); + + var breakpointItem = {}; + breakpointItem.element = element; + breakpointItem.checkbox = checkbox; + this._items[breakpointItemId] = breakpointItem; + + if (!this.expanded) + this.expanded = true; + }, + + removeBreakpoint: function(uiSourceCode, lineNumber) + { + var breakpointItemId = this._createBreakpointItemId(uiSourceCode, lineNumber); + var breakpointItem = this._items[breakpointItemId]; + if (!breakpointItem) + return; + delete this._items[breakpointItemId]; + this._removeListElement(breakpointItem.element); + }, + + highlightBreakpoint: function(uiSourceCode, lineNumber) + { + var breakpointItem = this._items[this._createBreakpointItemId(uiSourceCode, lineNumber)]; + if (!breakpointItem) + return; + breakpointItem.element.addStyleClass("breakpoint-hit"); + this._highlightedBreakpointItem = breakpointItem; + }, + + clearBreakpointHighlight: function() + { + if (this._highlightedBreakpointItem) { + this._highlightedBreakpointItem.element.removeStyleClass("breakpoint-hit"); + delete this._highlightedBreakpointItem; + } + }, + + _createBreakpointItemId: function(uiSourceCode, lineNumber) + { + return uiSourceCode.id + ":" + lineNumber; + }, + + _breakpointClicked: function(breakpoint, event) + { + this._showSourceLineDelegate(breakpoint.uiSourceCode, breakpoint.lineNumber); + }, + + _breakpointCheckboxClicked: function(breakpoint, event) + { + // Breakpoint element has it's own click handler. + event.stopPropagation(); + + this._model.setBreakpointEnabled(breakpoint.uiSourceCode, breakpoint.lineNumber, event.target.checked); + }, + + _breakpointContextMenu: function(breakpoint, event) + { + var contextMenu = new WebInspector.ContextMenu(); + + var removeHandler = this._model.removeBreakpoint.bind(this._model, breakpoint.uiSourceCode, breakpoint.lineNumber); + contextMenu.appendItem(WebInspector.UIString("Remove Breakpoint"), removeHandler); + var removeAllTitle = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Remove all JavaScript breakpoints" : "Remove All JavaScript Breakpoints"); + contextMenu.appendItem(removeAllTitle, this._model.removeAllBreakpoints.bind(this._model)); + + contextMenu.show(event); + }, + + _contextMenu: function(event) + { + var contextMenu = new WebInspector.ContextMenu(); + var removeAllTitle = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Remove all JavaScript breakpoints" : "Remove All JavaScript Breakpoints"); + contextMenu.appendItem(removeAllTitle, this._model.removeAllBreakpoints.bind(this._model)); + contextMenu.show(event); + }, + + _addListElement: function(element, beforeElement) + { + if (beforeElement) + this.listElement.insertBefore(element, beforeElement); + else { + if (!this.listElement.firstChild) { + this.bodyElement.removeChild(this.emptyElement); + this.bodyElement.appendChild(this.listElement); + } + this.listElement.appendChild(element); + } + }, + + _removeListElement: function(element) + { + this.listElement.removeChild(element); + if (!this.listElement.firstChild) { + this.bodyElement.removeChild(this.listElement); + this.bodyElement.appendChild(this.emptyElement); + } + }, + + _compare: function(x, y) + { + if (x !== y) + return x < y ? -1 : 1; + return 0; + }, + + _compareBreakpoints: function(b1, b2) + { + return this._compare(b1.url, b2.url) || this._compare(b1.lineNumber, b2.lineNumber); + }, + + reset: function() + { + this.listElement.removeChildren(); + if (this.listElement.parentElement) { + this.bodyElement.removeChild(this.listElement); + this.bodyElement.appendChild(this.emptyElement); + } + this._items = {}; + } +} + +WebInspector.JavaScriptBreakpointsSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype; + +/** + * @constructor + * @extends {WebInspector.SidebarPane} + */ +WebInspector.NativeBreakpointsSidebarPane = function(title) +{ + WebInspector.SidebarPane.call(this, title); + + this.listElement = document.createElement("ol"); + this.listElement.className = "breakpoint-list"; + + this.emptyElement = document.createElement("div"); + this.emptyElement.className = "info"; + this.emptyElement.textContent = WebInspector.UIString("No Breakpoints"); + + this.bodyElement.appendChild(this.emptyElement); +} + +WebInspector.NativeBreakpointsSidebarPane.prototype = { + _addListElement: function(element, beforeElement) + { + if (beforeElement) + this.listElement.insertBefore(element, beforeElement); + else { + if (!this.listElement.firstChild) { + this.bodyElement.removeChild(this.emptyElement); + this.bodyElement.appendChild(this.listElement); + } + this.listElement.appendChild(element); + } + }, + + _removeListElement: function(element) + { + this.listElement.removeChild(element); + if (!this.listElement.firstChild) { + this.bodyElement.removeChild(this.listElement); + this.bodyElement.appendChild(this.emptyElement); + } + }, + + _reset: function() + { + this.listElement.removeChildren(); + if (this.listElement.parentElement) { + this.bodyElement.removeChild(this.listElement); + this.bodyElement.appendChild(this.emptyElement); + } + } +} + +WebInspector.NativeBreakpointsSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype; + +/** + * @constructor + * @extends {WebInspector.NativeBreakpointsSidebarPane} + */ +WebInspector.XHRBreakpointsSidebarPane = function() +{ + WebInspector.NativeBreakpointsSidebarPane.call(this, WebInspector.UIString("XHR Breakpoints")); + + this._breakpointElements = {}; + + var addButton = document.createElement("button"); + addButton.className = "pane-title-button add"; + addButton.addEventListener("click", this._addButtonClicked.bind(this), false); + this.titleElement.appendChild(addButton); + + this._restoreBreakpoints(); +} + +WebInspector.XHRBreakpointsSidebarPane.prototype = { + _addButtonClicked: function(event) + { + event.stopPropagation(); + + this.expanded = true; + + var inputElementContainer = document.createElement("p"); + inputElementContainer.className = "breakpoint-condition"; + var inputElement = document.createElement("span"); + inputElementContainer.textContent = WebInspector.UIString("Break when URL contains:"); + inputElement.className = "editing"; + inputElement.id = "breakpoint-condition-input"; + inputElementContainer.appendChild(inputElement); + this._addListElement(inputElementContainer, this.listElement.firstChild); + + function finishEditing(accept, e, text) + { + this._removeListElement(inputElementContainer); + if (accept) { + this._setBreakpoint(text, true); + this._saveBreakpoints(); + } + } + + var config = new WebInspector.EditingConfig(finishEditing.bind(this, true), finishEditing.bind(this, false)); + WebInspector.startEditing(inputElement, config); + }, + + _setBreakpoint: function(url, enabled) + { + if (url in this._breakpointElements) + return; + + var element = document.createElement("li"); + element._url = url; + element.addEventListener("contextmenu", this._contextMenu.bind(this, url), true); + + var checkboxElement = document.createElement("input"); + checkboxElement.className = "checkbox-elem"; + checkboxElement.type = "checkbox"; + checkboxElement.checked = enabled; + checkboxElement.addEventListener("click", this._checkboxClicked.bind(this, url), false); + element._checkboxElement = checkboxElement; + element.appendChild(checkboxElement); + + var labelElement = document.createElement("span"); + if (!url) + labelElement.textContent = WebInspector.UIString("Any XHR"); + else + labelElement.textContent = WebInspector.UIString("URL contains \"%s\"", url); + labelElement.addStyleClass("cursor-auto"); + labelElement.addEventListener("dblclick", this._labelClicked.bind(this, url), false); + element.appendChild(labelElement); + + var currentElement = this.listElement.firstChild; + while (currentElement) { + if (currentElement._url && currentElement._url < element._url) + break; + currentElement = currentElement.nextSibling; + } + this._addListElement(element, currentElement); + this._breakpointElements[url] = element; + if (enabled) + DOMDebuggerAgent.setXHRBreakpoint(url); + }, + + _removeBreakpoint: function(url) + { + var element = this._breakpointElements[url]; + if (!element) + return; + + this._removeListElement(element); + delete this._breakpointElements[url]; + if (element._checkboxElement.checked) + DOMDebuggerAgent.removeXHRBreakpoint(url); + }, + + _contextMenu: function(url, event) + { + var contextMenu = new WebInspector.ContextMenu(); + function removeBreakpoint() + { + this._removeBreakpoint(url); + this._saveBreakpoints(); + } + contextMenu.appendItem(WebInspector.UIString("Remove Breakpoint"), removeBreakpoint.bind(this)); + contextMenu.show(event); + }, + + _checkboxClicked: function(url, event) + { + if (event.target.checked) + DOMDebuggerAgent.setXHRBreakpoint(url); + else + DOMDebuggerAgent.removeXHRBreakpoint(url); + this._saveBreakpoints(); + }, + + _labelClicked: function(url) + { + var element = this._breakpointElements[url]; + var inputElement = document.createElement("span"); + inputElement.className = "breakpoint-condition editing"; + inputElement.textContent = url; + this.listElement.insertBefore(inputElement, element); + element.addStyleClass("hidden"); + + function finishEditing(accept, e, text) + { + this._removeListElement(inputElement); + if (accept) { + this._removeBreakpoint(url); + this._setBreakpoint(text, element._checkboxElement.checked); + this._saveBreakpoints(); + } else + element.removeStyleClass("hidden"); + } + + WebInspector.startEditing(inputElement, new WebInspector.EditingConfig(finishEditing.bind(this, true), finishEditing.bind(this, false))); + }, + + highlightBreakpoint: function(url) + { + var element = this._breakpointElements[url]; + if (!element) + return; + this.expanded = true; + element.addStyleClass("breakpoint-hit"); + this._highlightedElement = element; + }, + + clearBreakpointHighlight: function() + { + if (this._highlightedElement) { + this._highlightedElement.removeStyleClass("breakpoint-hit"); + delete this._highlightedElement; + } + }, + + _saveBreakpoints: function() + { + var breakpoints = []; + for (var url in this._breakpointElements) + breakpoints.push({ url: url, enabled: this._breakpointElements[url]._checkboxElement.checked }); + WebInspector.settings.xhrBreakpoints.set(breakpoints); + }, + + _restoreBreakpoints: function() + { + var breakpoints = WebInspector.settings.xhrBreakpoints.get(); + for (var i = 0; i < breakpoints.length; ++i) { + var breakpoint = breakpoints[i]; + if (breakpoint && typeof breakpoint.url === "string") + this._setBreakpoint(breakpoint.url, breakpoint.enabled); + } + } +} + +WebInspector.XHRBreakpointsSidebarPane.prototype.__proto__ = WebInspector.NativeBreakpointsSidebarPane.prototype; + +/** + * @constructor + * @extends {WebInspector.SidebarPane} + */ +WebInspector.EventListenerBreakpointsSidebarPane = function() +{ + WebInspector.SidebarPane.call(this, WebInspector.UIString("Event Listener Breakpoints")); + + this.categoriesElement = document.createElement("ol"); + this.categoriesElement.tabIndex = 0; + this.categoriesElement.addStyleClass("properties-tree"); + this.categoriesElement.addStyleClass("event-listener-breakpoints"); + this.categoriesTreeOutline = new TreeOutline(this.categoriesElement); + this.bodyElement.appendChild(this.categoriesElement); + + this._breakpointItems = {}; + this._createCategory(WebInspector.UIString("Keyboard"), "listener", ["keydown", "keyup", "keypress", "textInput"]); + this._createCategory(WebInspector.UIString("Mouse"), "listener", ["click", "dblclick", "mousedown", "mouseup", "mouseover", "mousemove", "mouseout", "mousewheel"]); + // FIXME: uncomment following once inspector stops being drop targer in major ports. + // Otherwise, inspector page reacts on drop event and tries to load the event data. + // this._createCategory(WebInspector.UIString("Drag"), "listener", ["drag", "drop", "dragstart", "dragend", "dragenter", "dragleave", "dragover"]); + this._createCategory(WebInspector.UIString("Control"), "listener", ["resize", "scroll", "zoom", "focus", "blur", "select", "change", "submit", "reset"]); + this._createCategory(WebInspector.UIString("Clipboard"), "listener", ["copy", "cut", "paste", "beforecopy", "beforecut", "beforepaste"]); + this._createCategory(WebInspector.UIString("Load"), "listener", ["load", "unload", "abort", "error"]); + this._createCategory(WebInspector.UIString("DOM Mutation"), "listener", ["DOMActivate", "DOMFocusIn", "DOMFocusOut", "DOMAttrModified", "DOMCharacterDataModified", "DOMNodeInserted", "DOMNodeInsertedIntoDocument", "DOMNodeRemoved", "DOMNodeRemovedFromDocument", "DOMSubtreeModified", "DOMContentLoaded"]); + this._createCategory(WebInspector.UIString("Device"), "listener", ["deviceorientation", "devicemotion"]); + this._createCategory(WebInspector.UIString("Timer"), "instrumentation", ["setTimer", "clearTimer", "timerFired"]); + this._createCategory(WebInspector.UIString("Touch"), "instrumentation", ["touchstart", "touchmove", "touchend", "touchcancel"]); + + this._restoreBreakpoints(); +} + +WebInspector.EventListenerBreakpointsSidebarPane.eventNameForUI = function(eventName) +{ + if (!WebInspector.EventListenerBreakpointsSidebarPane._eventNamesForUI) { + WebInspector.EventListenerBreakpointsSidebarPane._eventNamesForUI = { + "instrumentation:setTimer": WebInspector.UIString("Set Timer"), + "instrumentation:clearTimer": WebInspector.UIString("Clear Timer"), + "instrumentation:timerFired": WebInspector.UIString("Timer Fired") + }; + } + return WebInspector.EventListenerBreakpointsSidebarPane._eventNamesForUI[eventName] || eventName.substring(eventName.indexOf(":") + 1); +} + +WebInspector.EventListenerBreakpointsSidebarPane.prototype = { + _createCategory: function(name, type, eventNames) + { + var categoryItem = {}; + categoryItem.element = new TreeElement(name); + this.categoriesTreeOutline.appendChild(categoryItem.element); + categoryItem.element.listItemElement.addStyleClass("event-category"); + categoryItem.element.selectable = true; + + categoryItem.checkbox = this._createCheckbox(categoryItem.element); + categoryItem.checkbox.addEventListener("click", this._categoryCheckboxClicked.bind(this, categoryItem), true); + + categoryItem.children = {}; + for (var i = 0; i < eventNames.length; ++i) { + var eventName = type + ":" + eventNames[i]; + + var breakpointItem = {}; + var title = WebInspector.EventListenerBreakpointsSidebarPane.eventNameForUI(eventName); + breakpointItem.element = new TreeElement(title); + categoryItem.element.appendChild(breakpointItem.element); + var hitMarker = document.createElement("div"); + hitMarker.className = "breakpoint-hit-marker"; + breakpointItem.element.listItemElement.appendChild(hitMarker); + breakpointItem.element.listItemElement.addStyleClass("source-code"); + breakpointItem.element.selectable = true; + + breakpointItem.checkbox = this._createCheckbox(breakpointItem.element); + breakpointItem.checkbox.addEventListener("click", this._breakpointCheckboxClicked.bind(this, eventName), true); + breakpointItem.parent = categoryItem; + + this._breakpointItems[eventName] = breakpointItem; + categoryItem.children[eventName] = breakpointItem; + } + }, + + _createCheckbox: function(treeElement) + { + var checkbox = document.createElement("input"); + checkbox.className = "checkbox-elem"; + checkbox.type = "checkbox"; + treeElement.listItemElement.insertBefore(checkbox, treeElement.listItemElement.firstChild); + return checkbox; + }, + + _categoryCheckboxClicked: function(categoryItem) + { + var checked = categoryItem.checkbox.checked; + for (var eventName in categoryItem.children) { + var breakpointItem = categoryItem.children[eventName]; + if (breakpointItem.checkbox.checked === checked) + continue; + if (checked) + this._setBreakpoint(eventName); + else + this._removeBreakpoint(eventName); + } + this._saveBreakpoints(); + }, + + _breakpointCheckboxClicked: function(eventName, event) + { + if (event.target.checked) + this._setBreakpoint(eventName); + else + this._removeBreakpoint(eventName); + this._saveBreakpoints(); + }, + + _setBreakpoint: function(eventName) + { + var breakpointItem = this._breakpointItems[eventName]; + if (!breakpointItem) + return; + breakpointItem.checkbox.checked = true; + DOMDebuggerAgent.setEventListenerBreakpoint(eventName); + this._updateCategoryCheckbox(breakpointItem.parent); + }, + + _removeBreakpoint: function(eventName) + { + var breakpointItem = this._breakpointItems[eventName]; + if (!breakpointItem) + return; + breakpointItem.checkbox.checked = false; + DOMDebuggerAgent.removeEventListenerBreakpoint(eventName); + this._updateCategoryCheckbox(breakpointItem.parent); + }, + + _updateCategoryCheckbox: function(categoryItem) + { + var hasEnabled = false, hasDisabled = false; + for (var eventName in categoryItem.children) { + var breakpointItem = categoryItem.children[eventName]; + if (breakpointItem.checkbox.checked) + hasEnabled = true; + else + hasDisabled = true; + } + categoryItem.checkbox.checked = hasEnabled; + categoryItem.checkbox.indeterminate = hasEnabled && hasDisabled; + }, + + highlightBreakpoint: function(eventName) + { + var breakpointItem = this._breakpointItems[eventName]; + if (!breakpointItem) + return; + this.expanded = true; + breakpointItem.parent.element.expand(); + breakpointItem.element.listItemElement.addStyleClass("breakpoint-hit"); + this._highlightedElement = breakpointItem.element.listItemElement; + }, + + clearBreakpointHighlight: function() + { + if (this._highlightedElement) { + this._highlightedElement.removeStyleClass("breakpoint-hit"); + delete this._highlightedElement; + } + }, + + _saveBreakpoints: function() + { + var breakpoints = []; + for (var eventName in this._breakpointItems) { + if (this._breakpointItems[eventName].checkbox.checked) + breakpoints.push({ eventName: eventName }); + } + WebInspector.settings.eventListenerBreakpoints.set(breakpoints); + }, + + _restoreBreakpoints: function() + { + var breakpoints = WebInspector.settings.eventListenerBreakpoints.get(); + for (var i = 0; i < breakpoints.length; ++i) { + var breakpoint = breakpoints[i]; + if (breakpoint && typeof breakpoint.eventName === "string") + this._setBreakpoint(breakpoint.eventName); + } + } +} + +WebInspector.EventListenerBreakpointsSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype; +/* DOMBreakpointsSidebarPane.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.NativeBreakpointsSidebarPane} + */ +WebInspector.DOMBreakpointsSidebarPane = function() +{ + WebInspector.NativeBreakpointsSidebarPane.call(this, WebInspector.UIString("DOM Breakpoints")); + + this._breakpointElements = {}; + + this._breakpointTypes = { + SubtreeModified: "subtree-modified", + AttributeModified: "attribute-modified", + NodeRemoved: "node-removed" + }; + this._breakpointTypeLabels = {}; + this._breakpointTypeLabels[this._breakpointTypes.SubtreeModified] = WebInspector.UIString("Subtree Modified"); + this._breakpointTypeLabels[this._breakpointTypes.AttributeModified] = WebInspector.UIString("Attribute Modified"); + this._breakpointTypeLabels[this._breakpointTypes.NodeRemoved] = WebInspector.UIString("Node Removed"); + + this._contextMenuLabels = {}; + this._contextMenuLabels[this._breakpointTypes.SubtreeModified] = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Break on subtree modifications" : "Break on Subtree Modifications"); + this._contextMenuLabels[this._breakpointTypes.AttributeModified] = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Break on attributes modifications" : "Break on Attributes Modifications"); + this._contextMenuLabels[this._breakpointTypes.NodeRemoved] = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Break on node removal" : "Break on Node Removal"); + + WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.InspectedURLChanged, this._inspectedURLChanged, this); + WebInspector.domAgent.addEventListener(WebInspector.DOMAgent.Events.NodeRemoved, this._nodeRemoved, this); +} + +WebInspector.DOMBreakpointsSidebarPane.prototype = { + _inspectedURLChanged: function(event) + { + this._breakpointElements = {}; + this._reset(); + var url = event.data; + this._inspectedURL = url.removeURLFragment(); + }, + + populateNodeContextMenu: function(node, contextMenu) + { + var nodeBreakpoints = {}; + for (var id in this._breakpointElements) { + var element = this._breakpointElements[id]; + if (element._node === node) + nodeBreakpoints[element._type] = true; + } + + function toggleBreakpoint(type) + { + if (!nodeBreakpoints[type]) + this._setBreakpoint(node, type, true); + else + this._removeBreakpoint(node, type); + this._saveBreakpoints(); + } + + for (var key in this._breakpointTypes) { + var type = this._breakpointTypes[key]; + var label = this._contextMenuLabels[type]; + contextMenu.appendCheckboxItem(label, toggleBreakpoint.bind(this, type), nodeBreakpoints[type]); + } + }, + + createBreakpointHitStatusMessage: function(auxData, callback) + { + if (auxData.type === this._breakpointTypes.SubtreeModified) { + var targetNodeObject = WebInspector.RemoteObject.fromPayload(auxData["targetNode"]); + function didPushNodeToFrontend(targetNodeId) + { + if (targetNodeId) + targetNodeObject.release(); + this._doCreateBreakpointHitStatusMessage(auxData, targetNodeId, callback); + } + targetNodeObject.pushNodeToFrontend(didPushNodeToFrontend.bind(this)); + } else + this._doCreateBreakpointHitStatusMessage(auxData, null, callback); + }, + + _doCreateBreakpointHitStatusMessage: function (auxData, targetNodeId, callback) + { + var message; + var typeLabel = this._breakpointTypeLabels[auxData.type]; + var linkifiedNode = WebInspector.DOMPresentationUtils.linkifyNodeById(auxData.nodeId); + var substitutions = [typeLabel, linkifiedNode]; + var targetNode = ""; + if (targetNodeId) + targetNode = WebInspector.DOMPresentationUtils.linkifyNodeById(targetNodeId); + + if (auxData.type === this._breakpointTypes.SubtreeModified) { + if (auxData.insertion) { + if (targetNodeId !== auxData.nodeId) { + message = "Paused on a \"%s\" breakpoint set on %s, because a new child was added to its descendant %s."; + substitutions.push(targetNode); + } else + message = "Paused on a \"%s\" breakpoint set on %s, because a new child was added to that node."; + } else { + message = "Paused on a \"%s\" breakpoint set on %s, because its descendant %s was removed."; + substitutions.push(targetNode); + } + } else + message = "Paused on a \"%s\" breakpoint set on %s."; + + var element = document.createElement("span"); + var formatters = { + s: function(substitution) + { + return substitution; + } + }; + function append(a, b) + { + if (typeof b === "string") + b = document.createTextNode(b); + element.appendChild(b); + } + WebInspector.formatLocalized(message, substitutions, formatters, "", append); + + callback(element); + }, + + _nodeRemoved: function(event) + { + var node = event.data.node; + this._removeBreakpointsForNode(event.data.node); + if (!node.children) + return; + for (var i = 0; i < node.children.length; ++i) + this._removeBreakpointsForNode(node.children[i]); + this._saveBreakpoints(); + }, + + _removeBreakpointsForNode: function(node) + { + for (var id in this._breakpointElements) { + var element = this._breakpointElements[id]; + if (element._node === node) + this._removeBreakpoint(element._node, element._type); + } + }, + + _setBreakpoint: function(node, type, enabled) + { + var breakpointId = this._createBreakpointId(node.id, type); + if (breakpointId in this._breakpointElements) + return; + + var element = document.createElement("li"); + element._node = node; + element._type = type; + element.addEventListener("contextmenu", this._contextMenu.bind(this, node, type), true); + + var checkboxElement = document.createElement("input"); + checkboxElement.className = "checkbox-elem"; + checkboxElement.type = "checkbox"; + checkboxElement.checked = enabled; + checkboxElement.addEventListener("click", this._checkboxClicked.bind(this, node, type), false); + element._checkboxElement = checkboxElement; + element.appendChild(checkboxElement); + + var labelElement = document.createElement("span"); + element.appendChild(labelElement); + + var linkifiedNode = WebInspector.DOMPresentationUtils.linkifyNodeById(node.id); + linkifiedNode.addStyleClass("monospace"); + labelElement.appendChild(linkifiedNode); + + var description = document.createElement("div"); + description.className = "source-text"; + description.textContent = this._breakpointTypeLabels[type]; + labelElement.appendChild(description); + + var currentElement = this.listElement.firstChild; + while (currentElement) { + if (currentElement._type && currentElement._type < element._type) + break; + currentElement = currentElement.nextSibling; + } + this._addListElement(element, currentElement); + this._breakpointElements[breakpointId] = element; + if (enabled) + DOMDebuggerAgent.setDOMBreakpoint(node.id, type); + }, + + _removeBreakpoint: function(node, type) + { + var breakpointId = this._createBreakpointId(node.id, type); + var element = this._breakpointElements[breakpointId]; + if (!element) + return; + + this._removeListElement(element); + delete this._breakpointElements[breakpointId]; + if (element._checkboxElement.checked) + DOMDebuggerAgent.removeDOMBreakpoint(node.id, type); + }, + + _contextMenu: function(node, type, event) + { + var contextMenu = new WebInspector.ContextMenu(); + function removeBreakpoint() + { + this._removeBreakpoint(node, type); + this._saveBreakpoints(); + } + contextMenu.appendItem(WebInspector.UIString("Remove Breakpoint"), removeBreakpoint.bind(this)); + contextMenu.show(event); + }, + + _checkboxClicked: function(node, type, event) + { + if (event.target.checked) + DOMDebuggerAgent.setDOMBreakpoint(node.id, type); + else + DOMDebuggerAgent.removeDOMBreakpoint(node.id, type); + this._saveBreakpoints(); + }, + + highlightBreakpoint: function(auxData) + { + var breakpointId = this._createBreakpointId(auxData.nodeId, auxData.type); + var element = this._breakpointElements[breakpointId]; + if (!element) + return; + this.expanded = true; + element.addStyleClass("breakpoint-hit"); + this._highlightedElement = element; + }, + + clearBreakpointHighlight: function() + { + if (this._highlightedElement) { + this._highlightedElement.removeStyleClass("breakpoint-hit"); + delete this._highlightedElement; + } + }, + + _createBreakpointId: function(nodeId, type) + { + return nodeId + ":" + type; + }, + + _saveBreakpoints: function() + { + var breakpoints = []; + var storedBreakpoints = WebInspector.settings.domBreakpoints.get(); + for (var i = 0; i < storedBreakpoints.length; ++i) { + var breakpoint = storedBreakpoints[i]; + if (breakpoint.url !== this._inspectedURL) + breakpoints.push(breakpoint); + } + for (var id in this._breakpointElements) { + var element = this._breakpointElements[id]; + breakpoints.push({ url: this._inspectedURL, path: element._node.path(), type: element._type, enabled: element._checkboxElement.checked }); + } + WebInspector.settings.domBreakpoints.set(breakpoints); + }, + + restoreBreakpoints: function() + { + var pathToBreakpoints = {}; + + function didPushNodeByPathToFrontend(path, nodeId) + { + var node = WebInspector.domAgent.nodeForId(nodeId); + if (!node) + return; + + var breakpoints = pathToBreakpoints[path]; + for (var i = 0; i < breakpoints.length; ++i) + this._setBreakpoint(node, breakpoints[i].type, breakpoints[i].enabled); + } + + var breakpoints = WebInspector.settings.domBreakpoints.get(); + for (var i = 0; i < breakpoints.length; ++i) { + var breakpoint = breakpoints[i]; + if (breakpoint.url !== this._inspectedURL) + continue; + var path = breakpoint.path; + if (!pathToBreakpoints[path]) { + pathToBreakpoints[path] = []; + WebInspector.domAgent.pushNodeByPathToFrontend(path, didPushNodeByPathToFrontend.bind(this, path)); + } + pathToBreakpoints[path].push(breakpoint); + } + } +} + +WebInspector.DOMBreakpointsSidebarPane.prototype.__proto__ = WebInspector.NativeBreakpointsSidebarPane.prototype; +/* CallStackSidebarPane.js */ + +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.SidebarPane} + */ +WebInspector.CallStackSidebarPane = function(model) +{ + WebInspector.SidebarPane.call(this, WebInspector.UIString("Call Stack")); + this._model = model; + + this.bodyElement.addEventListener("contextmenu", this._contextMenu.bind(this), true); +} + +WebInspector.CallStackSidebarPane.prototype = { + update: function(callFrames) + { + this.bodyElement.removeChildren(); + + if (this.placards) { + for (var i = 0; i < this.placards.length; ++i) + this.placards[i].discard(); + } + this.placards = []; + + if (!callFrames) { + var infoElement = document.createElement("div"); + infoElement.className = "info"; + infoElement.textContent = WebInspector.UIString("Not Paused"); + this.bodyElement.appendChild(infoElement); + return; + } + + for (var i = 0; i < callFrames.length; ++i) { + var callFrame = callFrames[i]; + var placard = this._model.createPlacard(callFrame); + placard.callFrame = callFrame; + placard.element.addEventListener("click", this._placardSelected.bind(this, placard), false); + this.placards.push(placard); + this.bodyElement.appendChild(placard.element); + } + }, + + set selectedCallFrame(x) + { + for (var i = 0; i < this.placards.length; ++i) { + var placard = this.placards[i]; + placard.selected = (placard.callFrame === x); + } + }, + + _selectNextCallFrameOnStack: function() + { + var index = this._selectedCallFrameIndex(); + if (index == -1) + return; + this._selectedPlacardByIndex(index + 1); + }, + + _selectPreviousCallFrameOnStack: function() + { + var index = this._selectedCallFrameIndex(); + if (index == -1) + return; + this._selectedPlacardByIndex(index - 1); + }, + + _selectedPlacardByIndex: function(index) + { + if (index < 0 || index >= this.placards.length) + return; + this._placardSelected(this.placards[index]) + }, + + _selectedCallFrameIndex: function() + { + if (!this._model.selectedCallFrame) + return -1; + for (var i = 0; i < this.placards.length; ++i) { + var placard = this.placards[i]; + if (placard.callFrame === this._model.selectedCallFrame) + return i; + } + return -1; + }, + + _placardSelected: function(placard) + { + this._model.selectedCallFrame = placard.callFrame; + }, + + _contextMenu: function(event) + { + if (!this.placards.length) + return; + + var contextMenu = new WebInspector.ContextMenu(); + contextMenu.appendItem(WebInspector.UIString("Copy Stack Trace"), this._copyStackTrace.bind(this)); + contextMenu.show(event); + }, + + _copyStackTrace: function() + { + var text = ""; + for (var i = 0; i < this.placards.length; ++i) + text += this.placards[i]._text + "\n"; + InspectorFrontendHost.copyText(text); + }, + + registerShortcuts: function(section, registerShortcutDelegate) + { + var nextCallFrame = WebInspector.KeyboardShortcut.makeDescriptor(WebInspector.KeyboardShortcut.Keys.Period, + WebInspector.KeyboardShortcut.Modifiers.Ctrl); + registerShortcutDelegate(nextCallFrame.key, this._selectNextCallFrameOnStack.bind(this)); + + var prevCallFrame = WebInspector.KeyboardShortcut.makeDescriptor(WebInspector.KeyboardShortcut.Keys.Comma, + WebInspector.KeyboardShortcut.Modifiers.Ctrl); + registerShortcutDelegate(prevCallFrame.key, this._selectPreviousCallFrameOnStack.bind(this)); + + section.addRelatedKeys([ nextCallFrame.name, prevCallFrame.name ], WebInspector.UIString("Next/previous call frame")); + }, + + setStatus: function(status) + { + var statusMessageElement = document.createElement("div"); + statusMessageElement.className = "info"; + if (typeof status === "string") + statusMessageElement.textContent = status; + else + statusMessageElement.appendChild(status); + this.bodyElement.appendChild(statusMessageElement); + } +} + +WebInspector.CallStackSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype; +/* ScopeChainSidebarPane.js */ + +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.SidebarPane} + */ +WebInspector.ScopeChainSidebarPane = function() +{ + WebInspector.SidebarPane.call(this, WebInspector.UIString("Scope Variables")); + this._sections = []; + this._expandedSections = {}; + this._expandedProperties = []; +} + +WebInspector.ScopeChainSidebarPane.prototype = { + update: function(callFrame) + { + this.bodyElement.removeChildren(); + + if (!callFrame) { + var infoElement = document.createElement("div"); + infoElement.className = "info"; + infoElement.textContent = WebInspector.UIString("Not Paused"); + this.bodyElement.appendChild(infoElement); + return; + } + + for (var i = 0; i < this._sections.length; ++i) { + var section = this._sections[i]; + if (!section.title) + continue; + if (section.expanded) + this._expandedSections[section.title] = true; + else + delete this._expandedSections[section.title]; + } + + this._sections = []; + + var foundLocalScope = false; + var scopeChain = callFrame.scopeChain; + for (var i = 0; i < scopeChain.length; ++i) { + var scope = scopeChain[i]; + var title = null; + var subtitle = scope.object.description; + var emptyPlaceholder = null; + var extraProperties = null; + + switch (scope.type) { + case "local": + foundLocalScope = true; + title = WebInspector.UIString("Local"); + emptyPlaceholder = WebInspector.UIString("No Variables"); + subtitle = null; + if (callFrame.this) + extraProperties = [ new WebInspector.RemoteObjectProperty("this", WebInspector.RemoteObject.fromPayload(callFrame.this)) ]; + if (i == 0) { + var details = WebInspector.debuggerModel.debuggerPausedDetails; + var exception = details.reason === WebInspector.DebuggerModel.BreakReason.Exception ? details.auxData : 0; + if (exception) { + extraProperties = extraProperties || []; + var exceptionObject = /** @type {RuntimeAgent.RemoteObject} */ exception; + extraProperties.push(new WebInspector.RemoteObjectProperty("", WebInspector.RemoteObject.fromPayload(exceptionObject))); + } + } + break; + case "closure": + title = WebInspector.UIString("Closure"); + emptyPlaceholder = WebInspector.UIString("No Variables"); + subtitle = null; + break; + case "catch": + title = WebInspector.UIString("Catch"); + break; + case "with": + title = WebInspector.UIString("With Block"); + break; + case "global": + title = WebInspector.UIString("Global"); + break; + } + + if (!title || title === subtitle) + subtitle = null; + + var section = new WebInspector.ObjectPropertiesSection(WebInspector.RemoteObject.fromPayload(scope.object), title, subtitle, emptyPlaceholder, true, extraProperties, WebInspector.ScopeVariableTreeElement); + section.editInSelectedCallFrameWhenPaused = true; + section.pane = this; + + if (!foundLocalScope || scope.type === "local" || title in this._expandedSections) + section.expanded = true; + + this._sections.push(section); + this.bodyElement.appendChild(section.element); + } + } +} + +WebInspector.ScopeChainSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype; + +/** + * @constructor + * @extends {WebInspector.ObjectPropertyTreeElement} + */ +WebInspector.ScopeVariableTreeElement = function(property) +{ + WebInspector.ObjectPropertyTreeElement.call(this, property); +} + +WebInspector.ScopeVariableTreeElement.prototype = { + onattach: function() + { + WebInspector.ObjectPropertyTreeElement.prototype.onattach.call(this); + if (this.hasChildren && this.propertyIdentifier in this.treeOutline.section.pane._expandedProperties) + this.expand(); + }, + + onexpand: function() + { + this.treeOutline.section.pane._expandedProperties[this.propertyIdentifier] = true; + }, + + oncollapse: function() + { + delete this.treeOutline.section.pane._expandedProperties[this.propertyIdentifier]; + }, + + get propertyIdentifier() + { + if ("_propertyIdentifier" in this) + return this._propertyIdentifier; + var section = this.treeOutline.section; + this._propertyIdentifier = section.title + ":" + (section.subtitle ? section.subtitle + ":" : "") + this.propertyPath; + return this._propertyIdentifier; + }, + + get propertyPath() + { + if ("_propertyPath" in this) + return this._propertyPath; + + var current = this; + var result; + + do { + if (result) + result = current.property.name + "." + result; + else + result = current.property.name; + current = current.parent; + } while (current && !current.root); + + this._propertyPath = result; + return result; + } +} + +WebInspector.ScopeVariableTreeElement.prototype.__proto__ = WebInspector.ObjectPropertyTreeElement.prototype; +/* WatchExpressionsSidebarPane.js */ + +/* + * Copyright (C) IBM Corp. 2009 All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of IBM Corp. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.SidebarPane} + */ +WebInspector.WatchExpressionsSidebarPane = function() +{ + WebInspector.SidebarPane.call(this, WebInspector.UIString("Watch Expressions")); +} + +WebInspector.WatchExpressionsSidebarPane.prototype = { + show: function() + { + this._visible = true; + + // Expand and update watches first time they are shown. + if (this._wasShown) { + this._refreshExpressionsIfNeeded(); + return; + } + + this._wasShown = true; + + this.section = new WebInspector.WatchExpressionsSection(); + this.bodyElement.appendChild(this.section.element); + + var refreshButton = document.createElement("button"); + refreshButton.className = "pane-title-button refresh"; + refreshButton.addEventListener("click", this._refreshButtonClicked.bind(this), false); + this.titleElement.appendChild(refreshButton); + + var addButton = document.createElement("button"); + addButton.className = "pane-title-button add"; + addButton.addEventListener("click", this._addButtonClicked.bind(this), false); + this.titleElement.appendChild(addButton); + this._requiresUpdate = true; + + if (WebInspector.settings.watchExpressions.get().length > 0) + this.expanded = true; + }, + + hide: function() + { + this._visible = false; + }, + + reset: function() + { + this.refreshExpressions(); + }, + + refreshExpressions: function() + { + this._requiresUpdate = true; + this._refreshExpressionsIfNeeded(); + }, + + addExpression: function(expression) + { + this.section.addExpression(expression); + this.expanded = true; + }, + + _refreshExpressionsIfNeeded: function() + { + if (this._requiresUpdate && this._visible) { + this.section.update(); + delete this._requiresUpdate; + } else + this._requiresUpdate = true; + }, + + _addButtonClicked: function(event) + { + event.stopPropagation(); + this.expanded = true; + this.section.addNewExpressionAndEdit(); + }, + + _refreshButtonClicked: function(event) + { + event.stopPropagation(); + this.refreshExpressions(); + } +} + +WebInspector.WatchExpressionsSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype; + +/** + * @constructor + * @extends {WebInspector.ObjectPropertiesSection} + */ +WebInspector.WatchExpressionsSection = function() +{ + this._watchObjectGroupId = "watch-group"; + + WebInspector.ObjectPropertiesSection.call(this); + + this.emptyElement = document.createElement("div"); + this.emptyElement.className = "info"; + this.emptyElement.textContent = WebInspector.UIString("No Watch Expressions"); + + this.watchExpressions = WebInspector.settings.watchExpressions.get(); + + this.headerElement.className = "hidden"; + this.editable = true; + this.expanded = true; + this.propertiesElement.addStyleClass("watch-expressions"); + + this.element.addEventListener("mousemove", this._mouseMove.bind(this), true); + this.element.addEventListener("mouseout", this._mouseOut.bind(this), true); +} + +WebInspector.WatchExpressionsSection.NewWatchExpression = "\xA0"; + +WebInspector.WatchExpressionsSection.prototype = { + update: function(e) + { + if (e) + e.stopPropagation(); + + function appendResult(expression, watchIndex, result, wasThrown) + { + if (!result) + return; + + var property = new WebInspector.RemoteObjectProperty(expression, result); + property.watchIndex = watchIndex; + property.wasThrown = wasThrown; + + // To clarify what's going on here: + // In the outer function, we calculate the number of properties + // that we're going to be updating, and set that in the + // propertyCount variable. + // In this function, we test to see when we are processing the + // last property, and then call the superclass's updateProperties() + // method to get all the properties refreshed at once. + properties.push(property); + + if (properties.length == propertyCount) { + this.updateProperties(properties, WebInspector.WatchExpressionTreeElement, WebInspector.WatchExpressionsSection.CompareProperties); + + // check to see if we just added a new watch expression, + // which will always be the last property + if (this._newExpressionAdded) { + delete this._newExpressionAdded; + + var treeElement = this.findAddedTreeElement(); + if (treeElement) + treeElement.startEditing(); + } + + // Force displaying delete button for hovered element. + if (this._lastMouseMovePageY) + this._updateHoveredElement(this._lastMouseMovePageY); + } + } + + // TODO: pass exact injected script id. + RuntimeAgent.releaseObjectGroup(this._watchObjectGroupId) + var properties = []; + + // Count the properties, so we known when to call this.updateProperties() + // in appendResult() + var propertyCount = 0; + for (var i = 0; i < this.watchExpressions.length; ++i) { + if (!this.watchExpressions[i]) + continue; + ++propertyCount; + } + + // Now process all the expressions, since we have the actual count, + // which is checked in the appendResult inner function. + for (var i = 0; i < this.watchExpressions.length; ++i) { + var expression = this.watchExpressions[i]; + if (!expression) + continue; + + WebInspector.consoleView.evalInInspectedWindow(expression, this._watchObjectGroupId, false, true, false, appendResult.bind(this, expression, i)); + } + + if (!propertyCount) { + if (!this.emptyElement.parentNode) + this.element.appendChild(this.emptyElement); + } else { + if (this.emptyElement.parentNode) + this.element.removeChild(this.emptyElement); + } + + // note this is setting the expansion of the tree, not the section; + // with no expressions, and expanded tree, we get some extra vertical + // white space + this.expanded = (propertyCount != 0); + }, + + addExpression: function(expression) + { + this.watchExpressions.push(expression); + this.saveExpressions(); + this.update(); + }, + + addNewExpressionAndEdit: function() + { + this._newExpressionAdded = true; + this.watchExpressions.push(WebInspector.WatchExpressionsSection.NewWatchExpression); + this.update(); + }, + + updateExpression: function(element, value) + { + this.watchExpressions[element.property.watchIndex] = value; + this.saveExpressions(); + this.update(); + }, + + findAddedTreeElement: function() + { + var children = this.propertiesTreeOutline.children; + for (var i = 0; i < children.length; ++i) + if (children[i].property.name === WebInspector.WatchExpressionsSection.NewWatchExpression) + return children[i]; + }, + + saveExpressions: function() + { + var toSave = []; + for (var i = 0; i < this.watchExpressions.length; i++) + if (this.watchExpressions[i]) + toSave.push(this.watchExpressions[i]); + + WebInspector.settings.watchExpressions.set(toSave); + return toSave.length; + }, + + _mouseMove: function(e) + { + if (this.propertiesElement.firstChild) + this._updateHoveredElement(e.pageY); + }, + + _mouseOut: function() + { + if (this._hoveredElement) + this._hoveredElement.removeStyleClass("hovered"); + delete this._lastMouseMovePageY; + }, + + _updateHoveredElement: function(pageY) + { + if (this._hoveredElement) + this._hoveredElement.removeStyleClass("hovered"); + + this._hoveredElement = this.propertiesElement.firstChild; + while (true) { + var next = this._hoveredElement.nextSibling; + while(next && !next.clientHeight) + next = next.nextSibling; + if (!next || next.totalOffsetTop() > pageY) + break; + this._hoveredElement = next; + } + this._hoveredElement.addStyleClass("hovered"); + + this._lastMouseMovePageY = pageY; + } +} + +WebInspector.WatchExpressionsSection.prototype.__proto__ = WebInspector.ObjectPropertiesSection.prototype; + +WebInspector.WatchExpressionsSection.CompareProperties = function(propertyA, propertyB) +{ + if (propertyA.watchIndex == propertyB.watchIndex) + return 0; + else if (propertyA.watchIndex < propertyB.watchIndex) + return -1; + else + return 1; +} + +/** + * @constructor + * @extends {WebInspector.ObjectPropertyTreeElement} + */ +WebInspector.WatchExpressionTreeElement = function(property) +{ + WebInspector.ObjectPropertyTreeElement.call(this, property); +} + +WebInspector.WatchExpressionTreeElement.prototype = { + update: function() + { + WebInspector.ObjectPropertyTreeElement.prototype.update.call(this); + + if (this.property.wasThrown) + this.valueElement.addStyleClass("watch-expressions-error-level"); + + var deleteButton = document.createElement("input"); + deleteButton.type = "button"; + deleteButton.title = WebInspector.UIString("Delete watch expression."); + deleteButton.addStyleClass("enabled-button"); + deleteButton.addStyleClass("delete-button"); + deleteButton.addEventListener("click", this._deleteButtonClicked.bind(this), false); + this.listItemElement.insertBefore(deleteButton, this.listItemElement.firstChild); + }, + + _deleteButtonClicked: function() + { + this.treeOutline.section.updateExpression(this, null); + }, + + startEditing: function() + { + if (WebInspector.isBeingEdited(this.nameElement) || !this.treeOutline.section.editable) + return; + + this.nameElement.textContent = this.property.name.trim(); + + var context = { expanded: this.expanded }; + + // collapse temporarily, if required + this.hasChildren = false; + + this.listItemElement.addStyleClass("editing-sub-part"); + + WebInspector.startEditing(this.nameElement, new WebInspector.EditingConfig(this.editingCommitted.bind(this), this.editingCancelled.bind(this), context)); + }, + + editingCancelled: function(element, context) + { + if (!this.nameElement.textContent) + this.treeOutline.section.updateExpression(this, null); + + this.update(); + this.editingEnded(context); + }, + + applyExpression: function(expression, updateInterface) + { + expression = expression.trim(); + + if (!expression) + expression = null; + + this.property.name = expression; + this.treeOutline.section.updateExpression(this, expression); + } +} + +WebInspector.WatchExpressionTreeElement.prototype.__proto__ = WebInspector.ObjectPropertyTreeElement.prototype; +/* WorkersSidebarPane.js */ + +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.SidebarPane} + */ +WebInspector.WorkersSidebarPane = function() +{ + WebInspector.SidebarPane.call(this, WebInspector.UIString("Workers")); + + this._workers = {}; + + this._enableWorkersCheckbox = new WebInspector.Checkbox( + WebInspector.UIString("Debug"), + "sidebar-pane-subtitle", + WebInspector.UIString("Allow debugging workers. Enabling this option will replace native workers with the iframe-based JavaScript implementation")); + this.titleElement.insertBefore(this._enableWorkersCheckbox.element, this.titleElement.firstChild); + + this._enableWorkersCheckbox.addEventListener(this._onTriggerInstrument.bind(this)); + this._enableWorkersCheckbox.checked = false; + + this._listElement = document.createElement("ol"); + this._listElement.className = "workers-list"; + + this.bodyElement.appendChild(this._listElement); + this._treeOutline = new TreeOutline(this._listElement); +} + +WebInspector.WorkersSidebarPane.prototype = { + addWorker: function(id, url, isShared) + { + if (id in this._workers) + return; + var worker = new WebInspector.Worker(id, url, isShared); + this._workers[id] = worker; + + var title = WebInspector.linkifyURLAsNode(url, WebInspector.displayNameForURL(url), "worker-item", true, url); + this._treeOutline.appendChild(new TreeElement(title, worker, false)); + }, + + removeWorker: function(id) + { + if (id in this._workers) { + this._treeOutline.removeChild(this._treeOutline.getCachedTreeElement(this._workers[id])); + delete this._workers[id]; + } + }, + + _setInstrumentation: function(enabled) + { + if (!enabled === !this._fakeWorkersScriptIdentifier) + return; + + if (enabled) { + this._enableWorkersCheckbox.disabled = true; + function callback(error, identifier) + { + this._fakeWorkersScriptIdentifier = identifier; + this._enableWorkersCheckbox.disabled = false; + } + PageAgent.addScriptToEvaluateOnLoad("(" + InjectedFakeWorker + ")", callback.bind(this)); + } else { + PageAgent.removeScriptToEvaluateOnLoad(this._fakeWorkersScriptIdentifier); + this._fakeWorkersScriptIdentifier = null; + } + }, + + reset: function() + { + this._setInstrumentation(this._enableWorkersCheckbox.checked); + this._treeOutline.removeChildren(); + this._workers = {}; + }, + + _onTriggerInstrument: function(event) + { + this._setInstrumentation(this._enableWorkersCheckbox.checked); + } +}; + +WebInspector.WorkersSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype; + +/** + * @constructor + */ +WebInspector.Worker = function(id, url, shared) +{ + this.id = id; + this.url = url; + this.shared = shared; +} + +/** + * @constructor + * @extends {WebInspector.SidebarPane} + */ +WebInspector.WorkerListSidebarPane = function(workerManager) +{ + WebInspector.SidebarPane.call(this, WebInspector.UIString("Workers")); + + this._enableWorkersCheckbox = new WebInspector.Checkbox( + WebInspector.UIString("Pause on start"), + "sidebar-label", + WebInspector.UIString("Automatically attach to new workers and pause them. Enabling this option will force opening inspector for all new workers.")); + this._enableWorkersCheckbox.element.id = "pause-workers-checkbox"; + this.bodyElement.appendChild(this._enableWorkersCheckbox.element); + this._enableWorkersCheckbox.addEventListener(this._autoattachToWorkersClicked.bind(this)); + this._enableWorkersCheckbox.checked = false; + + if (Preferences.sharedWorkersDebugNote) { + var note = this.bodyElement.createChild("div"); + note.id = "shared-workers-list"; + note.addStyleClass("sidebar-label") + note.textContent = Preferences.sharedWorkersDebugNote; + } + + var separator = this.bodyElement.createChild("div", "sidebar-separator"); + separator.textContent = WebInspector.UIString("Dedicated worker inspectors"); + + this._workerListElement = document.createElement("ol"); + this._workerListElement.tabIndex = 0; + this._workerListElement.addStyleClass("properties-tree"); + this._workerListElement.addStyleClass("sidebar-label"); + this.bodyElement.appendChild(this._workerListElement); + + this._idToWorkerItem = {}; + this._workerManager = workerManager; + + workerManager.addEventListener(WebInspector.WorkerManager.Events.WorkerAdded, this._workerAdded, this); + workerManager.addEventListener(WebInspector.WorkerManager.Events.WorkerRemoved, this._workerRemoved, this); + workerManager.addEventListener(WebInspector.WorkerManager.Events.WorkersCleared, this._workersCleared, this); +} + +WebInspector.WorkerListSidebarPane.prototype = { + _workerAdded: function(event) + { + this._addWorker(event.data.workerId, event.data.url, event.data.inspectorConnected); + }, + + _workerRemoved: function(event) + { + var workerItem = this._idToWorkerItem[event.data]; + delete this._idToWorkerItem[event.data]; + workerItem.parentElement.removeChild(workerItem); + }, + + _workersCleared: function(event) + { + this._idToWorkerItem = {}; + this._workerListElement.removeChildren(); + }, + + _addWorker: function(workerId, url, inspectorConnected) + { + var item = this._workerListElement.createChild("div", "dedicated-worker-item"); + var link = item.createChild("a"); + link.textContent = url; + link.href = "#"; + link.target = "_blank"; + link.addEventListener("click", this._workerItemClicked.bind(this, workerId), true); + this._idToWorkerItem[workerId] = item; + }, + + _workerItemClicked: function(workerId, event) + { + event.preventDefault(); + this._workerManager.openWorkerInspector(workerId); + }, + + _autoattachToWorkersClicked: function(event) + { + WorkerAgent.setAutoconnectToWorkers(event.target.checked); + } +} + +WebInspector.WorkerListSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype; +/* MetricsSidebarPane.js */ + +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.SidebarPane} + */ +WebInspector.MetricsSidebarPane = function() +{ + WebInspector.SidebarPane.call(this, WebInspector.UIString("Metrics")); + + WebInspector.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetChanged, this._styleSheetOrMediaQueryResultChanged, this); + WebInspector.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.MediaQueryResultChanged, this._styleSheetOrMediaQueryResultChanged, this); + WebInspector.domAgent.addEventListener(WebInspector.DOMAgent.Events.AttrModified, this._attributesUpdated, this); + WebInspector.domAgent.addEventListener(WebInspector.DOMAgent.Events.AttrRemoved, this._attributesUpdated, this); +} + +WebInspector.MetricsSidebarPane.prototype = { + /** + * @param {WebInspector.DOMNode=} node + */ + update: function(node) + { + if (node) + this.node = node; + this._innerUpdate(); + }, + + _innerUpdate: function() + { + // "style" attribute might have changed. Update metrics unless they are being edited + // (if a CSS property is added, a StyleSheetChanged event is dispatched). + if (this._isEditingMetrics) + return; + + // FIXME: avoid updates of a collapsed pane. + var node = this.node; + + if (!node || node.nodeType() !== Node.ELEMENT_NODE) { + this.bodyElement.removeChildren(); + return; + } + + function callback(style) + { + if (!style || this.node !== node) + return; + this._updateMetrics(style); + } + WebInspector.cssModel.getComputedStyleAsync(node.id, WebInspector.panels.elements.sidebarPanes.styles.forcedPseudoClasses, callback.bind(this)); + + function inlineStyleCallback(style) + { + if (!style || this.node !== node) + return; + this.inlineStyle = style; + } + WebInspector.cssModel.getInlineStylesAsync(node.id, inlineStyleCallback.bind(this)); + }, + + _styleSheetOrMediaQueryResultChanged: function() + { + this._innerUpdate(); + }, + + _attributesUpdated: function(event) + { + if (this.node !== event.data.node) + return; + + this._innerUpdate(); + }, + + _getPropertyValueAsPx: function(style, propertyName) + { + return Number(style.getPropertyValue(propertyName).replace(/px$/, "") || 0); + }, + + _getBox: function(computedStyle, componentName) + { + var suffix = componentName === "border" ? "-width" : ""; + var left = this._getPropertyValueAsPx(computedStyle, componentName + "-left" + suffix); + var top = this._getPropertyValueAsPx(computedStyle, componentName + "-top" + suffix); + var right = this._getPropertyValueAsPx(computedStyle, componentName + "-right" + suffix); + var bottom = this._getPropertyValueAsPx(computedStyle, componentName + "-bottom" + suffix); + return { left: left, top: top, right: right, bottom: bottom }; + }, + + _highlightDOMNode: function(showHighlight, mode, event) + { + event.stopPropagation(); + var nodeId = showHighlight && this.node ? this.node.id : 0; + if (nodeId) { + if (this._highlightMode === mode) + return; + this._highlightMode = mode; + WebInspector.domAgent.highlightDOMNode(nodeId, mode); + } else { + delete this._highlightMode; + WebInspector.domAgent.hideDOMNodeHighlight(); + } + + for (var i = 0; this._boxElements && i < this._boxElements.length; ++i) { + var element = this._boxElements[i]; + if (!nodeId || mode === "all" || element._name === mode) + element.style.backgroundColor = element._backgroundColor; + else + element.style.backgroundColor = ""; + } + }, + + _updateMetrics: function(style) + { + // Updating with computed style. + var metricsElement = document.createElement("div"); + metricsElement.className = "metrics"; + var self = this; + + function createBoxPartElement(style, name, side, suffix) + { + var propertyName = (name !== "position" ? name + "-" : "") + side + suffix; + var value = style.getPropertyValue(propertyName); + if (value === "" || (name !== "position" && value === "0px")) + value = "\u2012"; + else if (name === "position" && value === "auto") + value = "\u2012"; + value = value.replace(/px$/, ""); + + var element = document.createElement("div"); + element.className = side; + element.textContent = value; + element.addEventListener("dblclick", this.startEditing.bind(this, element, name, propertyName, style), false); + return element; + } + + function getContentAreaWidthPx(style) + { + var width = style.getPropertyValue("width").replace(/px$/, ""); + if (style.getPropertyValue("box-sizing") === "border-box") { + var borderBox = self._getBox(style, "border"); + var paddingBox = self._getBox(style, "padding"); + + width = width - borderBox.left - borderBox.right - paddingBox.left - paddingBox.right; + } + + return width; + } + + function getContentAreaHeightPx(style) + { + var height = style.getPropertyValue("height").replace(/px$/, ""); + if (style.getPropertyValue("box-sizing") === "border-box") { + var borderBox = self._getBox(style, "border"); + var paddingBox = self._getBox(style, "padding"); + + height = height - borderBox.top - borderBox.bottom - paddingBox.top - paddingBox.bottom; + } + + return height; + } + + // Display types for which margin is ignored. + var noMarginDisplayType = { + "table-cell": true, + "table-column": true, + "table-column-group": true, + "table-footer-group": true, + "table-header-group": true, + "table-row": true, + "table-row-group": true + }; + + // Display types for which padding is ignored. + var noPaddingDisplayType = { + "table-column": true, + "table-column-group": true, + "table-footer-group": true, + "table-header-group": true, + "table-row": true, + "table-row-group": true + }; + + // Position types for which top, left, bottom and right are ignored. + var noPositionType = { + "static": true + }; + + var boxes = ["content", "padding", "border", "margin", "position"]; + var boxColors = [ + WebInspector.Color.PageHighlight.Content, + WebInspector.Color.PageHighlight.Padding, + WebInspector.Color.PageHighlight.Border, + WebInspector.Color.PageHighlight.Margin, + WebInspector.Color.fromRGBA(0, 0, 0, 0) + ]; + var boxLabels = [WebInspector.UIString("content"), WebInspector.UIString("padding"), WebInspector.UIString("border"), WebInspector.UIString("margin"), WebInspector.UIString("position")]; + var previousBox = null; + this._boxElements = []; + for (var i = 0; i < boxes.length; ++i) { + var name = boxes[i]; + + if (name === "margin" && noMarginDisplayType[style.getPropertyValue("display")]) + continue; + if (name === "padding" && noPaddingDisplayType[style.getPropertyValue("display")]) + continue; + if (name === "position" && noPositionType[style.getPropertyValue("position")]) + continue; + + var boxElement = document.createElement("div"); + boxElement.className = name; + boxElement._backgroundColor = boxColors[i].toString("original"); + boxElement._name = name; + boxElement.style.backgroundColor = boxElement._backgroundColor; + boxElement.addEventListener("mouseover", this._highlightDOMNode.bind(this, true, name === "position" ? "all" : name), false); + this._boxElements.push(boxElement); + + if (name === "content") { + var widthElement = document.createElement("span"); + widthElement.textContent = getContentAreaWidthPx(style); + widthElement.addEventListener("dblclick", this.startEditing.bind(this, widthElement, "width", "width", style), false); + + var heightElement = document.createElement("span"); + heightElement.textContent = getContentAreaHeightPx(style); + heightElement.addEventListener("dblclick", this.startEditing.bind(this, heightElement, "height", "height", style), false); + + boxElement.appendChild(widthElement); + boxElement.appendChild(document.createTextNode(" \u00D7 ")); + boxElement.appendChild(heightElement); + } else { + var suffix = (name === "border" ? "-width" : ""); + + var labelElement = document.createElement("div"); + labelElement.className = "label"; + labelElement.textContent = boxLabels[i]; + boxElement.appendChild(labelElement); + + boxElement.appendChild(createBoxPartElement.call(this, style, name, "top", suffix)); + boxElement.appendChild(document.createElement("br")); + boxElement.appendChild(createBoxPartElement.call(this, style, name, "left", suffix)); + + if (previousBox) + boxElement.appendChild(previousBox); + + boxElement.appendChild(createBoxPartElement.call(this, style, name, "right", suffix)); + boxElement.appendChild(document.createElement("br")); + boxElement.appendChild(createBoxPartElement.call(this, style, name, "bottom", suffix)); + } + + previousBox = boxElement; + } + + metricsElement.appendChild(previousBox); + metricsElement.addEventListener("mouseover", this._highlightDOMNode.bind(this, false, ""), false); + this.bodyElement.removeChildren(); + this.bodyElement.appendChild(metricsElement); + }, + + startEditing: function(targetElement, box, styleProperty, computedStyle) + { + if (WebInspector.isBeingEdited(targetElement)) + return; + + var context = { box: box, styleProperty: styleProperty, computedStyle: computedStyle }; + var boundKeyDown = this._handleKeyDown.bind(this, context, styleProperty); + context.keyDownHandler = boundKeyDown; + targetElement.addEventListener("keydown", boundKeyDown, false); + + this._isEditingMetrics = true; + + var config = new WebInspector.EditingConfig(this.editingCommitted.bind(this), this.editingCancelled.bind(this), context); + WebInspector.startEditing(targetElement, config); + + window.getSelection().setBaseAndExtent(targetElement, 0, targetElement, 1); + }, + + _handleKeyDown: function(context, styleProperty, event) + { + if (!/^(?:Page)?(?:Up|Down)$/.test(event.keyIdentifier)) + return; + var element = event.currentTarget; + + var selection = window.getSelection(); + if (!selection.rangeCount) + return; + + var selectionRange = selection.getRangeAt(0); + if (!selectionRange.commonAncestorContainer.isSelfOrDescendant(element)) + return; + + var originalValue = element.textContent; + var wordRange = selectionRange.startContainer.rangeOfWord(selectionRange.startOffset, WebInspector.StylesSidebarPane.StyleValueDelimiters, element); + var wordString = wordRange.toString(); + + var matches = /(.*?)(-?(?:\d+(?:\.\d+)?|\.\d+))(.*)/.exec(wordString); + var replacementString; + if (matches && matches.length) { + var prefix = matches[1]; + var suffix = matches[3]; + var number = WebInspector.StylesSidebarPane.alteredFloatNumber(parseFloat(matches[2]), event); + if (number === null) { + // Need to check for null explicitly. + return; + } + + if (styleProperty !== "margin" && number < 0) + number = 0; + + replacementString = prefix + number + suffix; + } + if (!replacementString) + return; + + var replacementTextNode = document.createTextNode(replacementString); + + wordRange.deleteContents(); + wordRange.insertNode(replacementTextNode); + + var finalSelectionRange = document.createRange(); + finalSelectionRange.setStart(replacementTextNode, 0); + finalSelectionRange.setEnd(replacementTextNode, replacementString.length); + + selection.removeAllRanges(); + selection.addRange(finalSelectionRange); + + event.handled = true; + event.preventDefault(); + this._applyUserInput(element, replacementString, originalValue, context, false); + }, + + editingEnded: function(element, context) + { + delete this.originalPropertyData; + delete this.previousPropertyDataCandidate; + element.removeEventListener("keydown", context.keyDownHandler, false); + delete this._isEditingMetrics; + }, + + editingCancelled: function(element, context) + { + if ("originalPropertyData" in this && this.inlineStyle) { + if (!this.originalPropertyData) { + // An added property, remove the last property in the style. + var pastLastSourcePropertyIndex = this.inlineStyle.pastLastSourcePropertyIndex(); + if (pastLastSourcePropertyIndex) + this.inlineStyle.allProperties[pastLastSourcePropertyIndex - 1].setText("", false); + } else + this.inlineStyle.allProperties[this.originalPropertyData.index].setText(this.originalPropertyData.propertyText, false); + } + this.editingEnded(element, context); + this.update(); + }, + + _applyUserInput: function(element, userInput, previousContent, context, commitEditor) + { + if (!this.inlineStyle) { + // Element has no renderer. + return this.editingCancelled(element, context); // nothing changed, so cancel + } + + if (commitEditor && userInput === previousContent) + return this.editingCancelled(element, context); // nothing changed, so cancel + + if (context.box !== "position" && (!userInput || userInput === "\u2012")) + userInput = "0px"; + else if (context.box === "position" && (!userInput || userInput === "\u2012")) + userInput = "auto"; + + userInput = userInput.toLowerCase(); + // Append a "px" unit if the user input was just a number. + if (/^\d+$/.test(userInput)) + userInput += "px"; + + var styleProperty = context.styleProperty; + var computedStyle = context.computedStyle; + + if (computedStyle.getPropertyValue("box-sizing") === "border-box" && (styleProperty === "width" || styleProperty === "height")) { + if (!userInput.match(/px$/)) { + WebInspector.log("For elements with box-sizing: border-box, only absolute content area dimensions can be applied", WebInspector.ConsoleMessage.MessageLevel.Error, true); + return; + } + + var borderBox = this._getBox(computedStyle, "border"); + var paddingBox = this._getBox(computedStyle, "padding"); + var userValuePx = Number(userInput.replace(/px$/, "")); + if (isNaN(userValuePx)) + return; + if (styleProperty === "width") + userValuePx += borderBox.left + borderBox.right + paddingBox.left + paddingBox.right; + else + userValuePx += borderBox.top + borderBox.bottom + paddingBox.top + paddingBox.bottom; + + userInput = userValuePx + "px"; + } + + this.previousPropertyDataCandidate = null; + var self = this; + var callback = function(style) { + if (!style) + return; + self.inlineStyle = style; + if (!("originalPropertyData" in self)) + self.originalPropertyData = self.previousPropertyDataCandidate; + + if (typeof self._highlightMode !== "undefined") { + WebInspector.domAgent.highlightDOMNode(self.node.id, self._highlightMode); + } + + if (commitEditor) { + self.dispatchEventToListeners("metrics edited"); + self.update(); + } + }; + + var allProperties = this.inlineStyle.allProperties; + for (var i = 0; i < allProperties.length; ++i) { + var property = allProperties[i]; + if (property.name !== context.styleProperty || property.inactive) + continue; + + this.previousPropertyDataCandidate = property; + property.setValue(userInput, commitEditor, callback); + return; + } + + this.inlineStyle.appendProperty(context.styleProperty, userInput, callback); + }, + + editingCommitted: function(element, userInput, previousContent, context) + { + this.editingEnded(element, context); + this._applyUserInput(element, userInput, previousContent, context, true); + } +} + +WebInspector.MetricsSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype; +/* PropertiesSidebarPane.js */ + +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.SidebarPane} + */ +WebInspector.PropertiesSidebarPane = function() +{ + WebInspector.SidebarPane.call(this, WebInspector.UIString("Properties")); +} + +WebInspector.PropertiesSidebarPane._objectGroupName = "properties-sidebar-pane"; + +WebInspector.PropertiesSidebarPane.prototype = { + update: function(node) + { + var body = this.bodyElement; + + if (!node) { + body.removeChildren(); + this.sections = []; + return; + } + + WebInspector.RemoteObject.resolveNode(node, WebInspector.PropertiesSidebarPane._objectGroupName, nodeResolved.bind(this)); + + function nodeResolved(object) + { + if (!object) + return; + function protoList() + { + var proto = this; + var result = {}; + var counter = 1; + while (proto) { + result[counter++] = proto; + proto = proto.__proto__; + } + return result; + } + object.callFunction(protoList, nodePrototypesReady.bind(this)); + object.release(); + } + + function nodePrototypesReady(object) + { + if (!object) + return; + object.getOwnProperties(fillSection.bind(this)); + } + + function fillSection(prototypes) + { + if (!prototypes) + return; + + var body = this.bodyElement; + body.removeChildren(); + this.sections = []; + + // Get array of prototype user-friendly names. + for (var i = 0; i < prototypes.length; ++i) { + if (!parseInt(prototypes[i].name, 10)) + continue; + + var prototype = prototypes[i].value; + var title = prototype.description; + if (title.match(/Prototype$/)) + title = title.replace(/Prototype$/, ""); + var section = new WebInspector.ObjectPropertiesSection(prototype, title); + this.sections.push(section); + body.appendChild(section.element); + } + } + } +} + +WebInspector.PropertiesSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype; +/* EventListenersSidebarPane.js */ + +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * Copyright (C) 2009 Joseph Pecoraro + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.SidebarPane} + */ +WebInspector.EventListenersSidebarPane = function() +{ + WebInspector.SidebarPane.call(this, WebInspector.UIString("Event Listeners")); + this.bodyElement.addStyleClass("events-pane"); + + this.sections = []; + + this.settingsSelectElement = document.createElement("select"); + this.settingsSelectElement.className = "select-filter"; + + var option = document.createElement("option"); + option.value = "all"; + option.label = WebInspector.UIString("All Nodes"); + this.settingsSelectElement.appendChild(option); + + option = document.createElement("option"); + option.value = "selected"; + option.label = WebInspector.UIString("Selected Node Only"); + this.settingsSelectElement.appendChild(option); + + var filter = WebInspector.settings.eventListenersFilter.get(); + if (filter === "all") + this.settingsSelectElement[0].selected = true; + else if (filter === "selected") + this.settingsSelectElement[1].selected = true; + this.settingsSelectElement.addEventListener("click", function(event) { event.stopPropagation() }, false); + this.settingsSelectElement.addEventListener("change", this._changeSetting.bind(this), false); + + this.titleElement.appendChild(this.settingsSelectElement); + + this._linkifier = WebInspector.debuggerPresentationModel.createLinkifier(); +} + +WebInspector.EventListenersSidebarPane._objectGroupName = "event-listeners-sidebar-pane"; + +WebInspector.EventListenersSidebarPane.prototype = { + update: function(node) + { + RuntimeAgent.releaseObjectGroup(WebInspector.EventListenersSidebarPane._objectGroupName); + this._linkifier.reset(); + + var body = this.bodyElement; + body.removeChildren(); + this.sections = []; + + var self = this; + function callback(error, eventListeners) { + if (error) + return; + + var sectionNames = []; + var sectionMap = {}; + for (var i = 0; i < eventListeners.length; ++i) { + var eventListener = eventListeners[i]; + eventListener.node = WebInspector.domAgent.nodeForId(eventListener.nodeId); + delete eventListener.nodeId; // no longer needed + if (/^function _inspectorCommandLineAPI_logEvent\(/.test(eventListener.handlerBody.toString())) + continue; // ignore event listeners generated by monitorEvent + var type = eventListener.type; + var section = sectionMap[type]; + if (!section) { + section = new WebInspector.EventListenersSection(type, node.id, self._linkifier); + sectionMap[type] = section; + sectionNames.push(type); + self.sections.push(section); + } + section.addListener(eventListener); + } + + if (sectionNames.length === 0) { + var div = document.createElement("div"); + div.className = "info"; + div.textContent = WebInspector.UIString("No Event Listeners"); + body.appendChild(div); + return; + } + + sectionNames.sort(); + for (var i = 0; i < sectionNames.length; ++i) { + var section = sectionMap[sectionNames[i]]; + section.update(); + body.appendChild(section.element); + } + } + + if (node) + node.eventListeners(callback); + }, + + _changeSetting: function(event) + { + var selectedOption = this.settingsSelectElement[this.settingsSelectElement.selectedIndex]; + WebInspector.settings.eventListenersFilter.set(selectedOption.value); + + for (var i = 0; i < this.sections.length; ++i) + this.sections[i].update(); + } +} + +WebInspector.EventListenersSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype; + +/** + * @constructor + * @extends {WebInspector.PropertiesSection} + */ +WebInspector.EventListenersSection = function(title, nodeId, linkifier) +{ + this.eventListeners = []; + this._nodeId = nodeId; + this._linkifier = linkifier; + WebInspector.PropertiesSection.call(this, title); + + // Changed from a Properties List + this.propertiesElement.parentNode.removeChild(this.propertiesElement); + delete this.propertiesElement; + delete this.propertiesTreeOutline; + + this.eventBars = document.createElement("div"); + this.eventBars.className = "event-bars"; + this.element.appendChild(this.eventBars); +} + +WebInspector.EventListenersSection.prototype = { + update: function() + { + // A Filtered Array simplifies when to create connectors + var filteredEventListeners = this.eventListeners; + if (WebInspector.settings.eventListenersFilter.get() == "selected") { + filteredEventListeners = []; + for (var i = 0; i < this.eventListeners.length; ++i) { + var eventListener = this.eventListeners[i]; + if (eventListener.node.id === this._nodeId) + filteredEventListeners.push(eventListener); + } + } + + this.eventBars.removeChildren(); + var length = filteredEventListeners.length; + for (var i = 0; i < length; ++i) { + var eventListener = filteredEventListeners[i]; + var eventListenerBar = new WebInspector.EventListenerBar(eventListener, this._nodeId, this._linkifier); + this.eventBars.appendChild(eventListenerBar.element); + } + }, + + addListener: function(eventListener) + { + this.eventListeners.push(eventListener); + } +} + +WebInspector.EventListenersSection.prototype.__proto__ = WebInspector.PropertiesSection.prototype; + +/** + * @constructor + * @extends {WebInspector.ObjectPropertiesSection} + */ +WebInspector.EventListenerBar = function(eventListener, nodeId, linkifier) +{ + WebInspector.ObjectPropertiesSection.call(this); + + this.eventListener = eventListener; + this._nodeId = nodeId; + this._setNodeTitle(); + this._setFunctionSubtitle(linkifier); + this.editable = false; + this.element.className = "event-bar"; /* Changed from "section" */ + this.headerElement.addStyleClass("source-code"); + this.propertiesElement.className = "event-properties properties-tree source-code"; /* Changed from "properties" */ +} + +WebInspector.EventListenerBar.prototype = { + update: function() + { + function updateWithNodeObject(nodeObject) + { + var properties = []; + + if (this.eventListener.type) + properties.push(WebInspector.RemoteObjectProperty.fromPrimitiveValue("type", this.eventListener.type)); + if (typeof this.eventListener.useCapture !== "undefined") + properties.push(WebInspector.RemoteObjectProperty.fromPrimitiveValue("useCapture", this.eventListener.useCapture)); + if (typeof this.eventListener.isAttribute !== "undefined") + properties.push(WebInspector.RemoteObjectProperty.fromPrimitiveValue("isAttribute", this.eventListener.isAttribute)); + if (nodeObject) + properties.push(new WebInspector.RemoteObjectProperty("node", nodeObject)); + if (typeof this.eventListener.handlerBody !== "undefined") + properties.push(WebInspector.RemoteObjectProperty.fromPrimitiveValue("listenerBody", this.eventListener.handlerBody)); + if (this.eventListener.location) { + properties.push(WebInspector.RemoteObjectProperty.fromPrimitiveValue("sourceName", this.eventListener.location.scriptId)); + properties.push(WebInspector.RemoteObjectProperty.fromPrimitiveValue("lineNumber", this.eventListener.location.lineNumber)); + } + + this.updateProperties(properties); + } + WebInspector.RemoteObject.resolveNode(this.eventListener.node, WebInspector.EventListenersSidebarPane._objectGroupName, updateWithNodeObject.bind(this)); + }, + + _setNodeTitle: function() + { + var node = this.eventListener.node; + if (!node) + return; + + if (node.nodeType() === Node.DOCUMENT_NODE) { + this.titleElement.textContent = "document"; + return; + } + + if (node.id === this._nodeId) { + this.titleElement.textContent = node.appropriateSelectorFor(); + return; + } + + this.titleElement.removeChildren(); + this.titleElement.appendChild(WebInspector.DOMPresentationUtils.linkifyNodeReference(this.eventListener.node)); + }, + + _setFunctionSubtitle: function(linkifier) + { + // Requires that Function.toString() return at least the function's signature. + if (this.eventListener.location) { + this.subtitleElement.removeChildren(); + // FIXME(62725): eventListener.location should be a debugger Location. + var url = this.eventListener.location.scriptId; + var lineNumber = this.eventListener.location.lineNumber - 1; + var columnNumber = 0; + var urlElement = linkifier.linkifyLocation(url, lineNumber, columnNumber); + this.subtitleElement.appendChild(urlElement); + } else { + var match = this.eventListener.handlerBody.match(/function ([^\(]+?)\(/); + if (match) + this.subtitleElement.textContent = match[1]; + else + this.subtitleElement.textContent = WebInspector.UIString("(anonymous function)"); + } + } +} + +WebInspector.EventListenerBar.prototype.__proto__ = WebInspector.ObjectPropertiesSection.prototype; +/* Color.js */ + +/* + * Copyright (C) 2009 Apple Inc. All rights reserved. + * Copyright (C) 2009 Joseph Pecoraro + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + */ +WebInspector.Color = function(str) +{ + this.value = str; + this._parse(); +} + +/** + * @param {number=} a + */ +WebInspector.Color.fromRGBA = function(r, g, b, a) +{ + return new WebInspector.Color("rgba(" + r + "," + g + "," + b + "," + (typeof a === "undefined" ? 1 : a) + ")"); +} + +WebInspector.Color.prototype = { + /** + * @return {string} + */ + get shorthex() + { + if ("_short" in this) + return this._short; + + if (!this.simple) + return ""; + + var hex = this.hex; + if (hex.charAt(0) === hex.charAt(1) && hex.charAt(2) === hex.charAt(3) && hex.charAt(4) === hex.charAt(5)) + this._short = hex.charAt(0) + hex.charAt(2) + hex.charAt(4); + else + this._short = hex; + + return this._short; + }, + + /** + * @return {string} + */ + get hex() + { + if (!this.simple) + return ""; + + return this._hex; + }, + + set hex(x) + { + this._hex = x; + }, + + /** + * @return {Array.} + */ + get rgb() + { + if (this._rgb) + return this._rgb; + + if (this.simple) + this._rgb = this._hexToRGB(this.hex); + else { + var rgba = this.rgba; + this._rgb = [rgba[0], rgba[1], rgba[2]]; + } + + return this._rgb; + }, + + set rgb(x) + { + this._rgb = x; + }, + + /** + * @return {Array.} + */ + get hsl() + { + if (this._hsl) + return this._hsl; + + this._hsl = this._rgbToHSL(this.rgb); + return this._hsl; + }, + + set hsl(x) + { + this._hsl = x; + }, + + /** + * @return {string} + */ + get nickname() + { + if (typeof this._nickname !== "undefined") // would be set on parse if there was a nickname + return this._nickname; + else + return ""; + }, + + set nickname(x) + { + this._nickname = x; + }, + + /** + * @return {Array.} + */ + get rgba() + { + return this._rgba; + }, + + set rgba(x) + { + this._rgba = x; + }, + + /** + * @return {Array.} + */ + get hsla() + { + return this._hsla; + }, + + set hsla(x) + { + this._hsla = x; + }, + + /** + * @return {boolean} + */ + hasShortHex: function() + { + var shorthex = this.shorthex; + return (!!shorthex && shorthex.length === 3); + }, + + /** + * @return {string} + */ + toString: function(format) + { + if (!format) + format = this.format; + + switch (format) { + case "original": + return this.value; + case "rgb": + return "rgb(" + this.rgb.join(", ") + ")"; + case "rgba": + return "rgba(" + this.rgba.join(", ") + ")"; + case "hsl": + var hsl = this.hsl; + return "hsl(" + hsl[0] + ", " + hsl[1] + "%, " + hsl[2] + "%)"; + case "hsla": + var hsla = this.hsla; + return "hsla(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%, " + hsla[3] + ")"; + case "hex": + return "#" + this.hex; + case "shorthex": + return "#" + this.shorthex; + case "nickname": + return this.nickname; + } + + throw "invalid color format"; + }, + + /** + * @return {Object} + */ + toProtocolRGBA: function() + { + if (this._protocolRGBA) + return this._protocolRGBA; + + var components = this.rgba; + if (components) + this._protocolRGBA = { r: Number(components[0]), g: Number(components[1]), b: Number(components[2]), a: Number(components[3]) }; + else { + components = this.rgb; + this._protocolRGBA = { r: Number(components[0]), g: Number(components[1]), b: Number(components[2]) }; + } + return this._protocolRGBA; + }, + + /** + * @param {number} value + * @param {number} min + * @param {number} max + * @return {number} + */ + _clamp: function(value, min, max) + { + if (value < min) + return min; + if (value > max) + return max; + return value; + }, + + /** + * @param {number|string} rgbValue + * @return {number} + */ + _individualRGBValueToFloatValue: function(rgbValue) + { + if (typeof rgbValue === "number") + return this._clamp(rgbValue, 0, 255); + + if (rgbValue.indexOf("%") === -1) { + var intValue = parseInt(rgbValue, 10); + return this._clamp(intValue, 0, 255); + } + + var percentValue = parseFloat(rgbValue); + return this._clamp(percentValue, 0, 100) * 2.55; + }, + + /** + * @param {number|string} rgbValue + * @return {string} + */ + _individualRGBValueToHexValue: function(rgbValue) + { + var floatValue = this._individualRGBValueToFloatValue(rgbValue); + var hex = Math.round(floatValue).toString(16); + if (hex.length === 1) + hex = "0" + hex; + return hex; + }, + + /** + * @param {Array.} rgb + * @return {string} + */ + _rgbStringsToHex: function(rgb) + { + var r = this._individualRGBValueToHexValue(rgb[0]); + var g = this._individualRGBValueToHexValue(rgb[1]); + var b = this._individualRGBValueToHexValue(rgb[2]); + return (r + g + b).toUpperCase(); + }, + + /** + * @param {Array.} rgb + * @return {string} + */ + _rgbToHex: function(rgb) + { + var r = this._individualRGBValueToHexValue(rgb[0]); + var g = this._individualRGBValueToHexValue(rgb[1]); + var b = this._individualRGBValueToHexValue(rgb[2]); + return (r + g + b).toUpperCase(); + }, + + /** + * @param {string} hex + * @return {Array.} + */ + _hexToRGB: function(hex) + { + var r = parseInt(hex.substring(0,2), 16); + var g = parseInt(hex.substring(2,4), 16); + var b = parseInt(hex.substring(4,6), 16); + + return [r, g, b]; + }, + + /** + * @param {Array.} rgb + * @return {Array.} + */ + _rgbToHSL: function(rgb) + { + var r = this._individualRGBValueToFloatValue(rgb[0]) / 255; + var g = this._individualRGBValueToFloatValue(rgb[1]) / 255; + var b = this._individualRGBValueToFloatValue(rgb[2]) / 255; + var max = Math.max(r, g, b); + var min = Math.min(r, g, b); + var diff = max - min; + var add = max + min; + + if (min === max) + var h = 0; + else if (r === max) + var h = ((60 * (g - b) / diff) + 360) % 360; + else if (g === max) + var h = (60 * (b - r) / diff) + 120; + else + var h = (60 * (r - g) / diff) + 240; + + var l = 0.5 * add; + + if (l === 0) + var s = 0; + else if (l === 1) + var s = 1; + else if (l <= 0.5) + var s = diff / add; + else + var s = diff / (2 - add); + + h = Math.round(h); + s = Math.round(s*100); + l = Math.round(l*100); + + return [h, s, l]; + }, + + /** + * @param {Array.} hsl + * @return {Array.} + */ + _hslToRGB: function(hsl) + { + var h = parseFloat(hsl[0]) / 360; + var s = parseFloat(hsl[1]) / 100; + var l = parseFloat(hsl[2]) / 100; + + if (s < 0) + s = 0; + + if (l <= 0.5) + var q = l * (1 + s); + else + var q = l + s - (l * s); + + var p = 2 * l - q; + + var tr = h + (1 / 3); + var tg = h; + var tb = h - (1 / 3); + + var r = Math.round(hueToRGB(p, q, tr) * 255); + var g = Math.round(hueToRGB(p, q, tg) * 255); + var b = Math.round(hueToRGB(p, q, tb) * 255); + return [r, g, b]; + + function hueToRGB(p, q, h) { + if (h < 0) + h += 1; + else if (h > 1) + h -= 1; + + if ((h * 6) < 1) + return p + (q - p) * h * 6; + else if ((h * 2) < 1) + return q; + else if ((h * 3) < 2) + return p + (q - p) * ((2 / 3) - h) * 6; + else + return p; + } + }, + + /** + * @param {Array.} rgba + * @return {Array.} + */ + _rgbaToHSLA: function(rgba, alpha) + { + var hsl = this._rgbToHSL(rgba) + hsl.push(alpha); + return hsl; + }, + + /** + * @param {Array.} hsla + * @param {number} alpha + * @return {Array.} + */ + _hslaToRGBA: function(hsla, alpha) + { + var rgb = this._hslToRGB(hsla); + rgb.push(alpha); + return rgb; + }, + + _parse: function() + { + // Special Values - Advanced but Must Be Parsed First - transparent + var value = this.value.toLowerCase().replace(/%|\s+/g, ""); + if (value in WebInspector.Color.AdvancedNickNames) { + this.format = "nickname"; + var set = WebInspector.Color.AdvancedNickNames[value]; + this.simple = false; + this.rgba = set[0]; + this.hsla = set[1]; + this.nickname = set[2]; + this.alpha = set[0][3]; + return; + } + + // Simple - #hex, rgb(), nickname, hsl() + var simple = /^(?:#([0-9a-f]{3,6})|rgb\(([^)]+)\)|(\w+)|hsl\(([^)]+)\))$/i; + var match = this.value.match(simple); + if (match) { + this.simple = true; + + if (match[1]) { // hex + var hex = match[1].toUpperCase(); + if (hex.length === 3) { + this.format = "shorthex"; + this.hex = hex.charAt(0) + hex.charAt(0) + hex.charAt(1) + hex.charAt(1) + hex.charAt(2) + hex.charAt(2); + } else { + this.format = "hex"; + this.hex = hex; + } + } else if (match[2]) { // rgb + this.format = "rgb"; + var rgb = match[2].split(/\s*,\s*/); + this.rgb = rgb; + this.hex = this._rgbStringsToHex(rgb); + } else if (match[3]) { // nickname + var nickname = match[3].toLowerCase(); + if (nickname in WebInspector.Color.Nicknames) { + this.format = "nickname"; + this.hex = WebInspector.Color.Nicknames[nickname]; + } else // unknown name + throw "unknown color name"; + } else if (match[4]) { // hsl + this.format = "hsl"; + var hsl = match[4].replace(/%/g, "").split(/\s*,\s*/); + this.hsl = hsl; + this.rgb = this._hslToRGB(hsl); + this.hex = this._rgbToHex(this.rgb); + } + + // Fill in the values if this is a known hex color + var hex = this.hex; + if (hex && hex in WebInspector.Color.HexTable) { + var set = WebInspector.Color.HexTable[hex]; + this.rgb = set[0]; + this.hsl = set[1]; + this.nickname = set[2]; + } + + return; + } + + // Advanced - rgba(), hsla() + var advanced = /^(?:rgba\(([^)]+)\)|hsla\(([^)]+)\))$/; + match = this.value.match(advanced); + if (match) { + this.simple = false; + if (match[1]) { // rgba + this.format = "rgba"; + this.rgba = match[1].split(/\s*,\s*/); + this.rgba[3] = this.alpha = this._clamp(this.rgba[3], 0, 1); + this.hsla = this._rgbaToHSLA(this.rgba, this.alpha); + } else if (match[2]) { // hsla + this.format = "hsla"; + this.hsla = match[2].replace(/%/g, "").split(/\s*,\s*/); + this.hsla[3] = this.alpha = this._clamp(this.hsla[3], 0, 1); + this.rgba = this._hslaToRGBA(this.hsla, this.alpha); + } + + return; + } + + // Could not parse as a valid color + throw "could not parse color"; + } +} + +// Simple Values: [rgb, hsl, nickname] +WebInspector.Color.HexTable = { + "000000": [[0, 0, 0], [0, 0, 0], "black"], + "000080": [[0, 0, 128], [240, 100, 25], "navy"], + "00008B": [[0, 0, 139], [240, 100, 27], "darkBlue"], + "0000CD": [[0, 0, 205], [240, 100, 40], "mediumBlue"], + "0000FF": [[0, 0, 255], [240, 100, 50], "blue"], + "006400": [[0, 100, 0], [120, 100, 20], "darkGreen"], + "008000": [[0, 128, 0], [120, 100, 25], "green"], + "008080": [[0, 128, 128], [180, 100, 25], "teal"], + "008B8B": [[0, 139, 139], [180, 100, 27], "darkCyan"], + "00BFFF": [[0, 191, 255], [195, 100, 50], "deepSkyBlue"], + "00CED1": [[0, 206, 209], [181, 100, 41], "darkTurquoise"], + "00FA9A": [[0, 250, 154], [157, 100, 49], "mediumSpringGreen"], + "00FF00": [[0, 255, 0], [120, 100, 50], "lime"], + "00FF7F": [[0, 255, 127], [150, 100, 50], "springGreen"], + "00FFFF": [[0, 255, 255], [180, 100, 50], "cyan"], + "191970": [[25, 25, 112], [240, 64, 27], "midnightBlue"], + "1E90FF": [[30, 144, 255], [210, 100, 56], "dodgerBlue"], + "20B2AA": [[32, 178, 170], [177, 70, 41], "lightSeaGreen"], + "228B22": [[34, 139, 34], [120, 61, 34], "forestGreen"], + "2E8B57": [[46, 139, 87], [146, 50, 36], "seaGreen"], + "2F4F4F": [[47, 79, 79], [180, 25, 25], "darkSlateGray"], + "32CD32": [[50, 205, 50], [120, 61, 50], "limeGreen"], + "3CB371": [[60, 179, 113], [147, 50, 47], "mediumSeaGreen"], + "40E0D0": [[64, 224, 208], [174, 72, 56], "turquoise"], + "4169E1": [[65, 105, 225], [225, 73, 57], "royalBlue"], + "4682B4": [[70, 130, 180], [207, 44, 49], "steelBlue"], + "483D8B": [[72, 61, 139], [248, 39, 39], "darkSlateBlue"], + "48D1CC": [[72, 209, 204], [178, 60, 55], "mediumTurquoise"], + "4B0082": [[75, 0, 130], [275, 100, 25], "indigo"], + "556B2F": [[85, 107, 47], [82, 39, 30], "darkOliveGreen"], + "5F9EA0": [[95, 158, 160], [182, 25, 50], "cadetBlue"], + "6495ED": [[100, 149, 237], [219, 79, 66], "cornflowerBlue"], + "66CDAA": [[102, 205, 170], [160, 51, 60], "mediumAquaMarine"], + "696969": [[105, 105, 105], [0, 0, 41], "dimGray"], + "6A5ACD": [[106, 90, 205], [248, 53, 58], "slateBlue"], + "6B8E23": [[107, 142, 35], [80, 60, 35], "oliveDrab"], + "708090": [[112, 128, 144], [210, 13, 50], "slateGray"], + "778899": [[119, 136, 153], [210, 14, 53], "lightSlateGray"], + "7B68EE": [[123, 104, 238], [249, 80, 67], "mediumSlateBlue"], + "7CFC00": [[124, 252, 0], [90, 100, 49], "lawnGreen"], + "7FFF00": [[127, 255, 0], [90, 100, 50], "chartreuse"], + "7FFFD4": [[127, 255, 212], [160, 100, 75], "aquamarine"], + "800000": [[128, 0, 0], [0, 100, 25], "maroon"], + "800080": [[128, 0, 128], [300, 100, 25], "purple"], + "808000": [[128, 128, 0], [60, 100, 25], "olive"], + "808080": [[128, 128, 128], [0, 0, 50], "gray"], + "87CEEB": [[135, 206, 235], [197, 71, 73], "skyBlue"], + "87CEFA": [[135, 206, 250], [203, 92, 75], "lightSkyBlue"], + "8A2BE2": [[138, 43, 226], [271, 76, 53], "blueViolet"], + "8B0000": [[139, 0, 0], [0, 100, 27], "darkRed"], + "8B008B": [[139, 0, 139], [300, 100, 27], "darkMagenta"], + "8B4513": [[139, 69, 19], [25, 76, 31], "saddleBrown"], + "8FBC8F": [[143, 188, 143], [120, 25, 65], "darkSeaGreen"], + "90EE90": [[144, 238, 144], [120, 73, 75], "lightGreen"], + "9370D8": [[147, 112, 219], [260, 60, 65], "mediumPurple"], + "9400D3": [[148, 0, 211], [282, 100, 41], "darkViolet"], + "98FB98": [[152, 251, 152], [120, 93, 79], "paleGreen"], + "9932CC": [[153, 50, 204], [280, 61, 50], "darkOrchid"], + "9ACD32": [[154, 205, 50], [80, 61, 50], "yellowGreen"], + "A0522D": [[160, 82, 45], [19, 56, 40], "sienna"], + "A52A2A": [[165, 42, 42], [0, 59, 41], "brown"], + "A9A9A9": [[169, 169, 169], [0, 0, 66], "darkGray"], + "ADD8E6": [[173, 216, 230], [195, 53, 79], "lightBlue"], + "ADFF2F": [[173, 255, 47], [84, 100, 59], "greenYellow"], + "AFEEEE": [[175, 238, 238], [180, 65, 81], "paleTurquoise"], + "B0C4DE": [[176, 196, 222], [214, 41, 78], "lightSteelBlue"], + "B0E0E6": [[176, 224, 230], [187, 52, 80], "powderBlue"], + "B22222": [[178, 34, 34], [0, 68, 42], "fireBrick"], + "B8860B": [[184, 134, 11], [43, 89, 38], "darkGoldenrod"], + "BA55D3": [[186, 85, 211], [288, 59, 58], "mediumOrchid"], + "BC8F8F": [[188, 143, 143], [0, 25, 65], "rosyBrown"], + "BDB76B": [[189, 183, 107], [56, 38, 58], "darkKhaki"], + "C0C0C0": [[192, 192, 192], [0, 0, 75], "silver"], + "C71585": [[199, 21, 133], [322, 81, 43], "mediumVioletRed"], + "CD5C5C": [[205, 92, 92], [0, 53, 58], "indianRed"], + "CD853F": [[205, 133, 63], [30, 59, 53], "peru"], + "D2691E": [[210, 105, 30], [25, 75, 47], "chocolate"], + "D2B48C": [[210, 180, 140], [34, 44, 69], "tan"], + "D3D3D3": [[211, 211, 211], [0, 0, 83], "lightGrey"], + "D87093": [[219, 112, 147], [340, 60, 65], "paleVioletRed"], + "D8BFD8": [[216, 191, 216], [300, 24, 80], "thistle"], + "DA70D6": [[218, 112, 214], [302, 59, 65], "orchid"], + "DAA520": [[218, 165, 32], [43, 74, 49], "goldenrod"], + "DC143C": [[237, 164, 61], [35, 83, 58], "crimson"], + "DCDCDC": [[220, 220, 220], [0, 0, 86], "gainsboro"], + "DDA0DD": [[221, 160, 221], [300, 47, 75], "plum"], + "DEB887": [[222, 184, 135], [34, 57, 70], "burlyWood"], + "E0FFFF": [[224, 255, 255], [180, 100, 94], "lightCyan"], + "E6E6FA": [[230, 230, 250], [240, 67, 94], "lavender"], + "E9967A": [[233, 150, 122], [15, 72, 70], "darkSalmon"], + "EE82EE": [[238, 130, 238], [300, 76, 72], "violet"], + "EEE8AA": [[238, 232, 170], [55, 67, 80], "paleGoldenrod"], + "F08080": [[240, 128, 128], [0, 79, 72], "lightCoral"], + "F0E68C": [[240, 230, 140], [54, 77, 75], "khaki"], + "F0F8FF": [[240, 248, 255], [208, 100, 97], "aliceBlue"], + "F0FFF0": [[240, 255, 240], [120, 100, 97], "honeyDew"], + "F0FFFF": [[240, 255, 255], [180, 100, 97], "azure"], + "F4A460": [[244, 164, 96], [28, 87, 67], "sandyBrown"], + "F5DEB3": [[245, 222, 179], [39, 77, 83], "wheat"], + "F5F5DC": [[245, 245, 220], [60, 56, 91], "beige"], + "F5F5F5": [[245, 245, 245], [0, 0, 96], "whiteSmoke"], + "F5FFFA": [[245, 255, 250], [150, 100, 98], "mintCream"], + "F8F8FF": [[248, 248, 255], [240, 100, 99], "ghostWhite"], + "FA8072": [[250, 128, 114], [6, 93, 71], "salmon"], + "FAEBD7": [[250, 235, 215], [34, 78, 91], "antiqueWhite"], + "FAF0E6": [[250, 240, 230], [30, 67, 94], "linen"], + "FAFAD2": [[250, 250, 210], [60, 80, 90], "lightGoldenrodYellow"], + "FDF5E6": [[253, 245, 230], [39, 85, 95], "oldLace"], + "FF0000": [[255, 0, 0], [0, 100, 50], "red"], + "FF00FF": [[255, 0, 255], [300, 100, 50], "magenta"], + "FF1493": [[255, 20, 147], [328, 100, 54], "deepPink"], + "FF4500": [[255, 69, 0], [16, 100, 50], "orangeRed"], + "FF6347": [[255, 99, 71], [9, 100, 64], "tomato"], + "FF69B4": [[255, 105, 180], [330, 100, 71], "hotPink"], + "FF7F50": [[255, 127, 80], [16, 100, 66], "coral"], + "FF8C00": [[255, 140, 0], [33, 100, 50], "darkOrange"], + "FFA07A": [[255, 160, 122], [17, 100, 74], "lightSalmon"], + "FFA500": [[255, 165, 0], [39, 100, 50], "orange"], + "FFB6C1": [[255, 182, 193], [351, 100, 86], "lightPink"], + "FFC0CB": [[255, 192, 203], [350, 100, 88], "pink"], + "FFD700": [[255, 215, 0], [51, 100, 50], "gold"], + "FFDAB9": [[255, 218, 185], [28, 100, 86], "peachPuff"], + "FFDEAD": [[255, 222, 173], [36, 100, 84], "navajoWhite"], + "FFE4B5": [[255, 228, 181], [38, 100, 85], "moccasin"], + "FFE4C4": [[255, 228, 196], [33, 100, 88], "bisque"], + "FFE4E1": [[255, 228, 225], [6, 100, 94], "mistyRose"], + "FFEBCD": [[255, 235, 205], [36, 100, 90], "blanchedAlmond"], + "FFEFD5": [[255, 239, 213], [37, 100, 92], "papayaWhip"], + "FFF0F5": [[255, 240, 245], [340, 100, 97], "lavenderBlush"], + "FFF5EE": [[255, 245, 238], [25, 100, 97], "seaShell"], + "FFF8DC": [[255, 248, 220], [48, 100, 93], "cornsilk"], + "FFFACD": [[255, 250, 205], [54, 100, 90], "lemonChiffon"], + "FFFAF0": [[255, 250, 240], [40, 100, 97], "floralWhite"], + "FFFAFA": [[255, 250, 250], [0, 100, 99], "snow"], + "FFFF00": [[255, 255, 0], [60, 100, 50], "yellow"], + "FFFFE0": [[255, 255, 224], [60, 100, 94], "lightYellow"], + "FFFFF0": [[255, 255, 240], [60, 100, 97], "ivory"], + "FFFFFF": [[255, 255, 255], [0, 100, 100], "white"] +}; + +// Simple Values +WebInspector.Color.Nicknames = { + "aliceblue": "F0F8FF", + "antiquewhite": "FAEBD7", + "aqua": "00FFFF", + "aquamarine": "7FFFD4", + "azure": "F0FFFF", + "beige": "F5F5DC", + "bisque": "FFE4C4", + "black": "000000", + "blanchedalmond": "FFEBCD", + "blue": "0000FF", + "blueviolet": "8A2BE2", + "brown": "A52A2A", + "burlywood": "DEB887", + "cadetblue": "5F9EA0", + "chartreuse": "7FFF00", + "chocolate": "D2691E", + "coral": "FF7F50", + "cornflowerblue": "6495ED", + "cornsilk": "FFF8DC", + "crimson": "DC143C", + "cyan": "00FFFF", + "darkblue": "00008B", + "darkcyan": "008B8B", + "darkgoldenrod": "B8860B", + "darkgray": "A9A9A9", + "darkgreen": "006400", + "darkkhaki": "BDB76B", + "darkmagenta": "8B008B", + "darkolivegreen": "556B2F", + "darkorange": "FF8C00", + "darkorchid": "9932CC", + "darkred": "8B0000", + "darksalmon": "E9967A", + "darkseagreen": "8FBC8F", + "darkslateblue": "483D8B", + "darkslategray": "2F4F4F", + "darkturquoise": "00CED1", + "darkviolet": "9400D3", + "deeppink": "FF1493", + "deepskyblue": "00BFFF", + "dimgray": "696969", + "dodgerblue": "1E90FF", + "firebrick": "B22222", + "floralwhite": "FFFAF0", + "forestgreen": "228B22", + "fuchsia": "FF00FF", + "gainsboro": "DCDCDC", + "ghostwhite": "F8F8FF", + "gold": "FFD700", + "goldenrod": "DAA520", + "gray": "808080", + "green": "008000", + "greenyellow": "ADFF2F", + "honeydew": "F0FFF0", + "hotpink": "FF69B4", + "indianred": "CD5C5C", + "indigo": "4B0082", + "ivory": "FFFFF0", + "khaki": "F0E68C", + "lavender": "E6E6FA", + "lavenderblush": "FFF0F5", + "lawngreen": "7CFC00", + "lemonchiffon": "FFFACD", + "lightblue": "ADD8E6", + "lightcoral": "F08080", + "lightcyan": "E0FFFF", + "lightgoldenrodyellow": "FAFAD2", + "lightgreen": "90EE90", + "lightgrey": "D3D3D3", + "lightpink": "FFB6C1", + "lightsalmon": "FFA07A", + "lightseagreen": "20B2AA", + "lightskyblue": "87CEFA", + "lightslategray": "778899", + "lightsteelblue": "B0C4DE", + "lightyellow": "FFFFE0", + "lime": "00FF00", + "limegreen": "32CD32", + "linen": "FAF0E6", + "magenta": "FF00FF", + "maroon": "800000", + "mediumaquamarine": "66CDAA", + "mediumblue": "0000CD", + "mediumorchid": "BA55D3", + "mediumpurple": "9370DB", + "mediumseagreen": "3CB371", + "mediumslateblue": "7B68EE", + "mediumspringgreen": "00FA9A", + "mediumturquoise": "48D1CC", + "mediumvioletred": "C71585", + "midnightblue": "191970", + "mintcream": "F5FFFA", + "mistyrose": "FFE4E1", + "moccasin": "FFE4B5", + "navajowhite": "FFDEAD", + "navy": "000080", + "oldlace": "FDF5E6", + "olive": "808000", + "olivedrab": "6B8E23", + "orange": "FFA500", + "orangered": "FF4500", + "orchid": "DA70D6", + "palegoldenrod": "EEE8AA", + "palegreen": "98FB98", + "paleturquoise": "AFEEEE", + "palevioletred": "DB7093", + "papayawhip": "FFEFD5", + "peachpuff": "FFDAB9", + "peru": "CD853F", + "pink": "FFC0CB", + "plum": "DDA0DD", + "powderblue": "B0E0E6", + "purple": "800080", + "red": "FF0000", + "rosybrown": "BC8F8F", + "royalblue": "4169E1", + "saddlebrown": "8B4513", + "salmon": "FA8072", + "sandybrown": "F4A460", + "seagreen": "2E8B57", + "seashell": "FFF5EE", + "sienna": "A0522D", + "silver": "C0C0C0", + "skyblue": "87CEEB", + "slateblue": "6A5ACD", + "slategray": "708090", + "snow": "FFFAFA", + "springgreen": "00FF7F", + "steelblue": "4682B4", + "tan": "D2B48C", + "teal": "008080", + "thistle": "D8BFD8", + "tomato": "FF6347", + "turquoise": "40E0D0", + "violet": "EE82EE", + "wheat": "F5DEB3", + "white": "FFFFFF", + "whitesmoke": "F5F5F5", + "yellow": "FFFF00", + "yellowgreen": "9ACD32" +}; + +// Advanced Values [rgba, hsla, nickname] +WebInspector.Color.AdvancedNickNames = { + "transparent": [[0, 0, 0, 0], [0, 0, 0, 0], "transparent"], + "rgba(0,0,0,0)": [[0, 0, 0, 0], [0, 0, 0, 0], "transparent"], + "hsla(0,0,0,0)": [[0, 0, 0, 0], [0, 0, 0, 0], "transparent"], +}; + +WebInspector.Color.PageHighlight = { + Content: WebInspector.Color.fromRGBA(111, 168, 220, .66), + ContentLight: WebInspector.Color.fromRGBA(111, 168, 220, .5), + ContentOutline: WebInspector.Color.fromRGBA(9, 83, 148), + Padding: WebInspector.Color.fromRGBA(147, 196, 125, .55), + PaddingLight: WebInspector.Color.fromRGBA(147, 196, 125, .4), + Border: WebInspector.Color.fromRGBA(255, 229, 153, .66), + BorderLight: WebInspector.Color.fromRGBA(255, 229, 153, .5), + Margin: WebInspector.Color.fromRGBA(246, 178, 107, .66), + MarginLight: WebInspector.Color.fromRGBA(246, 178, 107, .5) +} +/* CSSCompletions.js */ + +/* + * Copyright (C) 2010 Nikita Vasilyev. All rights reserved. + * Copyright (C) 2010 Joseph Pecoraro. All rights reserved. + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + */ +WebInspector.CSSCompletions = function(values, acceptEmptyPrefix) +{ + this._values = values.slice(); + this._values.sort(); + this._acceptEmptyPrefix = acceptEmptyPrefix; +} + + +/** + * @type {WebInspector.CSSCompletions} + */ +WebInspector.CSSCompletions.cssNameCompletions = null; + +WebInspector.CSSCompletions.requestCSSNameCompletions = function() +{ + function propertyNamesCallback(error, names) + { + if (!error) + WebInspector.CSSCompletions.cssNameCompletions = new WebInspector.CSSCompletions(names, false); + } + CSSAgent.getSupportedCSSProperties(propertyNamesCallback); +} + +WebInspector.CSSCompletions.prototype = { + startsWith: function(prefix) + { + var firstIndex = this._firstIndexOfPrefix(prefix); + if (firstIndex === -1) + return []; + + var results = []; + while (firstIndex < this._values.length && this._values[firstIndex].indexOf(prefix) === 0) + results.push(this._values[firstIndex++]); + return results; + }, + + firstStartsWith: function(prefix) + { + var foundIndex = this._firstIndexOfPrefix(prefix); + return (foundIndex === -1 ? "" : this._values[foundIndex]); + }, + + _firstIndexOfPrefix: function(prefix) + { + if (!this._values.length) + return -1; + if (!prefix) + return this._acceptEmptyPrefix ? 0 : -1; + + var maxIndex = this._values.length - 1; + var minIndex = 0; + var foundIndex; + + do { + var middleIndex = (maxIndex + minIndex) >> 1; + if (this._values[middleIndex].indexOf(prefix) === 0) { + foundIndex = middleIndex; + break; + } + if (this._values[middleIndex] < prefix) + minIndex = middleIndex + 1; + else + maxIndex = middleIndex - 1; + } while (minIndex <= maxIndex); + + if (foundIndex === undefined) + return -1; + + while (foundIndex && this._values[foundIndex - 1].indexOf(prefix) === 0) + foundIndex--; + + return foundIndex; + }, + + keySet: function() + { + if (!this._keySet) + this._keySet = this._values.keySet(); + return this._keySet; + }, + + next: function(str, prefix) + { + return this._closest(str, prefix, 1); + }, + + previous: function(str, prefix) + { + return this._closest(str, prefix, -1); + }, + + _closest: function(str, prefix, shift) + { + if (!str) + return ""; + + var index = this._values.indexOf(str); + if (index === -1) + return ""; + + if (!prefix) { + index = (index + this._values.length + shift) % this._values.length; + return this._values[index]; + } + + var propertiesWithPrefix = this.startsWith(prefix); + var j = propertiesWithPrefix.indexOf(str); + j = (j + propertiesWithPrefix.length + shift) % propertiesWithPrefix.length; + return propertiesWithPrefix[j]; + } +} +/* CSSKeywordCompletions.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.CSSKeywordCompletions = {} + +WebInspector.CSSKeywordCompletions.forProperty = function(propertyName) +{ + var acceptedKeywords = ["initial"]; + if (propertyName in WebInspector.CSSKeywordCompletions._propertyKeywordMap) + acceptedKeywords = acceptedKeywords.concat(WebInspector.CSSKeywordCompletions._propertyKeywordMap[propertyName]); + if (propertyName in WebInspector.CSSKeywordCompletions._colorAwareProperties) + acceptedKeywords = acceptedKeywords.concat(WebInspector.CSSKeywordCompletions._colors); + if (propertyName in WebInspector.CSSKeywordCompletions.InheritedProperties) + acceptedKeywords.push("inherit"); + return new WebInspector.CSSCompletions(acceptedKeywords, true); +} + +WebInspector.CSSKeywordCompletions.isColorAwareProperty = function(propertyName) +{ + return WebInspector.CSSKeywordCompletions._colorAwareProperties[propertyName] === true; +} + +WebInspector.CSSKeywordCompletions.colors = function() +{ + if (!WebInspector.CSSKeywordCompletions._colorsKeySet) + WebInspector.CSSKeywordCompletions._colorsKeySet = WebInspector.CSSKeywordCompletions._colors.keySet(); + return WebInspector.CSSKeywordCompletions._colorsKeySet; +} + +// Taken from http://www.w3.org/TR/CSS21/propidx.html. +WebInspector.CSSKeywordCompletions.InheritedProperties = [ + "azimuth", "border-collapse", "border-spacing", "caption-side", "color", "cursor", "direction", "elevation", + "empty-cells", "font-family", "font-size", "font-style", "font-variant", "font-weight", "font", "letter-spacing", + "line-height", "list-style-image", "list-style-position", "list-style-type", "list-style", "orphans", "pitch-range", + "pitch", "quotes", "richness", "speak-header", "speak-numeral", "speak-punctuation", "speak", "speech-rate", "stress", + "text-align", "text-indent", "text-transform", "text-shadow", "visibility", "voice-family", "volume", "white-space", "widows", "word-spacing" +].keySet(); + +WebInspector.CSSKeywordCompletions._colors = [ + "aqua", "black", "blue", "fuchsia", "gray", "green", "lime", "maroon", "navy", "olive", "orange", "purple", "red", + "silver", "teal", "white", "yellow", "transparent", "currentcolor", "grey", "aliceblue", "antiquewhite", + "aquamarine", "azure", "beige", "bisque", "blanchedalmond", "blueviolet", "brown", "burlywood", "cadetblue", + "chartreuse", "chocolate", "coral", "cornflowerblue", "cornsilk", "crimson", "cyan", "darkblue", "darkcyan", + "darkgoldenrod", "darkgray", "darkgreen", "darkgrey", "darkkhaki", "darkmagenta", "darkolivegreen", "darkorange", + "darkorchid", "darkred", "darksalmon", "darkseagreen", "darkslateblue", "darkslategray", "darkslategrey", + "darkturquoise", "darkviolet", "deeppink", "deepskyblue", "dimgray", "dimgrey", "dodgerblue", "firebrick", + "floralwhite", "forestgreen", "gainsboro", "ghostwhite", "gold", "goldenrod", "greenyellow", "honeydew", "hotpink", + "indianred", "indigo", "ivory", "khaki", "lavender", "lavenderblush", "lawngreen", "lemonchiffon", "lightblue", + "lightcoral", "lightcyan", "lightgoldenrodyellow", "lightgray", "lightgreen", "lightgrey", "lightpink", + "lightsalmon", "lightseagreen", "lightskyblue", "lightslategray", "lightslategrey", "lightsteelblue", "lightyellow", + "limegreen", "linen", "magenta", "mediumaquamarine", "mediumblue", "mediumorchid", "mediumpurple", "mediumseagreen", + "mediumslateblue", "mediumspringgreen", "mediumturquoise", "mediumvioletred", "midnightblue", "mintcream", + "mistyrose", "moccasin", "navajowhite", "oldlace", "olivedrab", "orangered", "orchid", "palegoldenrod", "palegreen", + "paleturquoise", "palevioletred", "papayawhip", "peachpuff", "peru", "pink", "plum", "powderblue", "rosybrown", + "royalblue", "saddlebrown", "salmon", "sandybrown", "seagreen", "seashell", "sienna", "skyblue", "slateblue", + "slategray", "slategrey", "snow", "springgreen", "steelblue", "tan", "thistle", "tomato", "turquoise", "violet", + "wheat", "whitesmoke", "yellowgreen" +]; + +WebInspector.CSSKeywordCompletions._colorAwareProperties = [ + "background", "background-color", "background-image", "border", "border-color", "border-top", "border-right", "border-bottom", + "border-left", "border-top-color", "border-right-color", "border-bottom-color", "border-left-color", "color", + "outline", "outline-color", "text-line-through", "text-line-through-color", "text-overline", "text-overline-color", + "text-shadow", "text-underline", "text-underline-color", "-webkit-text-emphasis", "-webkit-text-emphasis-color" +].keySet(); + +WebInspector.CSSKeywordCompletions._propertyKeywordMap = { + "table-layout": [ + "auto", "fixed" + ], + "visibility": [ + "hidden", "visible", "collapse" + ], + "background-repeat": [ + "repeat", "repeat-x", "repeat-y", "no-repeat", "space", "round" + ], + "text-underline": [ + "none", "dotted", "dashed", "solid", "double", "dot-dash", "dot-dot-dash", "wave" + ], + "content": [ + "list-item", "close-quote", "no-close-quote", "no-open-quote", "open-quote" + ], + "list-style-image": [ + "none" + ], + "clear": [ + "none", "left", "right", "both" + ], + "text-underline-mode": [ + "continuous", "skip-white-space" + ], + "overflow-x": [ + "hidden", "auto", "visible", "overlay", "scroll" + ], + "stroke-linejoin": [ + "round", "miter", "bevel" + ], + "baseline-shift": [ + "baseline", "sub", "super" + ], + "border-bottom-width": [ + "medium", "thick", "thin" + ], + "marquee-speed": [ + "normal", "slow", "fast" + ], + "margin-top-collapse": [ + "collapse", "separate", "discard" + ], + "max-height": [ + "none" + ], + "box-orient": [ + "horizontal", "vertical", "inline-axis", "block-axis" + ], + "font-stretch": [ + "normal", "wider", "narrower", "ultra-condensed", "extra-condensed", "condensed", "semi-condensed", + "semi-expanded", "expanded", "extra-expanded", "ultra-expanded" + ], + "-webkit-color-correction": [ + "default", "srgb" + ], + "text-underline-style": [ + "none", "dotted", "dashed", "solid", "double", "dot-dash", "dot-dot-dash", "wave" + ], + "text-overline-mode": [ + "continuous", "skip-white-space" + ], + "-webkit-background-composite": [ + "highlight", "clear", "copy", "source-over", "source-in", "source-out", "source-atop", "destination-over", + "destination-in", "destination-out", "destination-atop", "xor", "plus-darker", "plus-lighter" + ], + "border-left-width": [ + "medium", "thick", "thin" + ], + "-webkit-writing-mode": [ + "lr", "rl", "tb", "lr-tb", "rl-tb", "tb-rl", "horizontal-tb", "vertical-rl", "vertical-lr", "horizontal-bt" + ], + "text-line-through-mode": [ + "continuous", "skip-white-space" + ], + "border-collapse": [ + "collapse", "separate" + ], + "page-break-inside": [ + "auto", "avoid" + ], + "border-top-width": [ + "medium", "thick", "thin" + ], + "outline-color": [ + "invert" + ], + "text-line-through-style": [ + "none", "dotted", "dashed", "solid", "double", "dot-dash", "dot-dot-dash", "wave" + ], + "outline-style": [ + "none", "hidden", "inset", "groove", "ridge", "outset", "dotted", "dashed", "solid", "double" + ], + "cursor": [ + "none", "copy", "auto", "crosshair", "default", "pointer", "move", "vertical-text", "cell", "context-menu", + "alias", "progress", "no-drop", "not-allowed", "-webkit-zoom-in", "-webkit-zoom-out", "e-resize", "ne-resize", + "nw-resize", "n-resize", "se-resize", "sw-resize", "s-resize", "w-resize", "ew-resize", "ns-resize", + "nesw-resize", "nwse-resize", "col-resize", "row-resize", "text", "wait", "help", "all-scroll", "-webkit-grab", + "-webkit-grabbing" + ], + "border-width": [ + "medium", "thick", "thin" + ], + "size": [ + "a3", "a4", "a5", "b4", "b5", "landscape", "ledger", "legal", "letter", "portrait" + ], + "background-size": [ + "contain", "cover" + ], + "direction": [ + "ltr", "rtl" + ], + "marquee-direction": [ + "left", "right", "auto", "reverse", "forwards", "backwards", "ahead", "up", "down" + ], + "enable-background": [ + "accumulate", "new" + ], + "float": [ + "none", "left", "right" + ], + "overflow-y": [ + "hidden", "auto", "visible", "overlay", "scroll" + ], + "margin-bottom-collapse": [ + "collapse", "separate", "discard" + ], + "box-reflect": [ + "left", "right", "above", "below" + ], + "overflow": [ + "hidden", "auto", "visible", "overlay", "scroll" + ], + "text-rendering": [ + "auto", "optimizespeed", "optimizelegibility", "geometricprecision" + ], + "text-align": [ + "-webkit-auto", "left", "right", "center", "justify", "-webkit-left", "-webkit-right", "-webkit-center" + ], + "list-style-position": [ + "outside", "inside" + ], + "margin-bottom": [ + "auto" + ], + "color-interpolation": [ + "linearrgb" + ], + "background-origin": [ + "border-box", "content-box", "padding-box" + ], + "word-wrap": [ + "normal", "break-word" + ], + "font-weight": [ + "normal", "bold", "bolder", "lighter", "100", "200", "300", "400", "500", "600", "700", "800", "900" + ], + "margin-before-collapse": [ + "collapse", "separate", "discard" + ], + "text-overline-width": [ + "normal", "medium", "auto", "thick", "thin" + ], + "text-transform": [ + "none", "capitalize", "uppercase", "lowercase" + ], + "border-right-style": [ + "none", "hidden", "inset", "groove", "ridge", "outset", "dotted", "dashed", "solid", "double" + ], + "border-left-style": [ + "none", "hidden", "inset", "groove", "ridge", "outset", "dotted", "dashed", "solid", "double" + ], + "-webkit-text-emphasis": [ + "circle", "filled", "open", "dot", "double-circle", "triangle", "sesame" + ], + "font-style": [ + "italic", "oblique", "normal" + ], + "speak": [ + "none", "normal", "spell-out", "digits", "literal-punctuation", "no-punctuation" + ], + "text-line-through": [ + "none", "dotted", "dashed", "solid", "double", "dot-dash", "dot-dot-dash", "wave", "continuous", + "skip-white-space" + ], + "color-rendering": [ + "auto", "optimizespeed", "optimizequality" + ], + "list-style-type": [ + "none", "disc", "circle", "square", "decimal", "decimal-leading-zero", "arabic-indic", "binary", "bengali", + "cambodian", "khmer", "devanagari", "gujarati", "gurmukhi", "kannada", "lower-hexadecimal", "lao", "malayalam", + "mongolian", "myanmar", "octal", "oriya", "persian", "urdu", "telugu", "tibetan", "thai", "upper-hexadecimal", + "lower-roman", "upper-roman", "lower-greek", "lower-alpha", "lower-latin", "upper-alpha", "upper-latin", "afar", + "ethiopic-halehame-aa-et", "ethiopic-halehame-aa-er", "amharic", "ethiopic-halehame-am-et", "amharic-abegede", + "ethiopic-abegede-am-et", "cjk-earthly-branch", "cjk-heavenly-stem", "ethiopic", "ethiopic-halehame-gez", + "ethiopic-abegede", "ethiopic-abegede-gez", "hangul-consonant", "hangul", "lower-norwegian", "oromo", + "ethiopic-halehame-om-et", "sidama", "ethiopic-halehame-sid-et", "somali", "ethiopic-halehame-so-et", "tigre", + "ethiopic-halehame-tig", "tigrinya-er", "ethiopic-halehame-ti-er", "tigrinya-er-abegede", + "ethiopic-abegede-ti-er", "tigrinya-et", "ethiopic-halehame-ti-et", "tigrinya-et-abegede", + "ethiopic-abegede-ti-et", "upper-greek", "upper-norwegian", "asterisks", "footnotes", "hebrew", "armenian", + "lower-armenian", "upper-armenian", "georgian", "cjk-ideographic", "hiragana", "katakana", "hiragana-iroha", + "katakana-iroha" + ], + "-webkit-text-combine": [ + "none", "horizontal" + ], + "outline": [ + "none", "hidden", "inset", "groove", "ridge", "outset", "dotted", "dashed", "solid", "double" + ], + "font": [ + "caption", "icon", "menu", "message-box", "small-caption", "-webkit-mini-control", "-webkit-small-control", + "-webkit-control", "status-bar", "italic", "oblique", "small-caps", "normal", "bold", "bolder", "lighter", + "100", "200", "300", "400", "500", "600", "700", "800", "900", "xx-small", "x-small", "small", "medium", + "large", "x-large", "xx-large", "-webkit-xxx-large", "smaller", "larger", "serif", "sans-serif", "cursive", + "fantasy", "monospace", "-webkit-body", "-webkit-pictograph" + ], + "dominant-baseline": [ + "middle", "auto", "central", "text-before-edge", "text-after-edge", "ideographic", "alphabetic", "hanging", + "mathematical", "use-script", "no-change", "reset-size" + ], + "display": [ + "none", "inline", "block", "list-item", "run-in", "compact", "inline-block", "table", "inline-table", + "table-row-group", "table-header-group", "table-footer-group", "table-row", "table-column-group", + "table-column", "table-cell", "table-caption", "-webkit-box", "-webkit-inline-box", "-wap-marquee" + ], + "-webkit-text-emphasis-position": [ + "over", "under" + ], + "image-rendering": [ + "auto", "optimizespeed", "optimizequality" + ], + "alignment-baseline": [ + "baseline", "middle", "auto", "before-edge", "after-edge", "central", "text-before-edge", "text-after-edge", + "ideographic", "alphabetic", "hanging", "mathematical" + ], + "outline-width": [ + "medium", "thick", "thin" + ], + "text-line-through-width": [ + "normal", "medium", "auto", "thick", "thin" + ], + "box-align": [ + "baseline", "center", "stretch", "start", "end" + ], + "border-right-width": [ + "medium", "thick", "thin" + ], + "border-top-style": [ + "none", "hidden", "inset", "groove", "ridge", "outset", "dotted", "dashed", "solid", "double" + ], + "line-height": [ + "normal" + ], + "text-overflow": [ + "clip", "ellipsis" + ], + "box-direction": [ + "normal", "reverse" + ], + "margin-after-collapse": [ + "collapse", "separate", "discard" + ], + "page-break-before": [ + "left", "right", "auto", "always", "avoid" + ], + "-webkit-hyphens": [ + "none", "auto", "manual" + ], + "border-image": [ + "repeat", "stretch" + ], + "text-decoration": [ + "blink", "line-through", "overline", "underline" + ], + "position": [ + "absolute", "fixed", "relative", "static" + ], + "font-family": [ + "serif", "sans-serif", "cursive", "fantasy", "monospace", "-webkit-body", "-webkit-pictograph" + ], + "text-overflow-mode": [ + "clip", "ellipsis" + ], + "border-bottom-style": [ + "none", "hidden", "inset", "groove", "ridge", "outset", "dotted", "dashed", "solid", "double" + ], + "unicode-bidi": [ + "normal", "bidi-override", "embed" + ], + "clip-rule": [ + "nonzero", "evenodd" + ], + "margin-left": [ + "auto" + ], + "margin-top": [ + "auto" + ], + "zoom": [ + "document", "reset" + ], + "text-overline-style": [ + "none", "dotted", "dashed", "solid", "double", "dot-dash", "dot-dot-dash", "wave" + ], + "max-width": [ + "none" + ], + "empty-cells": [ + "hide", "show" + ], + "pointer-events": [ + "none", "all", "auto", "visible", "visiblepainted", "visiblefill", "visiblestroke", "painted", "fill", "stroke" + ], + "letter-spacing": [ + "normal" + ], + "background-clip": [ + "border-box", "content-box", "padding-box" + ], + "-webkit-font-smoothing": [ + "none", "auto", "antialiased", "subpixel-antialiased" + ], + "border": [ + "none", "hidden", "inset", "groove", "ridge", "outset", "dotted", "dashed", "solid", "double" + ], + "font-size": [ + "xx-small", "x-small", "small", "medium", "large", "x-large", "xx-large", "-webkit-xxx-large", "smaller", + "larger" + ], + "font-variant": [ + "small-caps", "normal" + ], + "vertical-align": [ + "baseline", "middle", "sub", "super", "text-top", "text-bottom", "top", "bottom", "-webkit-baseline-middle" + ], + "marquee-style": [ + "none", "scroll", "slide", "alternate" + ], + "white-space": [ + "normal", "nowrap", "pre", "pre-line", "pre-wrap" + ], + "text-underline-width": [ + "normal", "medium", "auto", "thick", "thin" + ], + "box-lines": [ + "single", "multiple" + ], + "page-break-after": [ + "left", "right", "auto", "always", "avoid" + ], + "clip-path": [ + "none" + ], + "margin": [ + "auto" + ], + "marquee-repetition": [ + "infinite" + ], + "margin-right": [ + "auto" + ], + "-webkit-text-emphasis-style": [ + "circle", "filled", "open", "dot", "double-circle", "triangle", "sesame" + ], + "-webkit-transform": [ + "scale", "scaleX", "scaleY", "scale3d", "rotate", "rotateX", "rotateY", "rotateZ", "rotate3d", "skew", "skewX", "skewY", + "translate", "translateX", "translateY", "translateZ", "translate3d", "matrix", "matrix3d", "perspective" + ] +} +/* StylesSidebarPane.js */ + +/* + * Copyright (C) 2007 Apple Inc. All rights reserved. + * Copyright (C) 2009 Joseph Pecoraro + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.SidebarPane} + */ +WebInspector.StylesSidebarPane = function(computedStylePane) +{ + WebInspector.SidebarPane.call(this, WebInspector.UIString("Styles")); + + this.settingsSelectElement = document.createElement("select"); + this.settingsSelectElement.className = "select-settings"; + + var option = document.createElement("option"); + option.value = WebInspector.StylesSidebarPane.ColorFormat.Original; + option.label = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "As authored" : "As Authored"); + this.settingsSelectElement.appendChild(option); + + option = document.createElement("option"); + option.value = WebInspector.StylesSidebarPane.ColorFormat.HEX; + option.label = WebInspector.UIString("Hex Colors"); + this.settingsSelectElement.appendChild(option); + + option = document.createElement("option"); + option.value = WebInspector.StylesSidebarPane.ColorFormat.RGB; + option.label = WebInspector.UIString("RGB Colors"); + this.settingsSelectElement.appendChild(option); + + option = document.createElement("option"); + option.value = WebInspector.StylesSidebarPane.ColorFormat.HSL; + option.label = WebInspector.UIString("HSL Colors"); + this.settingsSelectElement.appendChild(option); + + // Prevent section from collapsing. + var muteEventListener = function(event) { event.stopPropagation(); event.preventDefault(); }; + + this.settingsSelectElement.addEventListener("click", muteEventListener, true); + this.settingsSelectElement.addEventListener("change", this._changeSetting.bind(this), false); + this._updateColorFormatFilter(); + + this.titleElement.appendChild(this.settingsSelectElement); + + this._elementStateButton = document.createElement("button"); + this._elementStateButton.className = "pane-title-button element-state"; + this._elementStateButton.title = WebInspector.UIString("Toggle Element State"); + this._elementStateButton.addEventListener("click", this._toggleElementStatePane.bind(this), false); + this.titleElement.appendChild(this._elementStateButton); + + var addButton = document.createElement("button"); + addButton.className = "pane-title-button add"; + addButton.id = "add-style-button-test-id"; + addButton.title = WebInspector.UIString("New Style Rule"); + addButton.addEventListener("click", this._createNewRule.bind(this), false); + this.titleElement.appendChild(addButton); + + this._computedStylePane = computedStylePane; + this.element.addEventListener("contextmenu", this._contextMenuEventFired.bind(this), true); + WebInspector.settings.colorFormat.addChangeListener(this._colorFormatSettingChanged.bind(this)); + + this._createElementStatePane(); + this.bodyElement.appendChild(this._elementStatePane); + this._sectionsContainer = document.createElement("div"); + this.bodyElement.appendChild(this._sectionsContainer); + + WebInspector.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.StyleSheetChanged, this._styleSheetOrMediaQueryResultChanged, this); + WebInspector.cssModel.addEventListener(WebInspector.CSSStyleModel.Events.MediaQueryResultChanged, this._styleSheetOrMediaQueryResultChanged, this); + WebInspector.domAgent.addEventListener(WebInspector.DOMAgent.Events.AttrModified, this._attributesModified, this); + WebInspector.domAgent.addEventListener(WebInspector.DOMAgent.Events.AttrRemoved, this._attributesRemoved, this); + WebInspector.domAgent.addEventListener(WebInspector.DOMAgent.Events.StyleInvalidated, this._styleInvalidated, this); + WebInspector.settings.showUserAgentStyles.addChangeListener(this._showUserAgentStylesSettingChanged.bind(this)); +} + +WebInspector.StylesSidebarPane.ColorFormat = { + Original: "original", + Nickname: "nickname", + HEX: "hex", + ShortHEX: "shorthex", + RGB: "rgb", + RGBA: "rgba", + HSL: "hsl", + HSLA: "hsla" +} + +WebInspector.StylesSidebarPane.StyleValueDelimiters = " \xA0\t\n\"':;,/()"; + + +// Keep in sync with RenderStyleConstants.h PseudoId enum. Array below contains pseudo id names for corresponding enum indexes. +// First item is empty due to its artificial NOPSEUDO nature in the enum. +// FIXME: find a way of generating this mapping or getting it from combination of RenderStyleConstants and CSSSelector.cpp at +// runtime. +WebInspector.StylesSidebarPane.PseudoIdNames = [ + "", "first-line", "first-letter", "before", "after", "selection", "", "-webkit-scrollbar", "-webkit-file-upload-button", + "-webkit-input-placeholder", "-webkit-slider-thumb", "-webkit-search-cancel-button", "-webkit-search-decoration", + "-webkit-search-results-decoration", "-webkit-search-results-button", "-webkit-media-controls-panel", + "-webkit-media-controls-play-button", "-webkit-media-controls-mute-button", "-webkit-media-controls-timeline", + "-webkit-media-controls-timeline-container", "-webkit-media-controls-volume-slider", + "-webkit-media-controls-volume-slider-container", "-webkit-media-controls-current-time-display", + "-webkit-media-controls-time-remaining-display", "-webkit-media-controls-seek-back-button", "-webkit-media-controls-seek-forward-button", + "-webkit-media-controls-fullscreen-button", "-webkit-media-controls-rewind-button", "-webkit-media-controls-return-to-realtime-button", + "-webkit-media-controls-toggle-closed-captions-button", "-webkit-media-controls-status-display", "-webkit-scrollbar-thumb", + "-webkit-scrollbar-button", "-webkit-scrollbar-track", "-webkit-scrollbar-track-piece", "-webkit-scrollbar-corner", + "-webkit-resizer", "-webkit-input-list-button", "-webkit-inner-spin-button", "-webkit-outer-spin-button" +]; + +WebInspector.StylesSidebarPane.CSSNumberRegex = /^(-?(?:\d+(?:\.\d+)?|\.\d+))$/; + +WebInspector.StylesSidebarPane.alteredFloatNumber = function(number, event) +{ + var arrowKeyPressed = (event.keyIdentifier === "Up" || event.keyIdentifier === "Down"); + + // Jump by 10 when shift is down or jump by 0.1 when Alt/Option is down. + // Also jump by 10 for page up and down, or by 100 if shift is held with a page key. + var changeAmount = 1; + if (event.shiftKey && !arrowKeyPressed) + changeAmount = 100; + else if (event.shiftKey || !arrowKeyPressed) + changeAmount = 10; + else if (event.altKey) + changeAmount = 0.1; + + if (event.keyIdentifier === "Down" || event.keyIdentifier === "PageDown") + changeAmount *= -1; + + // Make the new number and constrain it to a precision of 6, this matches numbers the engine returns. + // Use the Number constructor to forget the fixed precision, so 1.100000 will print as 1.1. + var result = Number((number + changeAmount).toFixed(6)); + if (!String(result).match(WebInspector.StylesSidebarPane.CSSNumberRegex)) + return null; + + return result; +} + +WebInspector.StylesSidebarPane.alteredHexNumber = function(hexString, event) +{ + var number = parseInt(hexString, 16); + if (isNaN(number) || !isFinite(number)) + return hexString; + + var maxValue = Math.pow(16, hexString.length) - 1; + var arrowKeyPressed = (event.keyIdentifier === "Up" || event.keyIdentifier === "Down"); + + var delta; + if (arrowKeyPressed) + delta = (event.keyIdentifier === "Up") ? 1 : -1; + else + delta = (event.keyIdentifier === "PageUp") ? 16 : -16; + + if (event.shiftKey) + delta *= 16; + + var result = number + delta; + if (result < 0) + result = 0; // Color hex values are never negative, so clamp to 0. + else if (result > maxValue) + return hexString; + + // Ensure the result length is the same as the original hex value. + var resultString = result.toString(16).toUpperCase(); + for (var i = 0, lengthDelta = hexString.length - resultString.length; i < lengthDelta; ++i) + resultString = "0" + resultString; + return resultString; +}, + +WebInspector.StylesSidebarPane.prototype = { + _contextMenuEventFired: function(event) + { + var contextMenu = new WebInspector.ContextMenu(); + if (WebInspector.populateHrefContextMenu(contextMenu, this.node, event)) + contextMenu.show(event); + }, + + get forcedPseudoClasses() + { + return this._forcedPseudoClasses; + }, + + update: function(node, forceUpdate) + { + var refresh = false; + + if (forceUpdate) + delete this.node; + + if (!forceUpdate && (node === this.node)) + refresh = true; + + if (node && node.nodeType() === Node.TEXT_NODE && node.parentNode) + node = node.parentNode; + + if (node && node.nodeType() !== Node.ELEMENT_NODE) + node = null; + + if (node) + this.node = node; + else + node = this.node; + + this._innerUpdate(refresh); + }, + + _executeRebuildUpdate: function(node, callback) + { + var resultStyles = {}; + + function stylesCallback(matchedResult) + { + if (matchedResult) { + resultStyles.matchedCSSRules = matchedResult.matchedCSSRules; + resultStyles.pseudoElements = matchedResult.pseudoElements; + resultStyles.inherited = matchedResult.inherited; + this._rebuildUpdate(node, resultStyles); + } + if (callback) + callback(); + } + + function inlineCallback(inlineStyle, styleAttributes) + { + resultStyles.inlineStyle = inlineStyle; + resultStyles.styleAttributes = styleAttributes; + } + + function computedCallback(computedStyle) + { + resultStyles.computedStyle = computedStyle; + } + + WebInspector.cssModel.getComputedStyleAsync(node.id, this._forcedPseudoClasses, computedCallback.bind(this)); + WebInspector.cssModel.getInlineStylesAsync(node.id, inlineCallback.bind(this)); + WebInspector.cssModel.getMatchedStylesAsync(node.id, this._forcedPseudoClasses, true, true, stylesCallback.bind(this)); + }, + + /** + * @param {WebInspector.StylePropertiesSection=} editedSection + * @param {function()=} userCallback + */ + _innerUpdate: function(refresh, editedSection, userCallback) + { + var node = this.node; + if (!node) { + this._sectionsContainer.removeChildren(); + this._computedStylePane.bodyElement.removeChildren(); + this.sections = {}; + if (userCallback) + userCallback(); + return; + } + + function computedStyleCallback(computedStyle) + { + if (this.node === node && computedStyle) + this._refreshUpdate(node, computedStyle, editedSection); + if (userCallback) + userCallback(); + } + + if (refresh) + WebInspector.cssModel.getComputedStyleAsync(node.id, this._forcedPseudoClasses, computedStyleCallback.bind(this)); + else + this._executeRebuildUpdate(node, userCallback); + }, + + _styleSheetOrMediaQueryResultChanged: function() + { + if (this._userOperation || this._isEditingStyle) + return; + + this._innerUpdate(false); + }, + + _attributesModified: function(event) + { + if (this.node !== event.data.node) + return; + + // Changing style attribute will anyways generate _styleInvalidated message. + if (event.data.name === "style") + return; + + // "class" (or any other) attribute might have changed. Update styles unless they are being edited. + if (!this._isEditingStyle && !this._userOperation) + this._innerUpdate(false); + }, + + _attributesRemoved: function(event) + { + if (this.node !== event.data.node) + return; + + // "style" attribute might have been removed. + if (!this._isEditingStyle && !this._userOperation) + this._innerUpdate(false); + }, + + _styleInvalidated: function(event) + { + if (this.node !== event.data) + return; + + if (!this._isEditingStyle && !this._userOperation) + this._innerUpdate(false); + }, + + _refreshUpdate: function(node, computedStyle, editedSection) + { + for (var pseudoId in this.sections) { + var styleRules = this._refreshStyleRules(this.sections[pseudoId], computedStyle); + var usedProperties = {}; + var disabledComputedProperties = {}; + this._markUsedProperties(styleRules, usedProperties, disabledComputedProperties); + this._refreshSectionsForStyleRules(styleRules, usedProperties, disabledComputedProperties, editedSection); + } + // Trace the computed style. + this.sections[0][0].rebuildComputedTrace(this.sections[0]); + + this._nodeStylesUpdatedForTest(node, true); + }, + + _rebuildUpdate: function(node, styles) + { + this._sectionsContainer.removeChildren(); + this._computedStylePane.bodyElement.removeChildren(); + + var styleRules = this._rebuildStyleRules(node, styles); + var usedProperties = {}; + var disabledComputedProperties = {}; + this._markUsedProperties(styleRules, usedProperties, disabledComputedProperties); + this.sections[0] = this._rebuildSectionsForStyleRules(styleRules, usedProperties, disabledComputedProperties, 0, null); + var anchorElement = this.sections[0].inheritedPropertiesSeparatorElement; + // Trace the computed style. + this.sections[0][0].rebuildComputedTrace(this.sections[0]); + + for (var i = 0; i < styles.pseudoElements.length; ++i) { + var pseudoElementCSSRules = styles.pseudoElements[i]; + + styleRules = []; + var pseudoId = pseudoElementCSSRules.pseudoId; + + var entry = { isStyleSeparator: true, pseudoId: pseudoId }; + styleRules.push(entry); + + // Add rules in reverse order to match the cascade order. + for (var j = pseudoElementCSSRules.rules.length - 1; j >= 0; --j) { + var rule = pseudoElementCSSRules.rules[j]; + styleRules.push({ style: rule.style, selectorText: rule.selectorText, media: rule.media, sourceURL: rule.sourceURL, rule: rule, editable: !!(rule.style && rule.style.id) }); + } + usedProperties = {}; + disabledComputedProperties = {}; + this._markUsedProperties(styleRules, usedProperties, disabledComputedProperties); + this.sections[pseudoId] = this._rebuildSectionsForStyleRules(styleRules, usedProperties, disabledComputedProperties, pseudoId, anchorElement); + } + + this._nodeStylesUpdatedForTest(node, false); + }, + + _nodeStylesUpdatedForTest: function(node, refresh) + { + // Tests override this method. + }, + + _refreshStyleRules: function(sections, computedStyle) + { + var nodeComputedStyle = computedStyle; + var styleRules = []; + for (var i = 0; sections && i < sections.length; ++i) { + var section = sections[i]; + if (section.isBlank) + continue; + if (section.computedStyle) + section.styleRule.style = nodeComputedStyle; + var styleRule = { section: section, style: section.styleRule.style, computedStyle: section.computedStyle, rule: section.rule, editable: !!(section.styleRule.style && section.styleRule.style.id) }; + styleRules.push(styleRule); + } + return styleRules; + }, + + _rebuildStyleRules: function(node, styles) + { + var nodeComputedStyle = styles.computedStyle; + this.sections = {}; + + var styleRules = []; + + function addStyleAttributes() + { + for (var name in styles.styleAttributes) { + var attrStyle = { style: styles.styleAttributes[name], editable: false }; + attrStyle.selectorText = node.nodeNameInCorrectCase() + "[" + name; + if (node.getAttribute(name)) + attrStyle.selectorText += "=" + node.getAttribute(name); + attrStyle.selectorText += "]"; + styleRules.push(attrStyle); + } + } + + styleRules.push({ computedStyle: true, selectorText: "", style: nodeComputedStyle, editable: false }); + + // Inline style has the greatest specificity. + if (styles.inlineStyle && node.nodeType() === Node.ELEMENT_NODE) { + var inlineStyle = { selectorText: "element.style", style: styles.inlineStyle, isAttribute: true }; + styleRules.push(inlineStyle); + } + + // Add rules in reverse order to match the cascade order. + if (styles.matchedCSSRules.length) + styleRules.push({ isStyleSeparator: true, text: WebInspector.UIString("Matched CSS Rules") }); + var addedStyleAttributes; + for (var i = styles.matchedCSSRules.length - 1; i >= 0; --i) { + var rule = styles.matchedCSSRules[i]; + if (!WebInspector.settings.showUserAgentStyles.get() && (rule.isUser || rule.isUserAgent)) + continue; + if ((rule.isUser || rule.isUserAgent) && !addedStyleAttributes) { + // Show element's Style Attributes after all author rules. + addedStyleAttributes = true; + addStyleAttributes(); + } + styleRules.push({ style: rule.style, selectorText: rule.selectorText, media: rule.media, sourceURL: rule.sourceURL, rule: rule, editable: !!(rule.style && rule.style.id) }); + } + + if (!addedStyleAttributes) + addStyleAttributes(); + + // Walk the node structure and identify styles with inherited properties. + var parentNode = node.parentNode; + function insertInheritedNodeSeparator(node) + { + var entry = {}; + entry.isStyleSeparator = true; + entry.node = node; + styleRules.push(entry); + } + + for (var parentOrdinal = 0; parentOrdinal < styles.inherited.length; ++parentOrdinal) { + var parentStyles = styles.inherited[parentOrdinal]; + var separatorInserted = false; + if (parentStyles.inlineStyle) { + if (this._containsInherited(parentStyles.inlineStyle)) { + var inlineStyle = { selectorText: WebInspector.UIString("Style Attribute"), style: parentStyles.inlineStyle, isAttribute: true, isInherited: true }; + if (!separatorInserted) { + insertInheritedNodeSeparator(parentNode); + separatorInserted = true; + } + styleRules.push(inlineStyle); + } + } + + for (var i = parentStyles.matchedCSSRules.length - 1; i >= 0; --i) { + var rulePayload = parentStyles.matchedCSSRules[i]; + if (!this._containsInherited(rulePayload.style)) + continue; + var rule = rulePayload; + if (!WebInspector.settings.showUserAgentStyles.get() && (rule.isUser || rule.isUserAgent)) + continue; + + if (!separatorInserted) { + insertInheritedNodeSeparator(parentNode); + separatorInserted = true; + } + styleRules.push({ style: rule.style, selectorText: rule.selectorText, media: rule.media, sourceURL: rule.sourceURL, rule: rule, isInherited: true, editable: !!(rule.style && rule.style.id) }); + } + parentNode = parentNode.parentNode; + } + return styleRules; + }, + + _markUsedProperties: function(styleRules, usedProperties, disabledComputedProperties) + { + var priorityUsed = false; + + // Walk the style rules and make a list of all used and overloaded properties. + for (var i = 0; i < styleRules.length; ++i) { + var styleRule = styleRules[i]; + if (styleRule.computedStyle || styleRule.isStyleSeparator) + continue; + if (styleRule.section && styleRule.section.noAffect) + continue; + + styleRule.usedProperties = {}; + + var style = styleRule.style; + var allProperties = style.allProperties; + for (var j = 0; j < allProperties.length; ++j) { + var property = allProperties[j]; + if (!property.isLive || !property.parsedOk) + continue; + var name = property.name; + + if (!priorityUsed && property.priority.length) + priorityUsed = true; + + // If the property name is already used by another rule then this rule's + // property is overloaded, so don't add it to the rule's usedProperties. + if (!(name in usedProperties)) + styleRule.usedProperties[name] = true; + } + + // Add all the properties found in this style to the used properties list. + // Do this here so only future rules are affect by properties used in this rule. + for (var name in styleRules[i].usedProperties) + usedProperties[name] = true; + } + + if (priorityUsed) { + // Walk the properties again and account for !important. + var foundPriorityProperties = []; + + // Walk in direct order to detect the active/most specific rule providing a priority + // (in this case all subsequent !important values get canceled.) + for (var i = 0; i < styleRules.length; ++i) { + if (styleRules[i].computedStyle || styleRules[i].isStyleSeparator) + continue; + + var style = styleRules[i].style; + var allProperties = style.allProperties; + for (var j = 0; j < allProperties.length; ++j) { + var property = allProperties[j]; + if (!property.isLive) + continue; + var name = property.name; + if (property.priority.length) { + if (!(name in foundPriorityProperties)) + styleRules[i].usedProperties[name] = true; + else + delete styleRules[i].usedProperties[name]; + foundPriorityProperties[name] = true; + } else if (name in foundPriorityProperties) + delete styleRules[i].usedProperties[name]; + } + } + } + }, + + _refreshSectionsForStyleRules: function(styleRules, usedProperties, disabledComputedProperties, editedSection) + { + // Walk the style rules and update the sections with new overloaded and used properties. + for (var i = 0; i < styleRules.length; ++i) { + var styleRule = styleRules[i]; + var section = styleRule.section; + if (styleRule.computedStyle) { + section._disabledComputedProperties = disabledComputedProperties; + section._usedProperties = usedProperties; + section.update(); + } else { + section._usedProperties = styleRule.usedProperties; + section.update(section === editedSection); + } + } + }, + + _rebuildSectionsForStyleRules: function(styleRules, usedProperties, disabledComputedProperties, pseudoId, anchorElement) + { + // Make a property section for each style rule. + var sections = []; + var lastWasSeparator = true; + for (var i = 0; i < styleRules.length; ++i) { + var styleRule = styleRules[i]; + if (styleRule.isStyleSeparator) { + var separatorElement = document.createElement("div"); + separatorElement.className = "sidebar-separator"; + if (styleRule.node) { + var link = WebInspector.DOMPresentationUtils.linkifyNodeReference(styleRule.node); + separatorElement.appendChild(document.createTextNode(WebInspector.UIString("Inherited from") + " ")); + separatorElement.appendChild(link); + if (!sections.inheritedPropertiesSeparatorElement) + sections.inheritedPropertiesSeparatorElement = separatorElement; + } else if ("pseudoId" in styleRule) { + var pseudoName = WebInspector.StylesSidebarPane.PseudoIdNames[styleRule.pseudoId]; + if (pseudoName) + separatorElement.textContent = WebInspector.UIString("Pseudo ::%s element", pseudoName); + else + separatorElement.textContent = WebInspector.UIString("Pseudo element"); + } else + separatorElement.textContent = styleRule.text; + this._sectionsContainer.insertBefore(separatorElement, anchorElement); + lastWasSeparator = true; + continue; + } + var computedStyle = styleRule.computedStyle; + + // Default editable to true if it was omitted. + var editable = styleRule.editable; + if (typeof editable === "undefined") + editable = true; + + if (computedStyle) + var section = new WebInspector.ComputedStylePropertiesSection(styleRule, usedProperties, disabledComputedProperties); + else + var section = new WebInspector.StylePropertiesSection(this, styleRule, editable, styleRule.isInherited, lastWasSeparator); + section.pane = this; + section.expanded = true; + + if (computedStyle) { + this._computedStylePane.bodyElement.appendChild(section.element); + lastWasSeparator = true; + } else { + this._sectionsContainer.insertBefore(section.element, anchorElement); + lastWasSeparator = false; + } + sections.push(section); + } + return sections; + }, + + _containsInherited: function(style) + { + var properties = style.allProperties; + for (var i = 0; i < properties.length; ++i) { + var property = properties[i]; + // Does this style contain non-overridden inherited property? + if (property.isLive && property.name in WebInspector.CSSKeywordCompletions.InheritedProperties) + return true; + } + return false; + }, + + _colorFormatSettingChanged: function(event) + { + this._updateColorFormatFilter(); + for (var pseudoId in this.sections) { + var sections = this.sections[pseudoId]; + for (var i = 0; i < sections.length; ++i) + sections[i].update(true); + } + }, + + _updateColorFormatFilter: function() + { + // Select the correct color format setting again, since it needs to be selected. + var selectedIndex = 0; + var value = WebInspector.settings.colorFormat.get(); + var options = this.settingsSelectElement.options; + for (var i = 0; i < options.length; ++i) { + if (options[i].value === value) { + selectedIndex = i; + break; + } + } + this.settingsSelectElement.selectedIndex = selectedIndex; + }, + + _changeSetting: function(event) + { + var options = this.settingsSelectElement.options; + var selectedOption = options[this.settingsSelectElement.selectedIndex]; + WebInspector.settings.colorFormat.set(selectedOption.value); + }, + + _createNewRule: function(event) + { + event.stopPropagation(); + this.expanded = true; + this.addBlankSection().startEditingSelector(); + }, + + addBlankSection: function() + { + var blankSection = new WebInspector.BlankStylePropertiesSection(this, this.node ? this.node.appropriateSelectorFor(true) : ""); + blankSection.pane = this; + + var elementStyleSection = this.sections[0][1]; + this._sectionsContainer.insertBefore(blankSection.element, elementStyleSection.element.nextSibling); + + this.sections[0].splice(2, 0, blankSection); + + return blankSection; + }, + + removeSection: function(section) + { + for (var pseudoId in this.sections) { + var sections = this.sections[pseudoId]; + var index = sections.indexOf(section); + if (index === -1) + continue; + sections.splice(index, 1); + if (section.element.parentNode) + section.element.parentNode.removeChild(section.element); + } + }, + + registerShortcuts: function() + { + var section = WebInspector.shortcutsScreen.section(WebInspector.UIString("Styles Pane")); + var shortcut = WebInspector.KeyboardShortcut; + var keys = [ + shortcut.shortcutToString(shortcut.Keys.Tab), + shortcut.shortcutToString(shortcut.Keys.Tab, shortcut.Modifiers.Shift) + ]; + section.addRelatedKeys(keys, WebInspector.UIString("Next/previous property")); + keys = [ + shortcut.shortcutToString(shortcut.Keys.Up), + shortcut.shortcutToString(shortcut.Keys.Down) + ]; + section.addRelatedKeys(keys, WebInspector.UIString("Increment/decrement value")); + keys = [ + shortcut.shortcutToString(shortcut.Keys.Up, shortcut.Modifiers.Shift), + shortcut.shortcutToString(shortcut.Keys.Down, shortcut.Modifiers.Shift) + ]; + section.addRelatedKeys(keys, WebInspector.UIString("Increment/decrement by %f", 10)); + keys = [ + shortcut.shortcutToString(shortcut.Keys.PageUp), + shortcut.shortcutToString(shortcut.Keys.PageDown) + ]; + section.addRelatedKeys(keys, WebInspector.UIString("Increment/decrement by %f", 10)); + keys = [ + shortcut.shortcutToString(shortcut.Keys.PageUp, shortcut.Modifiers.Shift), + shortcut.shortcutToString(shortcut.Keys.PageDown, shortcut.Modifiers.Shift) + ]; + section.addRelatedKeys(keys, WebInspector.UIString("Increment/decrement by %f", 100)); + keys = [ + shortcut.shortcutToString(shortcut.Keys.PageUp, shortcut.Modifiers.Alt), + shortcut.shortcutToString(shortcut.Keys.PageDown, shortcut.Modifiers.Alt) + ]; + section.addRelatedKeys(keys, WebInspector.UIString("Increment/decrement by %f", 0.1)); + }, + + _toggleElementStatePane: function(event) + { + event.stopPropagation(); + if (!this._elementStateButton.hasStyleClass("toggled")) { + this.expand(); + this._elementStateButton.addStyleClass("toggled"); + this._elementStatePane.addStyleClass("expanded"); + } else { + this._elementStateButton.removeStyleClass("toggled"); + this._elementStatePane.removeStyleClass("expanded"); + // Clear flags on hide. + if (this._forcedPseudoClasses) { + for (var i = 0; i < this._elementStatePane.inputs.length; ++i) + this._elementStatePane.inputs[i].checked = false; + delete this._forcedPseudoClasses; + this._innerUpdate(false); + } + } + }, + + _createElementStatePane: function() + { + this._elementStatePane = document.createElement("div"); + this._elementStatePane.className = "styles-element-state-pane source-code"; + var table = document.createElement("table"); + + var inputs = []; + this._elementStatePane.inputs = inputs; + + function clickListener(event) + { + var pseudoClasses = []; + for (var i = 0; i < inputs.length; ++i) { + if (inputs[i].checked) + pseudoClasses.push(inputs[i].state); + } + this._forcedPseudoClasses = pseudoClasses.length ? pseudoClasses : undefined; + this._innerUpdate(false); + } + + function createCheckbox(state) + { + var td = document.createElement("td"); + var label = document.createElement("label"); + var input = document.createElement("input"); + input.type = "checkbox"; + input.state = state; + input.addEventListener("click", clickListener.bind(this), false); + inputs.push(input); + label.appendChild(input); + label.appendChild(document.createTextNode(":" + state)); + td.appendChild(label); + return td; + } + + var tr = document.createElement("tr"); + tr.appendChild(createCheckbox.call(this, "active")); + tr.appendChild(createCheckbox.call(this, "hover")); + table.appendChild(tr); + + tr = document.createElement("tr"); + tr.appendChild(createCheckbox.call(this, "focus")); + tr.appendChild(createCheckbox.call(this, "visited")); + table.appendChild(tr); + + this._elementStatePane.appendChild(table); + }, + + _showUserAgentStylesSettingChanged: function() + { + this._innerUpdate(false); + } +} + +WebInspector.StylesSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype; + +/** + * @constructor + * @extends {WebInspector.SidebarPane} + */ +WebInspector.ComputedStyleSidebarPane = function() +{ + WebInspector.SidebarPane.call(this, WebInspector.UIString("Computed Style")); + var showInheritedCheckbox = new WebInspector.Checkbox(WebInspector.UIString("Show inherited"), "sidebar-pane-subtitle"); + this.titleElement.appendChild(showInheritedCheckbox.element); + + if (WebInspector.settings.showInheritedComputedStyleProperties.get()) { + this.bodyElement.addStyleClass("show-inherited"); + showInheritedCheckbox.checked = true; + } + + function showInheritedToggleFunction(event) + { + WebInspector.settings.showInheritedComputedStyleProperties.set(showInheritedCheckbox.checked); + if (WebInspector.settings.showInheritedComputedStyleProperties.get()) + this.bodyElement.addStyleClass("show-inherited"); + else + this.bodyElement.removeStyleClass("show-inherited"); + } + + showInheritedCheckbox.addEventListener(showInheritedToggleFunction.bind(this)); +} + +WebInspector.ComputedStyleSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype; + +/** + * @constructor + * @extends {WebInspector.PropertiesSection} + */ +WebInspector.StylePropertiesSection = function(parentPane, styleRule, editable, isInherited, isFirstSection) +{ + WebInspector.PropertiesSection.call(this, ""); + this.element.className = "styles-section monospace" + (isFirstSection ? " first-styles-section" : ""); + + if (styleRule.media) { + for (var i = styleRule.media.length - 1; i >= 0; --i) { + var media = styleRule.media[i]; + var mediaDataElement = this.titleElement.createChild("div", "media"); + var mediaText; + switch (media.source) { + case WebInspector.CSSMedia.Source.LINKED_SHEET: + case WebInspector.CSSMedia.Source.INLINE_SHEET: + mediaText = "media=\"" + media.text + "\""; + break; + case WebInspector.CSSMedia.Source.MEDIA_RULE: + mediaText = "@media " + media.text; + break; + case WebInspector.CSSMedia.Source.IMPORT_RULE: + mediaText = "@import " + media.text; + break; + } + + if (media.sourceURL) { + var refElement = mediaDataElement.createChild("div", "subtitle"); + var anchor = WebInspector.linkifyResourceAsNode(media.sourceURL, media.sourceLine < 0 ? undefined : media.sourceLine, "subtitle"); + anchor.style.float = "right"; + refElement.appendChild(anchor); + } + + var mediaTextElement = mediaDataElement.createChild("span"); + mediaTextElement.textContent = mediaText; + mediaTextElement.title = media.text; + } + } + + var selectorContainer = document.createElement("div"); + this._selectorElement = document.createElement("span"); + this._selectorElement.textContent = styleRule.selectorText; + selectorContainer.appendChild(this._selectorElement); + + var openBrace = document.createElement("span"); + openBrace.textContent = " {"; + selectorContainer.appendChild(openBrace); + + var closeBrace = document.createElement("div"); + closeBrace.textContent = "}"; + this.element.appendChild(closeBrace); + + this._selectorElement.addEventListener("dblclick", this._handleSelectorDoubleClick.bind(this), false); + this.element.addEventListener("dblclick", this._handleEmptySpaceDoubleClick.bind(this), false); + + this._parentPane = parentPane; + this.styleRule = styleRule; + this.rule = this.styleRule.rule; + this.editable = editable; + this.isInherited = isInherited; + + if (this.rule) { + // Prevent editing the user agent and user rules. + if (this.rule.isUserAgent || this.rule.isUser) + this.editable = false; + this.titleElement.addStyleClass("styles-selector"); + } + + this._usedProperties = styleRule.usedProperties; + + this._selectorRefElement = document.createElement("div"); + this._selectorRefElement.className = "subtitle"; + this._selectorRefElement.appendChild(this._createRuleOriginNode()); + selectorContainer.insertBefore(this._selectorRefElement, selectorContainer.firstChild); + this.titleElement.appendChild(selectorContainer); + + if (isInherited) + this.element.addStyleClass("show-inherited"); // This one is related to inherited rules, not compted style. + + if (!this.editable) + this.element.addStyleClass("read-only"); +} + +WebInspector.StylePropertiesSection.prototype = { + collapse: function(dontRememberState) + { + // Overriding with empty body. + }, + + isPropertyInherited: function(propertyName) + { + if (this.isInherited) { + // While rendering inherited stylesheet, reverse meaning of this property. + // Render truly inherited properties with black, i.e. return them as non-inherited. + return !(propertyName in WebInspector.CSSKeywordCompletions.InheritedProperties); + } + return false; + }, + + isPropertyOverloaded: function(propertyName, shorthand) + { + if (!this._usedProperties || this.noAffect) + return false; + + if (this.isInherited && !(propertyName in WebInspector.CSSKeywordCompletions.InheritedProperties)) { + // In the inherited sections, only show overrides for the potentially inherited properties. + return false; + } + + var used = (propertyName in this._usedProperties); + if (used || !shorthand) + return !used; + + // Find out if any of the individual longhand properties of the shorthand + // are used, if none are then the shorthand is overloaded too. + var longhandProperties = this.styleRule.style.getLonghandProperties(propertyName); + for (var j = 0; j < longhandProperties.length; ++j) { + var individualProperty = longhandProperties[j]; + if (individualProperty.name in this._usedProperties) + return false; + } + + return true; + }, + + nextEditableSibling: function() + { + var curSection = this; + do { + curSection = curSection.nextSibling; + } while (curSection && !curSection.editable); + + if (!curSection) { + curSection = this.firstSibling; + while (curSection && !curSection.editable) + curSection = curSection.nextSibling; + } + + return (curSection && curSection.editable) ? curSection : null; + }, + + previousEditableSibling: function() + { + var curSection = this; + do { + curSection = curSection.previousSibling; + } while (curSection && !curSection.editable); + + if (!curSection) { + curSection = this.lastSibling; + while (curSection && !curSection.editable) + curSection = curSection.previousSibling; + } + + return (curSection && curSection.editable) ? curSection : null; + }, + + update: function(full) + { + if (full) { + this.propertiesTreeOutline.removeChildren(); + this.populated = false; + } else { + var child = this.propertiesTreeOutline.children[0]; + while (child) { + child.overloaded = this.isPropertyOverloaded(child.name, child.shorthand); + child = child.traverseNextTreeElement(false, null, true); + } + } + this.afterUpdate(); + }, + + afterUpdate: function() + { + if (this._afterUpdate) { + this._afterUpdate(this); + delete this._afterUpdate; + } + }, + + onpopulate: function() + { + var style = this.styleRule.style; + + var handledProperties = {}; + var shorthandNames = {}; + + this.uniqueProperties = []; + var allProperties = style.allProperties; + for (var i = 0; i < allProperties.length; ++i) + this.uniqueProperties.push(allProperties[i]); + + // Collect all shorthand names. + for (var i = 0; i < this.uniqueProperties.length; ++i) { + var property = this.uniqueProperties[i]; + if (property.disabled) + continue; + if (property.shorthand) + shorthandNames[property.shorthand] = true; + } + + // Create property tree elements. + for (var i = 0; i < this.uniqueProperties.length; ++i) { + var property = this.uniqueProperties[i]; + var disabled = property.disabled; + if (!disabled && this.disabledComputedProperties && !(property.name in this.usedProperties) && property.name in this.disabledComputedProperties) + disabled = true; + + var shorthand = !disabled ? property.shorthand : null; + + if (shorthand && shorthand in handledProperties) + continue; + + if (shorthand) { + property = style.getLiveProperty(shorthand); + if (!property) + property = new WebInspector.CSSProperty(style, style.allProperties.length, shorthand, style.getShorthandValue(shorthand), style.getShorthandPriority(shorthand), "style", true, true, "", undefined); + } + + // BUG71275: Never show purely style-based properties in editable rules. + if (!shorthand && this.editable && property.styleBased) + continue; + + var isShorthand = !!(property.isLive && (shorthand || shorthandNames[property.name])); + var inherited = this.isPropertyInherited(property.name); + var overloaded = this.isPropertyOverloaded(property.name, isShorthand); + + var item = new WebInspector.StylePropertyTreeElement(this._parentPane, this.styleRule, style, property, isShorthand, inherited, overloaded); + this.propertiesTreeOutline.appendChild(item); + handledProperties[property.name] = property; + } + }, + + findTreeElementWithName: function(name) + { + var treeElement = this.propertiesTreeOutline.children[0]; + while (treeElement) { + if (treeElement.name === name) + return treeElement; + treeElement = treeElement.traverseNextTreeElement(true, null, true); + } + return null; + }, + + /** + * @param {number=} optionalIndex + */ + addNewBlankProperty: function(optionalIndex) + { + var style = this.styleRule.style; + var property = style.newBlankProperty(); + var item = new WebInspector.StylePropertyTreeElement(this._parentPane, this.styleRule, style, property, false, false, false); + this.propertiesTreeOutline.appendChild(item); + item.listItemElement.textContent = ""; + item._newProperty = true; + item.updateTitle(); + return item; + }, + + _createRuleOriginNode: function() + { + function linkifyUncopyable(url, line) + { + var link = WebInspector.linkifyResourceAsNode(url, line); + link.classList.add("webkit-html-resource-link"); + link.setAttribute("data-uncopyable", link.textContent); + link.textContent = ""; + return link; + } + + if (this.styleRule.sourceURL) + return linkifyUncopyable(this.styleRule.sourceURL, this.rule.sourceLine); + if (!this.rule) + return document.createTextNode(""); + + var origin = ""; + if (this.rule.isUserAgent) + origin = WebInspector.UIString("user agent stylesheet"); + else if (this.rule.isUser) + origin = WebInspector.UIString("user stylesheet"); + else if (this.rule.isViaInspector) + origin = WebInspector.UIString("via inspector"); + return document.createTextNode(origin); + }, + + _handleEmptySpaceDoubleClick: function(event) + { + if (event.target.hasStyleClass("header") || this.element.hasStyleClass("read-only") || event.target.enclosingNodeOrSelfWithClass("media")) { + event.stopPropagation(); + return; + } + this.expand(); + this.addNewBlankProperty().startEditing(); + }, + + _handleSelectorClick: function(event) + { + event.stopPropagation(); + }, + + _handleSelectorDoubleClick: function(event) + { + this._startEditingOnMouseEvent(); + event.stopPropagation(); + }, + + _startEditingOnMouseEvent: function() + { + if (!this.editable) + return; + + if (!this.rule && this.propertiesTreeOutline.children.length === 0) { + this.expand(); + this.addNewBlankProperty().startEditing(); + return; + } + + if (!this.rule) + return; + + this.startEditingSelector(); + }, + + startEditingSelector: function() + { + var element = this._selectorElement; + if (WebInspector.isBeingEdited(element)) + return; + + this._selectorElement.scrollIntoViewIfNeeded(false); + + var config = new WebInspector.EditingConfig(this.editingSelectorCommitted.bind(this), this.editingSelectorCancelled.bind(this)); + WebInspector.startEditing(this._selectorElement, config); + + window.getSelection().setBaseAndExtent(element, 0, element, 1); + }, + + _moveEditorFromSelector: function(moveDirection) + { + if (!moveDirection) + return; + + if (moveDirection === "forward") { + this.expand(); + var firstChild = this.propertiesTreeOutline.children[0]; + if (!firstChild) + this.addNewBlankProperty().startEditing(); + else + firstChild.startEditing(firstChild.nameElement); + } else { + var previousSection = this.previousEditableSibling(); + if (!previousSection) + return; + + previousSection.expand(); + previousSection.addNewBlankProperty().startEditing(); + } + }, + + editingSelectorCommitted: function(element, newContent, oldContent, context, moveDirection) + { + if (newContent) + newContent = newContent.trim(); + if (newContent === oldContent) { + // Revert to a trimmed version of the selector if need be. + this._selectorElement.textContent = newContent; + return this._moveEditorFromSelector(moveDirection); + } + + function successCallback(newRule, doesAffectSelectedNode) + { + if (!doesAffectSelectedNode) { + this.noAffect = true; + this.element.addStyleClass("no-affect"); + } else { + delete this.noAffect; + this.element.removeStyleClass("no-affect"); + } + + this.rule = newRule; + this.styleRule = { section: this, style: newRule.style, selectorText: newRule.selectorText, media: newRule.media, sourceURL: newRule.sourceURL, rule: newRule }; + + this.pane.update(); + + this._moveEditorFromSelector(moveDirection); + } + + var selectedNode = WebInspector.panels.elements.selectedDOMNode(); + WebInspector.cssModel.setRuleSelector(this.rule.id, selectedNode ? selectedNode.id : 0, newContent, successCallback.bind(this), this._moveEditorFromSelector.bind(this, moveDirection)); + }, + + editingSelectorCancelled: function() + { + // Do nothing, this is overridden by BlankStylePropertiesSection. + } +} + +WebInspector.StylePropertiesSection.prototype.__proto__ = WebInspector.PropertiesSection.prototype; + +/** + * @constructor + * @extends {WebInspector.PropertiesSection} + */ +WebInspector.ComputedStylePropertiesSection = function(styleRule, usedProperties, disabledComputedProperties) +{ + WebInspector.PropertiesSection.call(this, ""); + this.headerElement.addStyleClass("hidden"); + this.element.className = "styles-section monospace first-styles-section read-only computed-style"; + this.styleRule = styleRule; + this._usedProperties = usedProperties; + this._disabledComputedProperties = disabledComputedProperties; + this._alwaysShowComputedProperties = { "display": true, "height": true, "width": true }; + this.computedStyle = true; + this._propertyTreeElements = {}; + this._expandedPropertyNames = {}; +} + +WebInspector.ComputedStylePropertiesSection.prototype = { + collapse: function(dontRememberState) + { + // Overriding with empty body. + }, + + _isPropertyInherited: function(propertyName) + { + return !(propertyName in this._usedProperties) && !(propertyName in this._alwaysShowComputedProperties) && !(propertyName in this._disabledComputedProperties); + }, + + update: function() + { + this._expandedPropertyNames = {}; + for (var name in this._propertyTreeElements) { + if (this._propertyTreeElements[name].expanded) + this._expandedPropertyNames[name] = true; + } + this._propertyTreeElements = {}; + this.propertiesTreeOutline.removeChildren(); + this.populated = false; + }, + + onpopulate: function() + { + function sorter(a, b) + { + return a.name.localeCompare(b.name); + } + + var style = this.styleRule.style; + var uniqueProperties = []; + var allProperties = style.allProperties; + for (var i = 0; i < allProperties.length; ++i) + uniqueProperties.push(allProperties[i]); + uniqueProperties.sort(sorter); + + this._propertyTreeElements = {}; + for (var i = 0; i < uniqueProperties.length; ++i) { + var property = uniqueProperties[i]; + var inherited = this._isPropertyInherited(property.name); + var item = new WebInspector.StylePropertyTreeElement(null, this.styleRule, style, property, false, inherited, false); + this.propertiesTreeOutline.appendChild(item); + this._propertyTreeElements[property.name] = item; + } + }, + + rebuildComputedTrace: function(sections) + { + for (var i = 0; i < sections.length; ++i) { + var section = sections[i]; + if (section.computedStyle || section.isBlank) + continue; + + for (var j = 0; j < section.uniqueProperties.length; ++j) { + var property = section.uniqueProperties[j]; + if (property.disabled) + continue; + if (section.isInherited && !(property.name in WebInspector.CSSKeywordCompletions.InheritedProperties)) + continue; + + var treeElement = this._propertyTreeElements[property.name]; + if (treeElement) { + var fragment = document.createDocumentFragment(); + var selector = fragment.createChild("span"); + selector.style.color = "gray"; + selector.textContent = section.styleRule.selectorText; + fragment.appendChild(document.createTextNode(" - " + property.value + " ")); + var subtitle = fragment.createChild("span"); + subtitle.style.float = "right"; + subtitle.appendChild(section._createRuleOriginNode()); + var childElement = new TreeElement(fragment, null, false); + treeElement.appendChild(childElement); + if (section.isPropertyOverloaded(property.name)) + childElement.listItemElement.addStyleClass("overloaded"); + if (!property.parsedOk) + childElement.listItemElement.addStyleClass("not-parsed-ok"); + } + } + } + + // Restore expanded state after update. + for (var name in this._expandedPropertyNames) { + if (name in this._propertyTreeElements) + this._propertyTreeElements[name].expand(); + } + } +} + +WebInspector.ComputedStylePropertiesSection.prototype.__proto__ = WebInspector.PropertiesSection.prototype; + +/** + * @constructor + * @extends {WebInspector.StylePropertiesSection} + */ +WebInspector.BlankStylePropertiesSection = function(parentPane, defaultSelectorText) +{ + WebInspector.StylePropertiesSection.call(this, parentPane, {selectorText: defaultSelectorText, rule: {isViaInspector: true}}, true, false, false); + this.element.addStyleClass("blank-section"); +} + +WebInspector.BlankStylePropertiesSection.prototype = { + get isBlank() + { + return !this._normal; + }, + + expand: function() + { + if (!this.isBlank) + WebInspector.StylePropertiesSection.prototype.expand.call(this); + }, + + editingSelectorCommitted: function(element, newContent, oldContent, context, moveDirection) + { + if (!this.isBlank) { + WebInspector.StylePropertiesSection.prototype.editingSelectorCommitted.call(this, element, newContent, oldContent, context, moveDirection); + return; + } + + function successCallback(newRule, doesSelectorAffectSelectedNode) + { + var styleRule = { section: this, style: newRule.style, selectorText: newRule.selectorText, sourceURL: newRule.sourceURL, rule: newRule }; + this.makeNormal(styleRule); + + if (!doesSelectorAffectSelectedNode) { + this.noAffect = true; + this.element.addStyleClass("no-affect"); + } + + this._selectorRefElement.textContent = WebInspector.UIString("via inspector"); + this.expand(); + if (this.element.parentElement) // Might have been detached already. + this._moveEditorFromSelector(moveDirection); + } + + WebInspector.cssModel.addRule(this.pane.node.id, newContent, successCallback.bind(this), this.editingSelectorCancelled.bind(this)); + }, + + editingSelectorCancelled: function() + { + if (!this.isBlank) { + WebInspector.StylePropertiesSection.prototype.editingSelectorCancelled.call(this); + return; + } + + this.pane.removeSection(this); + }, + + makeNormal: function(styleRule) + { + this.element.removeStyleClass("blank-section"); + this.styleRule = styleRule; + this.rule = styleRule.rule; + + // FIXME: replace this instance by a normal WebInspector.StylePropertiesSection. + this._normal = true; + } +} + +WebInspector.BlankStylePropertiesSection.prototype.__proto__ = WebInspector.StylePropertiesSection.prototype; + +/** + * @constructor + * @extends {TreeElement} + * @param {?WebInspector.StylesSidebarPane} parentPane + */ +WebInspector.StylePropertyTreeElement = function(parentPane, styleRule, style, property, shorthand, inherited, overloaded) +{ + this._parentPane = parentPane; + this._styleRule = styleRule; + this.style = style; + this.property = property; + this.shorthand = shorthand; + this._inherited = inherited; + this._overloaded = overloaded; + + // Pass an empty title, the title gets made later in onattach. + TreeElement.call(this, "", null, shorthand); +} + +WebInspector.StylePropertyTreeElement.prototype = { + get inherited() + { + return this._inherited; + }, + + set inherited(x) + { + if (x === this._inherited) + return; + this._inherited = x; + this.updateState(); + }, + + get overloaded() + { + return this._overloaded; + }, + + set overloaded(x) + { + if (x === this._overloaded) + return; + this._overloaded = x; + this.updateState(); + }, + + get disabled() + { + return this.property.disabled; + }, + + get name() + { + if (!this.disabled || !this.property.text) + return this.property.name; + + var text = this.property.text; + var index = text.indexOf(":"); + if (index < 1) + return this.property.name; + + return text.substring(0, index).trim(); + }, + + get priority() + { + if (this.disabled) + return ""; // rely upon raw text to render it in the value field + return this.property.priority; + }, + + get value() + { + if (!this.disabled || !this.property.text) + return this.property.value; + + var match = this.property.text.match(/(.*);\s*/); + if (!match || !match[1]) + return this.property.value; + + var text = match[1]; + var index = text.indexOf(":"); + if (index < 1) + return this.property.value; + + return text.substring(index + 1).trim(); + }, + + get parsedOk() + { + return this.property.parsedOk; + }, + + onattach: function() + { + this.updateTitle(); + }, + + updateTitle: function() + { + var value = this.value; + + this.updateState(); + + var enabledCheckboxElement; + if (this.parsedOk) { + enabledCheckboxElement = document.createElement("input"); + enabledCheckboxElement.className = "enabled-button"; + enabledCheckboxElement.type = "checkbox"; + enabledCheckboxElement.checked = !this.disabled; + enabledCheckboxElement.addEventListener("change", this.toggleEnabled.bind(this), false); + } + + var nameElement = document.createElement("span"); + nameElement.className = "webkit-css-property"; + nameElement.textContent = this.name; + this.nameElement = nameElement; + + var valueElement = document.createElement("span"); + valueElement.className = "value"; + this.valueElement = valueElement; + + var cf = WebInspector.StylesSidebarPane.ColorFormat; + + if (value) { + var self = this; + + function processValue(regex, processor, nextProcessor, valueText) + { + var container = document.createDocumentFragment(); + + var items = valueText.replace(regex, "\0$1\0").split("\0"); + for (var i = 0; i < items.length; ++i) { + if ((i % 2) === 0) { + if (nextProcessor) + container.appendChild(nextProcessor(items[i])); + else + container.appendChild(document.createTextNode(items[i])); + } else { + var processedNode = processor(items[i]); + if (processedNode) + container.appendChild(processedNode); + } + } + + return container; + } + + function linkifyURL(url) + { + var hrefUrl = url; + var match = hrefUrl.match(/['"]?([^'"]+)/); + if (match) + hrefUrl = match[1]; + var container = document.createDocumentFragment(); + container.appendChild(document.createTextNode("url(")); + if (self._styleRule.sourceURL) + hrefUrl = WebInspector.completeURL(self._styleRule.sourceURL, hrefUrl); + else if (WebInspector.panels.elements.selectedDOMNode()) + hrefUrl = WebInspector.resourceURLForRelatedNode(WebInspector.panels.elements.selectedDOMNode(), hrefUrl); + var hasResource = !!WebInspector.resourceForURL(hrefUrl); + // FIXME: WebInspector.linkifyURLAsNode() should really use baseURI. + container.appendChild(WebInspector.linkifyURLAsNode(hrefUrl, url, undefined, !hasResource)); + container.appendChild(document.createTextNode(")")); + return container; + } + + function processColor(text) + { + try { + var color = new WebInspector.Color(text); + } catch (e) { + return document.createTextNode(text); + } + + var swatchElement = document.createElement("span"); + swatchElement.title = WebInspector.UIString("Click to change color format"); + swatchElement.className = "swatch"; + swatchElement.style.setProperty("background-color", text); + + swatchElement.addEventListener("click", changeColorDisplay, false); + swatchElement.addEventListener("dblclick", function(event) { event.stopPropagation() }, false); + + var format; + var formatSetting = WebInspector.settings.colorFormat.get(); + if (formatSetting === cf.Original) + format = cf.Original; + else if (color.nickname) + format = cf.Nickname; + else if (formatSetting === cf.RGB) + format = (color.simple ? cf.RGB : cf.RGBA); + else if (formatSetting === cf.HSL) + format = (color.simple ? cf.HSL : cf.HSLA); + else if (color.simple) + format = (color.hasShortHex() ? cf.ShortHEX : cf.HEX); + else + format = cf.RGBA; + + var colorValueElement = document.createElement("span"); + colorValueElement.textContent = color.toString(format); + + function nextFormat(curFormat) + { + // The format loop is as follows: + // * original + // * rgb(a) + // * hsl(a) + // * nickname (if the color has a nickname) + // * if the color is simple: + // - shorthex (if has short hex) + // - hex + switch (curFormat) { + case cf.Original: + return color.simple ? cf.RGB : cf.RGBA; + + case cf.RGB: + case cf.RGBA: + return color.simple ? cf.HSL : cf.HSLA; + + case cf.HSL: + case cf.HSLA: + if (color.nickname) + return cf.Nickname; + if (color.simple) + return color.hasShortHex() ? cf.ShortHEX : cf.HEX; + else + return cf.Original; + + case cf.ShortHEX: + return cf.HEX; + + case cf.HEX: + return cf.Original; + + case cf.Nickname: + if (color.simple) + return color.hasShortHex() ? cf.ShortHEX : cf.HEX; + else + return cf.Original; + + default: + return null; + } + } + + function changeColorDisplay(event) + { + do { + format = nextFormat(format); + var currentValue = color.toString(format || ""); + } while (format && currentValue === color.value && format !== cf.Original); + + if (format) + colorValueElement.textContent = currentValue; + } + + var container = document.createDocumentFragment(); + container.appendChild(swatchElement); + container.appendChild(colorValueElement); + return container; + } + + var colorRegex = /((?:rgb|hsl)a?\([^)]+\)|#[0-9a-fA-F]{6}|#[0-9a-fA-F]{3}|\b\w+\b(?!-))/g; + var colorProcessor = processValue.bind(window, colorRegex, processColor, null); + + valueElement.appendChild(processValue(/url\(\s*([^)\s]+)\s*\)/g, linkifyURL, WebInspector.CSSKeywordCompletions.isColorAwareProperty(self.name) ? colorProcessor : null, value)); + } + + this.listItemElement.removeChildren(); + nameElement.normalize(); + valueElement.normalize(); + + if (!this.treeOutline) + return; + + // Append the checkbox for root elements of an editable section. + if (enabledCheckboxElement && this.treeOutline.section && this.treeOutline.section.editable && this.parent.root) + this.listItemElement.appendChild(enabledCheckboxElement); + this.listItemElement.appendChild(nameElement); + this.listItemElement.appendChild(document.createTextNode(": ")); + this.listItemElement.appendChild(valueElement); + this.listItemElement.appendChild(document.createTextNode(";")); + + if (!this.parsedOk) { + // Avoid having longhands under an invalid shorthand. + this.hasChildren = false; + this.listItemElement.addStyleClass("not-parsed-ok"); + + // Add a separate exclamation mark IMG element with a tooltip. + var exclamationElement = document.createElement("img"); + exclamationElement.className = "exclamation-mark"; + exclamationElement.title = WebInspector.CSSCompletions.cssNameCompletions.keySet()[this.property.name.toLowerCase()] ? WebInspector.UIString("Invalid property value.") : WebInspector.UIString("Unknown property name."); + this.listItemElement.insertBefore(exclamationElement, this.listItemElement.firstChild); + } + if (this.property.inactive) + this.listItemElement.addStyleClass("inactive"); + + this.tooltip = this.property.propertyText; + }, + + _updatePane: function(userCallback) + { + if (this.treeOutline && this.treeOutline.section && this.treeOutline.section.pane) + this.treeOutline.section.pane._innerUpdate(true, this.treeOutline.section, userCallback); + else { + if (userCallback) + userCallback(); + } + }, + + toggleEnabled: function(event) + { + var disabled = !event.target.checked; + + function callback(newStyle) + { + if (!newStyle) + return; + + this.style = newStyle; + this._styleRule.style = newStyle; + + if (this.treeOutline.section && this.treeOutline.section.pane) + this.treeOutline.section.pane.dispatchEventToListeners("style property toggled"); + + this._updatePane(); + + delete this._parentPane._userOperation; + } + + this._parentPane._userOperation = true; + this.property.setDisabled(disabled, callback.bind(this)); + }, + + updateState: function() + { + if (!this.listItemElement) + return; + + if (this.style.isPropertyImplicit(this.name) || this.value === "initial") + this.listItemElement.addStyleClass("implicit"); + else + this.listItemElement.removeStyleClass("implicit"); + + this.selectable = !this.inherited; + if (this.inherited) + this.listItemElement.addStyleClass("inherited"); + else + this.listItemElement.removeStyleClass("inherited"); + + if (this.overloaded) + this.listItemElement.addStyleClass("overloaded"); + else + this.listItemElement.removeStyleClass("overloaded"); + + if (this.disabled) + this.listItemElement.addStyleClass("disabled"); + else + this.listItemElement.removeStyleClass("disabled"); + }, + + onpopulate: function() + { + // Only populate once and if this property is a shorthand. + if (this.children.length || !this.shorthand) + return; + + var longhandProperties = this.style.getLonghandProperties(this.name); + for (var i = 0; i < longhandProperties.length; ++i) { + var name = longhandProperties[i].name; + + + if (this.treeOutline.section) { + var inherited = this.treeOutline.section.isPropertyInherited(name); + var overloaded = this.treeOutline.section.isPropertyOverloaded(name); + } + + var liveProperty = this.style.getLiveProperty(name); + var item = new WebInspector.StylePropertyTreeElement(this._parentPane, this._styleRule, this.style, liveProperty, false, inherited, overloaded); + this.appendChild(item); + } + }, + + ondblclick: function(event) + { + this.startEditing(event.target); + event.stopPropagation(); + }, + + restoreNameElement: function() + { + // Restore if it doesn't yet exist or was accidentally deleted. + if (this.nameElement === this.listItemElement.querySelector(".webkit-css-property")) + return; + + this.nameElement = document.createElement("span"); + this.nameElement.className = "webkit-css-property"; + this.nameElement.textContent = ""; + this.listItemElement.insertBefore(this.nameElement, this.listItemElement.firstChild); + }, + + startEditing: function(selectElement) + { + // FIXME: we don't allow editing of longhand properties under a shorthand right now. + if (this.parent.shorthand) + return; + + if (this.treeOutline.section && !this.treeOutline.section.editable) + return; + + if (!selectElement) + selectElement = this.nameElement; // No arguments passed in - edit the name element by default. + else + selectElement = selectElement.enclosingNodeOrSelfWithClass("webkit-css-property") || selectElement.enclosingNodeOrSelfWithClass("value"); + + var isEditingName = selectElement === this.nameElement; + if (!isEditingName && selectElement !== this.valueElement) { + // Double-click in the LI - start editing value. + isEditingName = false; + selectElement = this.valueElement; + } + + if (WebInspector.isBeingEdited(selectElement)) + return; + + var context = { + expanded: this.expanded, + hasChildren: this.hasChildren, + isEditingName: isEditingName, + previousContent: selectElement.textContent + }; + + // Lie about our children to prevent expanding on double click and to collapse shorthands. + this.hasChildren = false; + + if (selectElement.parentElement) + selectElement.parentElement.addStyleClass("child-editing"); + selectElement.textContent = selectElement.textContent; // remove color swatch and the like + + function pasteHandler(context, event) + { + var data = event.clipboardData.getData("Text"); + if (!data) + return; + var colonIdx = data.indexOf(":"); + if (colonIdx < 0) + return; + var name = data.substring(0, colonIdx).trim(); + var value = data.substring(colonIdx + 1).trim(); + + event.preventDefault(); + + if (!("originalName" in context)) { + context.originalName = this.nameElement.textContent; + context.originalValue = this.valueElement.textContent; + } + this.nameElement.textContent = name; + this.valueElement.textContent = value; + this.nameElement.normalize(); + this.valueElement.normalize(); + + this.editingCommitted(null, event.target.textContent, context.previousContent, context, "forward"); + } + + function blurListener(context, event) + { + this.editingCommitted(null, event.target.textContent, context.previousContent, context, ""); + } + + delete this.originalPropertyText; + + this._parentPane._isEditingStyle = true; + if (selectElement.parentElement) + selectElement.parentElement.scrollIntoViewIfNeeded(false); + + var applyItemCallback = !isEditingName ? this._applyFreeFlowStyleTextEdit.bind(this, true) : undefined; + this._prompt = new WebInspector.StylesSidebarPane.CSSPropertyPrompt(isEditingName ? WebInspector.CSSCompletions.cssNameCompletions : WebInspector.CSSKeywordCompletions.forProperty(this.nameElement.textContent), this, isEditingName); + if (applyItemCallback) { + this._prompt.addEventListener(WebInspector.TextPrompt.Events.ItemApplied, applyItemCallback, this); + this._prompt.addEventListener(WebInspector.TextPrompt.Events.ItemAccepted, applyItemCallback, this); + } + var proxyElement = this._prompt.attachAndStartEditing(selectElement, blurListener.bind(this, context)); + + proxyElement.addEventListener("keydown", this.editingNameValueKeyDown.bind(this, context), false); + if (isEditingName) + proxyElement.addEventListener("paste", pasteHandler.bind(this, context)); + + window.getSelection().setBaseAndExtent(selectElement, 0, selectElement, 1); + }, + + editingNameValueKeyDown: function(context, event) + { + if (event.handled) + return; + + var isEditingName = context.isEditingName; + var result; + + function shouldCommitValueSemicolon(text, cursorPosition) + { + // FIXME: should this account for semicolons inside comments? + var openQuote = ""; + for (var i = 0; i < cursorPosition; ++i) { + var ch = text[i]; + if (ch === "\\" && openQuote !== "") + ++i; // skip next character inside string + else if (!openQuote && (ch === "\"" || ch === "'")) + openQuote = ch; + else if (openQuote === ch) + openQuote = ""; + } + return !openQuote; + } + + // FIXME: the ":"/";" detection does not work for non-US layouts due to the event being keydown rather than keypress. + var isFieldInputTerminated = (event.keyCode === WebInspector.KeyboardShortcut.Keys.Semicolon.code) && + (isEditingName ? event.shiftKey : (!event.shiftKey && shouldCommitValueSemicolon(event.target.textContent, event.target.selectionLeftOffset()))); + if (isEnterKey(event) || isFieldInputTerminated) { + // Enter or colon (for name)/semicolon outside of string (for value). + event.preventDefault(); + result = "forward"; + } else if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code || event.keyIdentifier === "U+001B") + result = "cancel"; + else if (!isEditingName && this._newProperty && event.keyCode === WebInspector.KeyboardShortcut.Keys.Backspace.code) { + // For a new property, when Backspace is pressed at the beginning of new property value, move back to the property name. + var selection = window.getSelection(); + if (selection.isCollapsed && !selection.focusOffset) { + event.preventDefault(); + result = "backward"; + } + } else if (event.keyIdentifier === "U+0009") { // Tab key. + result = event.shiftKey ? "backward" : "forward"; + event.preventDefault(); + } + + if (result) { + switch (result) { + case "cancel": + this.editingCancelled(null, context); + break; + case "forward": + case "backward": + this.editingCommitted(null, event.target.textContent, context.previousContent, context, result); + break; + } + + event.stopPropagation(); + return; + } + + if (!isEditingName) + this._applyFreeFlowStyleTextEdit(false); + }, + + _applyFreeFlowStyleTextEdit: function(now) + { + if (this._applyFreeFlowStyleTextEditTimer) + clearTimeout(this._applyFreeFlowStyleTextEditTimer); + + function apply() + { + this.applyStyleText(this.nameElement.textContent + ": " + this.valueElement.textContent, false, false, false); + } + if (now) + apply.call(this); + else + this._applyFreeFlowStyleTextEditTimer = setTimeout(apply.bind(this), 100); + }, + + kickFreeFlowStyleEditForTest: function() + { + this._applyFreeFlowStyleTextEdit(true); + }, + + editingEnded: function(context) + { + if (this._applyFreeFlowStyleTextEditTimer) + clearTimeout(this._applyFreeFlowStyleTextEditTimer); + + this.hasChildren = context.hasChildren; + if (context.expanded) + this.expand(); + var editedElement = context.isEditingName ? this.nameElement : this.valueElement; + // The proxyElement has been deleted, no need to remove listener. + if (editedElement.parentElement) + editedElement.parentElement.removeStyleClass("child-editing"); + + delete this._parentPane._isEditingStyle; + }, + + editingCancelled: function(element, context) + { + this._removePrompt(); + this._revertStyleUponEditingCanceled(this.originalPropertyText); + // This should happen last, as it clears the info necessary to restore the property value after [Page]Up/Down changes. + this.editingEnded(context); + }, + + _revertStyleUponEditingCanceled: function(originalPropertyText) + { + if (typeof originalPropertyText === "string") { + delete this.originalPropertyText; + this.applyStyleText(originalPropertyText, true, false, true); + } else { + if (this._newProperty) + this.treeOutline.removeChild(this); + else + this.updateTitle(); + } + }, + + editingCommitted: function(element, userInput, previousContent, context, moveDirection) + { + this._removePrompt(); + this.editingEnded(context); + var isEditingName = context.isEditingName; + + // Determine where to move to before making changes + var createNewProperty, moveToPropertyName, moveToSelector; + var moveTo = this; + var moveToOther = (isEditingName ^ (moveDirection === "forward")); + var abandonNewProperty = this._newProperty && !userInput && (moveToOther || isEditingName); + if (moveDirection === "forward" && !isEditingName || moveDirection === "backward" && isEditingName) { + do { + moveTo = (moveDirection === "forward" ? moveTo.nextSibling : moveTo.previousSibling); + } while(moveTo && !moveTo.selectable); + + if (moveTo) + moveToPropertyName = moveTo.name; + else if (moveDirection === "forward" && (!this._newProperty || userInput)) + createNewProperty = true; + else if (moveDirection === "backward") + moveToSelector = true; + } + + // Make the Changes and trigger the moveToNextCallback after updating. + var blankInput = /^\s*$/.test(userInput); + var isDataPasted = "originalName" in context; + var isDirtyViaPaste = isDataPasted && (this.nameElement.textContent !== context.originalName || this.valueElement.textContent !== context.originalValue); + var shouldCommitNewProperty = this._newProperty && (moveToOther || (!moveDirection && !isEditingName) || (isEditingName && blankInput)); + if (((userInput !== previousContent || isDirtyViaPaste) && !this._newProperty) || shouldCommitNewProperty) { + this.treeOutline.section._afterUpdate = moveToNextCallback.bind(this, this._newProperty, !blankInput, this.treeOutline.section); + var propertyText; + if (blankInput || (this._newProperty && /^\s*$/.test(this.valueElement.textContent))) + propertyText = ""; + else { + if (isEditingName) + propertyText = userInput + ": " + this.valueElement.textContent; + else + propertyText = this.nameElement.textContent + ": " + userInput; + } + this.applyStyleText(propertyText, true, true, false); + } else { + if (!isDataPasted && !this._newProperty) + this.updateTitle(); + moveToNextCallback.call(this, this._newProperty, false, this.treeOutline.section); + } + + var moveToIndex = moveTo && this.treeOutline ? this.treeOutline.children.indexOf(moveTo) : -1; + + // The Callback to start editing the next/previous property/selector. + function moveToNextCallback(alreadyNew, valueChanged, section) + { + if (!moveDirection) + return; + + // User just tabbed through without changes. + if (moveTo && moveTo.parent) { + moveTo.startEditing(!isEditingName ? moveTo.nameElement : moveTo.valueElement); + return; + } + + // User has made a change then tabbed, wiping all the original treeElements. + // Recalculate the new treeElement for the same property we were going to edit next. + if (moveTo && !moveTo.parent) { + var propertyElements = section.propertiesTreeOutline.children; + if (moveDirection === "forward" && blankInput && !isEditingName) + --moveToIndex; + if (moveToIndex >= propertyElements.length && !this._newProperty) + createNewProperty = true; + else { + var treeElement = moveToIndex >= 0 ? propertyElements[moveToIndex] : null; + if (treeElement) { + treeElement.startEditing(!isEditingName ? treeElement.nameElement : treeElement.valueElement); + return; + } else if (!alreadyNew) + moveToSelector = true; + } + } + + // Create a new attribute in this section (or move to next editable selector if possible). + if (createNewProperty) { + if (alreadyNew && !valueChanged && (isEditingName ^ (moveDirection === "backward"))) + return; + + section.addNewBlankProperty().startEditing(); + return; + } + + if (abandonNewProperty) { + var sectionToEdit = moveDirection === "backward" ? section : section.nextEditableSibling(); + if (sectionToEdit) { + if (sectionToEdit.rule) + sectionToEdit.startEditingSelector(); + else + sectionToEdit._moveEditorFromSelector(moveDirection); + } + return; + } + + if (moveToSelector) { + if (section.rule) + section.startEditingSelector(); + else + section._moveEditorFromSelector(moveDirection); + } + } + }, + + _removePrompt: function() + { + // BUG 53242. This cannot go into editingEnded(), as it should always happen first for any editing outcome. + if (this._prompt) { + this._prompt.detach(); + delete this._prompt; + } + }, + + _hasBeenModifiedIncrementally: function() + { + // New properties applied via up/down or live editing have an originalPropertyText and will be deleted later + // on, if cancelled, when the empty string gets applied as their style text. + return typeof this.originalPropertyText === "string" || (!!this.property.propertyText && this._newProperty); + }, + + applyStyleText: function(styleText, updateInterface, majorChange, isRevert) + { + function userOperationFinishedCallback(parentPane, updateInterface) + { + if (updateInterface) + delete parentPane._userOperation; + } + + // Leave a way to cancel editing after incremental changes. + if (!isRevert && !updateInterface && !this._hasBeenModifiedIncrementally()) { + // Remember the rule's original CSS text on [Page](Up|Down), so it can be restored + // if the editing is canceled. + this.originalPropertyText = this.property.propertyText; + } + + if (!this.treeOutline) + return; + + var section = this.treeOutline.section; + var elementsPanel = WebInspector.panels.elements; + styleText = styleText.replace(/\s/g, " ").trim(); // Replace   with whitespace. + var styleTextLength = styleText.length; + if (!styleTextLength && updateInterface && !isRevert && this._newProperty && !this._hasBeenModifiedIncrementally()) { + // The user deleted everything and never applied a new property value via Up/Down scrolling/live editing, so remove the tree element and update. + this.parent.removeChild(this); + section.afterUpdate(); + return; + } + + var currentNode = this._parentPane.node; + if (updateInterface) + this._parentPane._userOperation = true; + + function callback(userCallback, originalPropertyText, newStyle) + { + if (!newStyle) { + if (updateInterface) { + // It did not apply, cancel editing. + this._revertStyleUponEditingCanceled(originalPropertyText); + } + userCallback(); + return; + } + + this.style = newStyle; + this.property = newStyle.propertyAt(this.property.index); + this._styleRule.style = this.style; + + if (section && section.pane) + section.pane.dispatchEventToListeners("style edited"); + + if (updateInterface && currentNode === section.pane.node) { + this._updatePane(userCallback); + return; + } + + userCallback(); + } + + // Append a ";" if the new text does not end in ";". + // FIXME: this does not handle trailing comments. + if (styleText.length && !/;\s*$/.test(styleText)) + styleText += ";"; + this.property.setText(styleText, majorChange, callback.bind(this, userOperationFinishedCallback.bind(null, this._parentPane, updateInterface), this.originalPropertyText)); + } +} + +WebInspector.StylePropertyTreeElement.prototype.__proto__ = TreeElement.prototype; + +/** + * @constructor + * @extends {WebInspector.TextPrompt} + * @param {function(*)=} acceptCallback + */ +WebInspector.StylesSidebarPane.CSSPropertyPrompt = function(cssCompletions, sidebarPane, isEditingName, acceptCallback) +{ + // Use the same callback both for applyItemCallback and acceptItemCallback. + WebInspector.TextPrompt.call(this, this._buildPropertyCompletions.bind(this), WebInspector.StylesSidebarPane.StyleValueDelimiters); + this.setSuggestBoxEnabled("generic-suggest"); + this._cssCompletions = cssCompletions; + this._sidebarPane = sidebarPane; + this._isEditingName = isEditingName; +} + +WebInspector.StylesSidebarPane.CSSPropertyPrompt.prototype = { + onKeyDown: function(event) + { + switch (event.keyIdentifier) { + case "Up": + case "Down": + case "PageUp": + case "PageDown": + if (this._handleNameOrValueUpDown(event)) { + event.preventDefault(); + return; + } + break; + } + + WebInspector.TextPrompt.prototype.onKeyDown.call(this, event); + }, + + tabKeyPressed: function() + { + this.acceptAutoComplete(); + + // Always tab to the next field. + return false; + }, + + _handleNameOrValueUpDown: function(event) + { + // Handle numeric value increment/decrement only at this point. + if (!this._isEditingName && this._handleUpOrDownValue(event)) + return true; + + return false; + }, + + _handleUpOrDownValue: function(event) + { + var arrowKeyPressed = (event.keyIdentifier === "Up" || event.keyIdentifier === "Down"); + var pageKeyPressed = (event.keyIdentifier === "PageUp" || event.keyIdentifier === "PageDown"); + if (!arrowKeyPressed && !pageKeyPressed) + return false; + + var selection = window.getSelection(); + if (!selection.rangeCount) + return false; + + var selectionRange = selection.getRangeAt(0); + if (!selectionRange.commonAncestorContainer.isSelfOrDescendant(this._sidebarPane.valueElement)) + return false; + + var wordRange = selectionRange.startContainer.rangeOfWord(selectionRange.startOffset, WebInspector.StylesSidebarPane.StyleValueDelimiters, this._sidebarPane.valueElement); + var wordString = wordRange.toString(); + var replacementString; + var prefix, suffix, number; + + var matches; + matches = /(.*#)([\da-fA-F]+)(.*)/.exec(wordString); + if (matches && matches.length) { + prefix = matches[1]; + suffix = matches[3]; + number = WebInspector.StylesSidebarPane.alteredHexNumber(matches[2], event); + + replacementString = prefix + number + suffix; + } else { + matches = /(.*?)(-?(?:\d+(?:\.\d+)?|\.\d+))(.*)/.exec(wordString); + if (matches && matches.length) { + prefix = matches[1]; + suffix = matches[3]; + number = WebInspector.StylesSidebarPane.alteredFloatNumber(parseFloat(matches[2]), event); + if (number === null) { + // Need to check for null explicitly. + return false; + } + + replacementString = prefix + number + suffix; + } + } + + if (replacementString) { + var replacementTextNode = document.createTextNode(replacementString); + + wordRange.deleteContents(); + wordRange.insertNode(replacementTextNode); + + var finalSelectionRange = document.createRange(); + finalSelectionRange.setStart(replacementTextNode, 0); + finalSelectionRange.setEnd(replacementTextNode, replacementString.length); + + selection.removeAllRanges(); + selection.addRange(finalSelectionRange); + + event.handled = true; + event.preventDefault(); + + // Synthesize property text disregarding any comments, custom whitespace etc. + this._sidebarPane.applyStyleText(this._sidebarPane.nameElement.textContent + ": " + this._sidebarPane.valueElement.textContent, false, false, false); + + return true; + } + return false; + }, + + _buildPropertyCompletions: function(wordRange, force, completionsReadyCallback) + { + var prefix = wordRange.toString().toLowerCase(); + if (!prefix && !force) + return; + + var results = this._cssCompletions.startsWith(prefix); + completionsReadyCallback(results); + } +} + +WebInspector.StylesSidebarPane.CSSPropertyPrompt.prototype.__proto__ = WebInspector.TextPrompt.prototype; +/* PanelEnablerView.js */ + +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @extends {WebInspector.View} + * @constructor + */ +WebInspector.PanelEnablerView = function(identifier, headingText, disclaimerText, buttonTitle) +{ + WebInspector.View.call(this); + this.registerRequiredCSS("panelEnablerView.css"); + + this.element.addStyleClass("panel-enabler-view"); + this.element.addStyleClass(identifier); + + this.contentElement = document.createElement("div"); + this.contentElement.className = "panel-enabler-view-content"; + this.element.appendChild(this.contentElement); + + this.imageElement = document.createElement("img"); + this.contentElement.appendChild(this.imageElement); + + this.choicesForm = document.createElement("form"); + this.contentElement.appendChild(this.choicesForm); + + this.headerElement = document.createElement("h1"); + this.headerElement.textContent = headingText; + this.choicesForm.appendChild(this.headerElement); + + var self = this; + function enableOption(text, checked) { + var label = document.createElement("label"); + var option = document.createElement("input"); + option.type = "radio"; + option.name = "enable-option"; + if (checked) + option.checked = true; + label.appendChild(option); + label.appendChild(document.createTextNode(text)); + self.choicesForm.appendChild(label); + return option; + }; + + this.enabledForSession = enableOption(WebInspector.UIString("Only enable for this session"), true); + this.enabledAlways = enableOption(WebInspector.UIString("Always enable"), false); + + this.disclaimerElement = document.createElement("div"); + this.disclaimerElement.className = "panel-enabler-disclaimer"; + this.disclaimerElement.textContent = disclaimerText; + this.choicesForm.appendChild(this.disclaimerElement); + + this.enableButton = document.createElement("button"); + this.enableButton.setAttribute("type", "button"); + this.enableButton.textContent = buttonTitle; + this.enableButton.addEventListener("click", this._enableButtonCicked.bind(this), false); + this.choicesForm.appendChild(this.enableButton); +} + +WebInspector.PanelEnablerView.prototype = { + _enableButtonCicked: function() + { + this.dispatchEventToListeners("enable clicked"); + }, + + onResize: function() + { + this.imageElement.removeStyleClass("hidden"); + + if (this.element.offsetWidth < (this.choicesForm.offsetWidth + this.imageElement.offsetWidth)) + this.imageElement.addStyleClass("hidden"); + }, + + get alwaysEnabled() { + return this.enabledAlways.checked; + } +} + +WebInspector.PanelEnablerView.prototype.__proto__ = WebInspector.View.prototype; +/* StatusBarButton.js */ + +/* + * Copyright (C) 2009 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @extends {WebInspector.Object} + * @constructor + * @param {number=} states + */ +WebInspector.StatusBarButton = function(title, className, states) +{ + this.element = document.createElement("button"); + this.element.className = className + " status-bar-item"; + this.element.addEventListener("click", this._clicked.bind(this), false); + + this.glyph = document.createElement("div"); + this.glyph.className = "glyph"; + this.element.appendChild(this.glyph); + + this.glyphShadow = document.createElement("div"); + this.glyphShadow.className = "glyph shadow"; + this.element.appendChild(this.glyphShadow); + + this.states = states; + if (!states) + this.states = 2; + + if (states == 2) + this._state = false; + else + this._state = 0; + + this.title = title; + this.disabled = false; + this._visible = true; +} + +WebInspector.StatusBarButton.prototype = { + _clicked: function() + { + this.dispatchEventToListeners("click"); + }, + + get disabled() + { + return this._disabled; + }, + + set disabled(x) + { + if (this._disabled === x) + return; + this._disabled = x; + this.element.disabled = x; + }, + + get title() + { + return this._title; + }, + + set title(x) + { + if (this._title === x) + return; + this._title = x; + this.element.title = x; + }, + + get state() + { + return this._state; + }, + + set state(x) + { + if (this._state === x) + return; + + if (this.states === 2) { + if (x) + this.element.addStyleClass("toggled-on"); + else + this.element.removeStyleClass("toggled-on"); + } else { + if (x !== 0) { + this.element.removeStyleClass("toggled-" + this._state); + this.element.addStyleClass("toggled-" + x); + } else + this.element.removeStyleClass("toggled-" + this._state); + } + this._state = x; + }, + + get toggled() + { + if (this.states !== 2) + throw("Only used toggled when there are 2 states, otherwise, use state"); + return this.state; + }, + + set toggled(x) + { + if (this.states !== 2) + throw("Only used toggled when there are 2 states, otherwise, use state"); + this.state = x; + }, + + get visible() + { + return this._visible; + }, + + set visible(x) + { + if (this._visible === x) + return; + + if (x) + this.element.removeStyleClass("hidden"); + else + this.element.addStyleClass("hidden"); + this._visible = x; + } +} + +WebInspector.StatusBarButton.prototype.__proto__ = WebInspector.Object.prototype; +/* ElementsPanel.js */ + +/* + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2008 Matt Lilek + * Copyright (C) 2009 Joseph Pecoraro + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.Panel} + */ +WebInspector.ElementsPanel = function() +{ + WebInspector.Panel.call(this, "elements"); + this.registerRequiredCSS("elementsPanel.css"); + this.registerRequiredCSS("textPrompt.css"); + this.setHideOnDetach(); + + const initialSidebarWidth = 325; + const minimalContentWidthPercent = 34; + this.createSplitView(this.element, WebInspector.SplitView.SidebarPosition.Right, initialSidebarWidth); + this.splitView.minimalSidebarWidth = Preferences.minElementsSidebarWidth; + this.splitView.minimalMainWidthPercent = minimalContentWidthPercent; + + this.contentElement = this.splitView.mainElement; + this.contentElement.id = "elements-content"; + this.contentElement.addStyleClass("outline-disclosure"); + this.contentElement.addStyleClass("source-code"); + if (!WebInspector.settings.domWordWrap.get()) + this.contentElement.classList.add("nowrap"); + WebInspector.settings.domWordWrap.addChangeListener(this._domWordWrapSettingChanged.bind(this)); + + this.contentElement.addEventListener("contextmenu", this._contextMenuEventFired.bind(this), true); + + this.treeOutline = new WebInspector.ElementsTreeOutline(true, true, false, this._populateContextMenu.bind(this)); + this.treeOutline.wireToDomAgent(); + + this.treeOutline.addEventListener(WebInspector.ElementsTreeOutline.Events.SelectedNodeChanged, this._selectedNodeChanged, this); + + this.crumbsElement = document.createElement("div"); + this.crumbsElement.className = "crumbs"; + this.crumbsElement.addEventListener("mousemove", this._mouseMovedInCrumbs.bind(this), false); + this.crumbsElement.addEventListener("mouseout", this._mouseMovedOutOfCrumbs.bind(this), false); + + this.sidebarPanes = {}; + this.sidebarPanes.computedStyle = new WebInspector.ComputedStyleSidebarPane(); + this.sidebarPanes.styles = new WebInspector.StylesSidebarPane(this.sidebarPanes.computedStyle); + this.sidebarPanes.metrics = new WebInspector.MetricsSidebarPane(); + this.sidebarPanes.properties = new WebInspector.PropertiesSidebarPane(); + if (Capabilities.nativeInstrumentationEnabled) + this.sidebarPanes.domBreakpoints = WebInspector.domBreakpointsSidebarPane; + this.sidebarPanes.eventListeners = new WebInspector.EventListenersSidebarPane(); + + this.sidebarPanes.styles.onexpand = this.updateStyles.bind(this); + this.sidebarPanes.metrics.onexpand = this.updateMetrics.bind(this); + this.sidebarPanes.properties.onexpand = this.updateProperties.bind(this); + this.sidebarPanes.eventListeners.onexpand = this.updateEventListeners.bind(this); + + this.sidebarPanes.styles.expanded = true; + + this.sidebarPanes.styles.addEventListener("style edited", this._stylesPaneEdited, this); + this.sidebarPanes.styles.addEventListener("style property toggled", this._stylesPaneEdited, this); + this.sidebarPanes.metrics.addEventListener("metrics edited", this._metricsPaneEdited, this); + + for (var pane in this.sidebarPanes) { + this.sidebarElement.appendChild(this.sidebarPanes[pane].element); + if (this.sidebarPanes[pane].onattach) + this.sidebarPanes[pane].onattach(); + } + + this.nodeSearchButton = new WebInspector.StatusBarButton(WebInspector.UIString("Select an element in the page to inspect it."), "node-search-status-bar-item"); + this.nodeSearchButton.addEventListener("click", this.toggleSearchingForNode, this); + + this._registerShortcuts(); + + WebInspector.domAgent.addEventListener(WebInspector.DOMAgent.Events.NodeRemoved, this._nodeRemoved, this); + WebInspector.domAgent.addEventListener(WebInspector.DOMAgent.Events.DocumentUpdated, this._documentUpdated, this); + WebInspector.domAgent.addEventListener(WebInspector.DOMAgent.Events.InspectElementRequested, this._inspectElementRequested, this); +} + +WebInspector.ElementsPanel.prototype = { + get toolbarItemLabel() + { + return WebInspector.UIString("Elements"); + }, + + get statusBarItems() + { + return [this.crumbsElement]; + }, + + get defaultFocusedElement() + { + return this.treeOutline.element; + }, + + statusBarResized: function() + { + this.updateBreadcrumbSizes(); + }, + + wasShown: function() + { + // Attach heavy component lazily + if (this.treeOutline.element.parentElement !== this.contentElement) + this.contentElement.appendChild(this.treeOutline.element); + + WebInspector.Panel.prototype.wasShown.call(this); + + this.updateBreadcrumb(); + this.treeOutline.updateSelection(); + this.treeOutline.setVisible(true); + + if (!this.treeOutline.rootDOMNode) + WebInspector.domAgent.requestDocument(); + + if (Capabilities.nativeInstrumentationEnabled) + this.sidebarElement.insertBefore(this.sidebarPanes.domBreakpoints.element, this.sidebarPanes.eventListeners.element); + }, + + willHide: function() + { + WebInspector.domAgent.hideDOMNodeHighlight(); + this.setSearchingForNode(false); + this.treeOutline.setVisible(false); + + // Detach heavy component on hide + this.contentElement.removeChild(this.treeOutline.element); + + WebInspector.Panel.prototype.willHide.call(this); + }, + + onResize: function() + { + this.treeOutline.updateSelection(); + this.updateBreadcrumbSizes(); + }, + + _selectedNodeChanged: function() + { + var selectedNode = this.selectedDOMNode(); + if (!selectedNode && this._lastValidSelectedNode) + this._selectedPathOnReset = this._lastValidSelectedNode.path(); + + this.updateBreadcrumb(false); + + for (var pane in this.sidebarPanes) + this.sidebarPanes[pane].needsUpdate = true; + + this.updateStyles(true); + this.updateMetrics(); + this.updateProperties(); + this.updateEventListeners(); + + if (selectedNode) { + ConsoleAgent.addInspectedNode(selectedNode.id); + this._lastValidSelectedNode = selectedNode; + } + }, + + _reset: function() + { + delete this.currentQuery; + }, + + _documentUpdated: function(event) + { + var inspectedRootDocument = event.data; + + this._reset(); + this.searchCanceled(); + + this.treeOutline.rootDOMNode = inspectedRootDocument; + + if (!inspectedRootDocument) { + if (this.isShowing()) + WebInspector.domAgent.requestDocument(); + return; + } + + if (Capabilities.nativeInstrumentationEnabled) + this.sidebarPanes.domBreakpoints.restoreBreakpoints(); + + /** + * @this {WebInspector.ElementsPanel} + * @param {WebInspector.DOMNode=} candidateFocusNode + */ + function selectNode(candidateFocusNode) + { + if (!candidateFocusNode) + candidateFocusNode = inspectedRootDocument.body || inspectedRootDocument.documentElement; + + if (!candidateFocusNode) + return; + + this.selectDOMNode(candidateFocusNode); + if (this.treeOutline.selectedTreeElement) + this.treeOutline.selectedTreeElement.expand(); + } + + function selectLastSelectedNode(nodeId) + { + if (this.selectedDOMNode()) { + // Focused node has been explicitly set while reaching out for the last selected node. + return; + } + var node = nodeId ? WebInspector.domAgent.nodeForId(nodeId) : null; + selectNode.call(this, node); + } + + if (this._selectedPathOnReset) + WebInspector.domAgent.pushNodeByPathToFrontend(this._selectedPathOnReset, selectLastSelectedNode.bind(this)); + else + selectNode.call(this); + delete this._selectedPathOnReset; + }, + + searchCanceled: function() + { + delete this._searchQuery; + this._hideSearchHighlights(); + + WebInspector.searchController.updateSearchMatchesCount(0, this); + + delete this._currentSearchResultIndex; + delete this._searchResults; + WebInspector.domAgent.cancelSearch(); + }, + + performSearch: function(query) + { + // Call searchCanceled since it will reset everything we need before doing a new search. + this.searchCanceled(); + + const whitespaceTrimmedQuery = query.trim(); + if (!whitespaceTrimmedQuery.length) + return; + + this._searchQuery = query; + + /** + * @param {number} resultCount + */ + function resultCountCallback(resultCount) + { + WebInspector.searchController.updateSearchMatchesCount(resultCount, this); + if (!resultCount) + return; + + this._searchResults = new Array(resultCount); + this._currentSearchResultIndex = -1; + this.jumpToNextSearchResult(); + } + WebInspector.domAgent.performSearch(whitespaceTrimmedQuery, resultCountCallback.bind(this)); + }, + + _contextMenuEventFired: function(event) + { + function toggleWordWrap() + { + WebInspector.settings.domWordWrap.set(!WebInspector.settings.domWordWrap.get()); + } + + var contextMenu = new WebInspector.ContextMenu(); + var populated = this.treeOutline.populateContextMenu(contextMenu, event); + if (populated) + contextMenu.appendSeparator(); + contextMenu.appendCheckboxItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Word wrap" : "Word Wrap"), toggleWordWrap.bind(this), WebInspector.settings.domWordWrap.get()); + + contextMenu.show(event); + }, + + _domWordWrapSettingChanged: function(event) + { + if (event.data) + this.contentElement.removeStyleClass("nowrap"); + else + this.contentElement.addStyleClass("nowrap"); + + var selectedNode = this.selectedDOMNode(); + if (!selectedNode) + return; + + var treeElement = this.treeOutline.findTreeElement(selectedNode); + if (treeElement) + treeElement.updateSelection(); // Recalculate selection highlight dimensions. + }, + + switchToAndFocus: function(node) + { + // Reset search restore. + WebInspector.searchController.cancelSearch(); + WebInspector.inspectorView.setCurrentPanel(this); + this.selectDOMNode(node, true); + }, + + _populateContextMenu: function(contextMenu, node) + { + if (Capabilities.nativeInstrumentationEnabled) { + // Add debbuging-related actions + contextMenu.appendSeparator(); + var pane = this.sidebarPanes.domBreakpoints; + pane.populateNodeContextMenu(node, contextMenu); + } + }, + + jumpToNextSearchResult: function() + { + if (!this._searchResults) + return; + + this._hideSearchHighlights(); + if (++this._currentSearchResultIndex >= this._searchResults.length) + this._currentSearchResultIndex = 0; + + this._highlightCurrentSearchResult(); + }, + + jumpToPreviousSearchResult: function() + { + if (!this._searchResults) + return; + + this._hideSearchHighlights(); + if (--this._currentSearchResultIndex < 0) + this._currentSearchResultIndex = (this._searchResults.length - 1); + + this._highlightCurrentSearchResult(); + }, + + _highlightCurrentSearchResult: function() + { + var index = this._currentSearchResultIndex; + var searchResults = this._searchResults; + var searchResult = searchResults[index]; + + if (searchResult === null) { + WebInspector.searchController.updateCurrentMatchIndex(index, this); + return; + } + + if (typeof searchResult === "undefined") { + // No data for slot, request it. + function callback(node) + { + searchResults[index] = node || null; + this._highlightCurrentSearchResult(); + } + WebInspector.domAgent.searchResult(index, callback.bind(this)); + return; + } + + WebInspector.searchController.updateCurrentMatchIndex(index, this); + + var treeElement = this.treeOutline.findTreeElement(searchResult); + if (treeElement) { + treeElement.highlightSearchResults(this._searchQuery); + treeElement.reveal(); + } + }, + + _hideSearchHighlights: function() + { + if (!this._searchResults) + return; + var searchResult = this._searchResults[this._currentSearchResultIndex]; + if (!searchResult) + return; + var treeElement = this.treeOutline.findTreeElement(searchResult); + if (treeElement) + treeElement.hideSearchHighlights(); + }, + + selectedDOMNode: function() + { + return this.treeOutline.selectedDOMNode(); + }, + + /** + * @param {boolean=} focus + */ + selectDOMNode: function(node, focus) + { + this.treeOutline.selectDOMNode(node, focus); + }, + + _nodeRemoved: function(event) + { + if (!this.isShowing()) + return; + + var crumbs = this.crumbsElement; + for (var crumb = crumbs.firstChild; crumb; crumb = crumb.nextSibling) { + if (crumb.representedObject === event.data.node) { + this.updateBreadcrumb(true); + return; + } + } + }, + + _stylesPaneEdited: function() + { + // Once styles are edited, the Metrics pane should be updated. + this.sidebarPanes.metrics.needsUpdate = true; + this.updateMetrics(); + }, + + _metricsPaneEdited: function() + { + // Once metrics are edited, the Styles pane should be updated. + this.sidebarPanes.styles.needsUpdate = true; + this.updateStyles(true); + }, + + _mouseMovedInCrumbs: function(event) + { + var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY); + var crumbElement = nodeUnderMouse.enclosingNodeOrSelfWithClass("crumb"); + + WebInspector.domAgent.highlightDOMNode(crumbElement ? crumbElement.representedObject.id : 0); + + if ("_mouseOutOfCrumbsTimeout" in this) { + clearTimeout(this._mouseOutOfCrumbsTimeout); + delete this._mouseOutOfCrumbsTimeout; + } + }, + + _mouseMovedOutOfCrumbs: function(event) + { + var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY); + if (nodeUnderMouse && nodeUnderMouse.isDescendant(this.crumbsElement)) + return; + + WebInspector.domAgent.hideDOMNodeHighlight(); + + this._mouseOutOfCrumbsTimeout = setTimeout(this.updateBreadcrumbSizes.bind(this), 1000); + }, + + /** + * @param {boolean=} forceUpdate + */ + updateBreadcrumb: function(forceUpdate) + { + if (!this.isShowing()) + return; + + var crumbs = this.crumbsElement; + + var handled = false; + var foundRoot = false; + var crumb = crumbs.firstChild; + while (crumb) { + if (crumb.representedObject === this.treeOutline.rootDOMNode) + foundRoot = true; + + if (foundRoot) + crumb.addStyleClass("dimmed"); + else + crumb.removeStyleClass("dimmed"); + + if (crumb.representedObject === this.selectedDOMNode()) { + crumb.addStyleClass("selected"); + handled = true; + } else { + crumb.removeStyleClass("selected"); + } + + crumb = crumb.nextSibling; + } + + if (handled && !forceUpdate) { + // We don't need to rebuild the crumbs, but we need to adjust sizes + // to reflect the new focused or root node. + this.updateBreadcrumbSizes(); + return; + } + + crumbs.removeChildren(); + + var panel = this; + + function selectCrumbFunction(event) + { + var crumb = event.currentTarget; + if (crumb.hasStyleClass("collapsed")) { + // Clicking a collapsed crumb will expose the hidden crumbs. + if (crumb === panel.crumbsElement.firstChild) { + // If the focused crumb is the first child, pick the farthest crumb + // that is still hidden. This allows the user to expose every crumb. + var currentCrumb = crumb; + while (currentCrumb) { + var hidden = currentCrumb.hasStyleClass("hidden"); + var collapsed = currentCrumb.hasStyleClass("collapsed"); + if (!hidden && !collapsed) + break; + crumb = currentCrumb; + currentCrumb = currentCrumb.nextSibling; + } + } + + panel.updateBreadcrumbSizes(crumb); + } else + panel.selectDOMNode(crumb.representedObject, true); + + event.preventDefault(); + } + + foundRoot = false; + for (var current = this.selectedDOMNode(); current; current = current.parentNode) { + if (current.nodeType() === Node.DOCUMENT_NODE) + continue; + + if (current === this.treeOutline.rootDOMNode) + foundRoot = true; + + crumb = document.createElement("span"); + crumb.className = "crumb"; + crumb.representedObject = current; + crumb.addEventListener("mousedown", selectCrumbFunction, false); + + var crumbTitle; + switch (current.nodeType()) { + case Node.ELEMENT_NODE: + WebInspector.DOMPresentationUtils.decorateNodeLabel(current, crumb); + break; + + case Node.TEXT_NODE: + crumbTitle = WebInspector.UIString("(text)"); + break + + case Node.COMMENT_NODE: + crumbTitle = ""; + break; + + case Node.DOCUMENT_TYPE_NODE: + crumbTitle = ""; + break; + + default: + crumbTitle = current.nodeNameInCorrectCase(); + } + + if (!crumb.childNodes.length) { + var nameElement = document.createElement("span"); + nameElement.textContent = crumbTitle; + crumb.appendChild(nameElement); + crumb.title = crumbTitle; + } + + if (foundRoot) + crumb.addStyleClass("dimmed"); + if (current === this.selectedDOMNode()) + crumb.addStyleClass("selected"); + if (!crumbs.childNodes.length) + crumb.addStyleClass("end"); + + crumbs.appendChild(crumb); + } + + if (crumbs.hasChildNodes()) + crumbs.lastChild.addStyleClass("start"); + + this.updateBreadcrumbSizes(); + }, + + /** + * @param {Element=} focusedCrumb + */ + updateBreadcrumbSizes: function(focusedCrumb) + { + if (!this.isShowing()) + return; + + if (document.body.offsetWidth <= 0) { + // The stylesheet hasn't loaded yet or the window is closed, + // so we can't calculate what is need. Return early. + return; + } + + var crumbs = this.crumbsElement; + if (!crumbs.childNodes.length || crumbs.offsetWidth <= 0) + return; // No crumbs, do nothing. + + // A Zero index is the right most child crumb in the breadcrumb. + var selectedIndex = 0; + var focusedIndex = 0; + var selectedCrumb; + + var i = 0; + var crumb = crumbs.firstChild; + while (crumb) { + // Find the selected crumb and index. + if (!selectedCrumb && crumb.hasStyleClass("selected")) { + selectedCrumb = crumb; + selectedIndex = i; + } + + // Find the focused crumb index. + if (crumb === focusedCrumb) + focusedIndex = i; + + // Remove any styles that affect size before + // deciding to shorten any crumbs. + if (crumb !== crumbs.lastChild) + crumb.removeStyleClass("start"); + if (crumb !== crumbs.firstChild) + crumb.removeStyleClass("end"); + + crumb.removeStyleClass("compact"); + crumb.removeStyleClass("collapsed"); + crumb.removeStyleClass("hidden"); + + crumb = crumb.nextSibling; + ++i; + } + + // Restore the start and end crumb classes in case they got removed in coalesceCollapsedCrumbs(). + // The order of the crumbs in the document is opposite of the visual order. + crumbs.firstChild.addStyleClass("end"); + crumbs.lastChild.addStyleClass("start"); + + function crumbsAreSmallerThanContainer() + { + var rightPadding = 20; + var errorWarningElement = document.getElementById("error-warning-count"); + if (!WebInspector.drawer.visible && errorWarningElement) + rightPadding += errorWarningElement.offsetWidth; + return ((crumbs.totalOffsetLeft() + crumbs.offsetWidth + rightPadding) < window.innerWidth); + } + + if (crumbsAreSmallerThanContainer()) + return; // No need to compact the crumbs, they all fit at full size. + + var BothSides = 0; + var AncestorSide = -1; + var ChildSide = 1; + + /** + * @param {boolean=} significantCrumb + */ + function makeCrumbsSmaller(shrinkingFunction, direction, significantCrumb) + { + if (!significantCrumb) + significantCrumb = (focusedCrumb || selectedCrumb); + + if (significantCrumb === selectedCrumb) + var significantIndex = selectedIndex; + else if (significantCrumb === focusedCrumb) + var significantIndex = focusedIndex; + else { + var significantIndex = 0; + for (var i = 0; i < crumbs.childNodes.length; ++i) { + if (crumbs.childNodes[i] === significantCrumb) { + significantIndex = i; + break; + } + } + } + + function shrinkCrumbAtIndex(index) + { + var shrinkCrumb = crumbs.childNodes[index]; + if (shrinkCrumb && shrinkCrumb !== significantCrumb) + shrinkingFunction(shrinkCrumb); + if (crumbsAreSmallerThanContainer()) + return true; // No need to compact the crumbs more. + return false; + } + + // Shrink crumbs one at a time by applying the shrinkingFunction until the crumbs + // fit in the container or we run out of crumbs to shrink. + if (direction) { + // Crumbs are shrunk on only one side (based on direction) of the signifcant crumb. + var index = (direction > 0 ? 0 : crumbs.childNodes.length - 1); + while (index !== significantIndex) { + if (shrinkCrumbAtIndex(index)) + return true; + index += (direction > 0 ? 1 : -1); + } + } else { + // Crumbs are shrunk in order of descending distance from the signifcant crumb, + // with a tie going to child crumbs. + var startIndex = 0; + var endIndex = crumbs.childNodes.length - 1; + while (startIndex != significantIndex || endIndex != significantIndex) { + var startDistance = significantIndex - startIndex; + var endDistance = endIndex - significantIndex; + if (startDistance >= endDistance) + var index = startIndex++; + else + var index = endIndex--; + if (shrinkCrumbAtIndex(index)) + return true; + } + } + + // We are not small enough yet, return false so the caller knows. + return false; + } + + function coalesceCollapsedCrumbs() + { + var crumb = crumbs.firstChild; + var collapsedRun = false; + var newStartNeeded = false; + var newEndNeeded = false; + while (crumb) { + var hidden = crumb.hasStyleClass("hidden"); + if (!hidden) { + var collapsed = crumb.hasStyleClass("collapsed"); + if (collapsedRun && collapsed) { + crumb.addStyleClass("hidden"); + crumb.removeStyleClass("compact"); + crumb.removeStyleClass("collapsed"); + + if (crumb.hasStyleClass("start")) { + crumb.removeStyleClass("start"); + newStartNeeded = true; + } + + if (crumb.hasStyleClass("end")) { + crumb.removeStyleClass("end"); + newEndNeeded = true; + } + + continue; + } + + collapsedRun = collapsed; + + if (newEndNeeded) { + newEndNeeded = false; + crumb.addStyleClass("end"); + } + } else + collapsedRun = true; + crumb = crumb.nextSibling; + } + + if (newStartNeeded) { + crumb = crumbs.lastChild; + while (crumb) { + if (!crumb.hasStyleClass("hidden")) { + crumb.addStyleClass("start"); + break; + } + crumb = crumb.previousSibling; + } + } + } + + function compact(crumb) + { + if (crumb.hasStyleClass("hidden")) + return; + crumb.addStyleClass("compact"); + } + + function collapse(crumb, dontCoalesce) + { + if (crumb.hasStyleClass("hidden")) + return; + crumb.addStyleClass("collapsed"); + crumb.removeStyleClass("compact"); + if (!dontCoalesce) + coalesceCollapsedCrumbs(); + } + + function compactDimmed(crumb) + { + if (crumb.hasStyleClass("dimmed")) + compact(crumb); + } + + function collapseDimmed(crumb) + { + if (crumb.hasStyleClass("dimmed")) + collapse(crumb, false); + } + + if (!focusedCrumb) { + // When not focused on a crumb we can be biased and collapse less important + // crumbs that the user might not care much about. + + // Compact child crumbs. + if (makeCrumbsSmaller(compact, ChildSide)) + return; + + // Collapse child crumbs. + if (makeCrumbsSmaller(collapse, ChildSide)) + return; + + // Compact dimmed ancestor crumbs. + if (makeCrumbsSmaller(compactDimmed, AncestorSide)) + return; + + // Collapse dimmed ancestor crumbs. + if (makeCrumbsSmaller(collapseDimmed, AncestorSide)) + return; + } + + // Compact ancestor crumbs, or from both sides if focused. + if (makeCrumbsSmaller(compact, (focusedCrumb ? BothSides : AncestorSide))) + return; + + // Collapse ancestor crumbs, or from both sides if focused. + if (makeCrumbsSmaller(collapse, (focusedCrumb ? BothSides : AncestorSide))) + return; + + if (!selectedCrumb) + return; + + // Compact the selected crumb. + compact(selectedCrumb); + if (crumbsAreSmallerThanContainer()) + return; + + // Collapse the selected crumb as a last resort. Pass true to prevent coalescing. + collapse(selectedCrumb, true); + }, + + updateStyles: function(forceUpdate) + { + var stylesSidebarPane = this.sidebarPanes.styles; + var computedStylePane = this.sidebarPanes.computedStyle; + if ((!stylesSidebarPane.expanded && !computedStylePane.expanded) || !stylesSidebarPane.needsUpdate) + return; + + stylesSidebarPane.update(this.selectedDOMNode(), forceUpdate); + stylesSidebarPane.needsUpdate = false; + }, + + updateMetrics: function() + { + var metricsSidebarPane = this.sidebarPanes.metrics; + if (!metricsSidebarPane.expanded || !metricsSidebarPane.needsUpdate) + return; + + metricsSidebarPane.update(this.selectedDOMNode()); + metricsSidebarPane.needsUpdate = false; + }, + + updateProperties: function() + { + var propertiesSidebarPane = this.sidebarPanes.properties; + if (!propertiesSidebarPane.expanded || !propertiesSidebarPane.needsUpdate) + return; + + propertiesSidebarPane.update(this.selectedDOMNode()); + propertiesSidebarPane.needsUpdate = false; + }, + + updateEventListeners: function() + { + var eventListenersSidebarPane = this.sidebarPanes.eventListeners; + if (!eventListenersSidebarPane.expanded || !eventListenersSidebarPane.needsUpdate) + return; + + eventListenersSidebarPane.update(this.selectedDOMNode()); + eventListenersSidebarPane.needsUpdate = false; + }, + + _registerShortcuts: function() + { + var shortcut = WebInspector.KeyboardShortcut; + var section = WebInspector.shortcutsScreen.section(WebInspector.UIString("Elements Panel")); + var keys = [ + shortcut.shortcutToString(shortcut.Keys.Up), + shortcut.shortcutToString(shortcut.Keys.Down) + ]; + section.addRelatedKeys(keys, WebInspector.UIString("Navigate elements")); + + keys = [ + shortcut.shortcutToString(shortcut.Keys.Right), + shortcut.shortcutToString(shortcut.Keys.Left) + ]; + section.addRelatedKeys(keys, WebInspector.UIString("Expand/collapse")); + section.addKey(shortcut.shortcutToString(shortcut.Keys.Enter), WebInspector.UIString("Edit attribute")); + + this.sidebarPanes.styles.registerShortcuts(); + }, + + handleShortcut: function(event) + { + // Cmd/Control + Shift + C should be a shortcut to clicking the Node Search Button. + // This shortcut matches Firebug. + if (event.keyIdentifier === "U+0043") { // C key + if (WebInspector.isMac()) + var isNodeSearchKey = event.metaKey && !event.ctrlKey && !event.altKey && event.shiftKey; + else + var isNodeSearchKey = event.ctrlKey && !event.metaKey && !event.altKey && event.shiftKey; + + if (isNodeSearchKey) { + this.toggleSearchingForNode(); + event.handled = true; + return; + } + } + }, + + handleCopyEvent: function(event) + { + // Don't prevent the normal copy if the user has a selection. + if (!window.getSelection().isCollapsed) + return; + event.clipboardData.clearData(); + event.preventDefault(); + this.selectedDOMNode().copyNode(); + }, + + sidebarResized: function(event) + { + this.treeOutline.updateSelection(); + }, + + _inspectElementRequested: function(event) + { + var node = event.data; + this.revealAndSelectNode(node.id); + }, + + revealAndSelectNode: function(nodeId) + { + WebInspector.inspectorView.setCurrentPanel(this); + + var node = WebInspector.domAgent.nodeForId(nodeId); + if (!node) + return; + + WebInspector.domAgent.highlightDOMNodeForTwoSeconds(nodeId); + this.selectDOMNode(node, true); + if (this.nodeSearchButton.toggled) { + InspectorFrontendHost.bringToFront(); + this.nodeSearchButton.toggled = false; + } + }, + + setSearchingForNode: function(enabled) + { + function callback(error) + { + if (!error) + this.nodeSearchButton.toggled = enabled; + } + WebInspector.domAgent.setInspectModeEnabled(enabled, callback.bind(this)); + }, + + toggleSearchingForNode: function() + { + this.setSearchingForNode(!this.nodeSearchButton.toggled); + } +} + +WebInspector.ElementsPanel.prototype.__proto__ = WebInspector.Panel.prototype; +/* NetworkPanel.js */ + +/* + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2008, 2009 Anthony Ricaud + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.View} + */ +WebInspector.NetworkLogView = function() +{ + WebInspector.View.call(this); + this.registerRequiredCSS("networkLogView.css"); + + this._allowResourceSelection = false; + this._resources = []; + this._resourcesById = {}; + this._resourcesByURL = {}; + this._staleResources = {}; + this._resourceGridNodes = {}; + this._lastResourceGridNodeId = 0; + this._mainResourceLoadTime = -1; + this._mainResourceDOMContentTime = -1; + this._hiddenCategories = {}; + this._matchedResources = []; + this._matchedResourcesMap = {}; + this._currentMatchedResourceIndex = -1; + + this._categories = WebInspector.resourceCategories; + + this._createStatusbarButtons(); + this._createFilterStatusBarItems(); + this._linkifier = WebInspector.debuggerPresentationModel.createLinkifier(); + + WebInspector.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.ResourceStarted, this._onResourceStarted, this); + WebInspector.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.ResourceUpdated, this._onResourceUpdated, this); + WebInspector.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.ResourceFinished, this._onResourceUpdated, this); + + WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.MainFrameNavigated, this._mainFrameNavigated, this); + WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.OnLoad, this._onLoadEventFired, this); + WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.DOMContentLoaded, this._domContentLoadedEventFired, this); + + this._initializeView(); + function onCanClearBrowserCache(error, result) + { + this._canClearBrowserCache = result; + } + NetworkAgent.canClearBrowserCache(onCanClearBrowserCache.bind(this)); + + function onCanClearBrowserCookies(error, result) + { + this._canClearBrowserCookies = result; + } + NetworkAgent.canClearBrowserCookies(onCanClearBrowserCookies.bind(this)); +} + +WebInspector.NetworkLogView.prototype = { + _initializeView: function() + { + this.element.id = "network-container"; + + this._createSortingFunctions(); + this._createTable(); + this._createTimelineGrid(); + this._createSummaryBar(); + + if (!this.useLargeRows) + this._setLargerResources(this.useLargeRows); + + this._allowPopover = true; + this._popoverHelper = new WebInspector.PopoverHelper(this.element, this._getPopoverAnchor.bind(this), this._showPopover.bind(this)); + // Enable faster hint. + this._popoverHelper.setTimeout(100); + + this.calculator = new WebInspector.NetworkTransferTimeCalculator(); + this._filter(this._filterAllElement, false); + + this.switchToDetailedView(); + }, + + get statusBarItems() + { + return [this._largerResourcesButton.element, this._preserveLogToggle.element, this._clearButton.element, this._filterBarElement]; + }, + + get useLargeRows() + { + return WebInspector.settings.resourcesLargeRows.get(); + }, + + set allowPopover(flag) + { + this._allowPopover = flag; + }, + + get allowResourceSelection() + { + return this._allowResourceSelection; + }, + + set allowResourceSelection(flag) + { + this._allowResourceSelection = !!flag; + }, + + elementsToRestoreScrollPositionsFor: function() + { + if (!this._dataGrid) // Not initialized yet. + return []; + return [this._dataGrid.scrollContainer]; + }, + + onResize: function() + { + this._updateOffscreenRows(); + }, + + _createTimelineGrid: function() + { + this._timelineGrid = new WebInspector.TimelineGrid(); + this._timelineGrid.element.addStyleClass("network-timeline-grid"); + this._dataGrid.element.appendChild(this._timelineGrid.element); + }, + + _createTable: function() + { + var columns; + if (Capabilities.nativeInstrumentationEnabled) + columns = {name: {}, method: {}, status: {}, type: {}, initiator: {}, size: {}, time: {}, timeline: {}}; + else + columns = {name: {}, method: {}, status: {}, type: {}, size: {}, time: {}, timeline: {}}; + columns.name.titleDOMFragment = this._makeHeaderFragment(WebInspector.UIString("Name"), WebInspector.UIString("Path")); + columns.name.sortable = true; + columns.name.width = "20%"; + columns.name.disclosure = true; + + columns.method.title = WebInspector.UIString("Method"); + columns.method.sortable = true; + columns.method.width = "6%"; + + columns.status.titleDOMFragment = this._makeHeaderFragment(WebInspector.UIString("Status"), WebInspector.UIString("Text")); + columns.status.sortable = true; + columns.status.width = "6%"; + + columns.type.title = WebInspector.UIString("Type"); + columns.type.sortable = true; + columns.type.width = "6%"; + + if (Capabilities.nativeInstrumentationEnabled) { + columns.initiator.title = WebInspector.UIString("Initiator"); + columns.initiator.sortable = true; + columns.initiator.width = "10%"; + } + + columns.size.titleDOMFragment = this._makeHeaderFragment(WebInspector.UIString("Size"), WebInspector.UIString("Content")); + columns.size.sortable = true; + columns.size.width = "6%"; + columns.size.aligned = "right"; + + columns.time.titleDOMFragment = this._makeHeaderFragment(WebInspector.UIString("Time"), WebInspector.UIString("Latency")); + columns.time.sortable = true; + columns.time.width = "6%"; + columns.time.aligned = "right"; + + columns.timeline.title = ""; + columns.timeline.sortable = false; + if (Capabilities.nativeInstrumentationEnabled) + columns.timeline.width = "40%"; + else + columns.timeline.width = "50%"; + columns.timeline.sort = "ascending"; + + this._dataGrid = new WebInspector.DataGrid(columns); + this._dataGrid.resizeMethod = WebInspector.DataGrid.ResizeMethod.Last; + this._dataGrid.element.addStyleClass("network-log-grid"); + this._dataGrid.element.addEventListener("contextmenu", this._contextMenu.bind(this), true); + this._dataGrid.show(this.element); + + // Event listeners need to be added _after_ we attach to the document, so that owner document is properly update. + this._dataGrid.addEventListener("sorting changed", this._sortItems, this); + this._dataGrid.addEventListener("width changed", this._updateDividersIfNeeded, this); + this._dataGrid.scrollContainer.addEventListener("scroll", this._updateOffscreenRows.bind(this)); + + this._patchTimelineHeader(); + }, + + _makeHeaderFragment: function(title, subtitle) + { + var fragment = document.createDocumentFragment(); + fragment.appendChild(document.createTextNode(title)); + var subtitleDiv = document.createElement("div"); + subtitleDiv.className = "network-header-subtitle"; + subtitleDiv.textContent = subtitle; + fragment.appendChild(subtitleDiv); + return fragment; + }, + + _patchTimelineHeader: function() + { + var timelineSorting = document.createElement("select"); + + var option = document.createElement("option"); + option.value = "startTime"; + option.label = WebInspector.UIString("Timeline"); + timelineSorting.appendChild(option); + + option = document.createElement("option"); + option.value = "startTime"; + option.label = WebInspector.UIString("Start Time"); + timelineSorting.appendChild(option); + + option = document.createElement("option"); + option.value = "responseTime"; + option.label = WebInspector.UIString("Response Time"); + timelineSorting.appendChild(option); + + option = document.createElement("option"); + option.value = "endTime"; + option.label = WebInspector.UIString("End Time"); + timelineSorting.appendChild(option); + + option = document.createElement("option"); + option.value = "duration"; + option.label = WebInspector.UIString("Duration"); + timelineSorting.appendChild(option); + + option = document.createElement("option"); + option.value = "latency"; + option.label = WebInspector.UIString("Latency"); + timelineSorting.appendChild(option); + + var header = this._dataGrid.headerTableHeader("timeline"); + header.replaceChild(timelineSorting, header.firstChild); + + timelineSorting.addEventListener("click", function(event) { event.stopPropagation() }, false); + timelineSorting.addEventListener("change", this._sortByTimeline.bind(this), false); + this._timelineSortSelector = timelineSorting; + }, + + _createSortingFunctions: function() + { + this._sortingFunctions = {}; + this._sortingFunctions.name = WebInspector.NetworkDataGridNode.NameComparator; + this._sortingFunctions.method = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "method", false); + this._sortingFunctions.status = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "statusCode", false); + this._sortingFunctions.type = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "mimeType", false); + this._sortingFunctions.initiator = WebInspector.NetworkDataGridNode.InitiatorComparator; + this._sortingFunctions.size = WebInspector.NetworkDataGridNode.SizeComparator; + this._sortingFunctions.time = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "duration", false); + this._sortingFunctions.timeline = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "startTime", false); + this._sortingFunctions.startTime = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "startTime", false); + this._sortingFunctions.endTime = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "endTime", false); + this._sortingFunctions.responseTime = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "responseReceivedTime", false); + this._sortingFunctions.duration = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "duration", true); + this._sortingFunctions.latency = WebInspector.NetworkDataGridNode.ResourcePropertyComparator.bind(null, "latency", true); + + var timeCalculator = new WebInspector.NetworkTransferTimeCalculator(); + var durationCalculator = new WebInspector.NetworkTransferDurationCalculator(); + + this._calculators = {}; + this._calculators.timeline = timeCalculator; + this._calculators.startTime = timeCalculator; + this._calculators.endTime = timeCalculator; + this._calculators.responseTime = timeCalculator; + this._calculators.duration = durationCalculator; + this._calculators.latency = durationCalculator; + }, + + _sortItems: function() + { + this._removeAllNodeHighlights(); + var columnIdentifier = this._dataGrid.sortColumnIdentifier; + if (columnIdentifier === "timeline") { + this._sortByTimeline(); + return; + } + var sortingFunction = this._sortingFunctions[columnIdentifier]; + if (!sortingFunction) + return; + + this._dataGrid.sortNodes(sortingFunction, this._dataGrid.sortOrder === "descending"); + this._timelineSortSelector.selectedIndex = 0; + this._updateOffscreenRows(); + + this.performSearch(null, true); + }, + + _sortByTimeline: function() + { + this._removeAllNodeHighlights(); + var selectedIndex = this._timelineSortSelector.selectedIndex; + if (!selectedIndex) + selectedIndex = 1; // Sort by start time by default. + var selectedOption = this._timelineSortSelector[selectedIndex]; + var value = selectedOption.value; + + var sortingFunction = this._sortingFunctions[value]; + this._dataGrid.sortNodes(sortingFunction); + this.calculator = this._calculators[value]; + if (this.calculator.startAtZero) + this._timelineGrid.hideEventDividers(); + else + this._timelineGrid.showEventDividers(); + this._dataGrid.markColumnAsSortedBy("timeline", "ascending"); + this._updateOffscreenRows(); + }, + + _createFilterStatusBarItems: function() + { + var filterBarElement = document.createElement("div"); + filterBarElement.className = "scope-bar status-bar-item"; + filterBarElement.id = "network-filter"; + + function createFilterElement(category, label) + { + var categoryElement = document.createElement("li"); + categoryElement.category = category; + categoryElement.className = category; + categoryElement.appendChild(document.createTextNode(label)); + categoryElement.addEventListener("click", this._updateFilter.bind(this), false); + filterBarElement.appendChild(categoryElement); + + return categoryElement; + } + + this._filterAllElement = createFilterElement.call(this, "all", WebInspector.UIString("All")); + + // Add a divider + var dividerElement = document.createElement("div"); + dividerElement.addStyleClass("scope-bar-divider"); + filterBarElement.appendChild(dividerElement); + + for (var category in this._categories) + createFilterElement.call(this, category, this._categories[category].title); + this._filterBarElement = filterBarElement; + }, + + _createSummaryBar: function() + { + var tbody = this._dataGrid.dataTableBody; + var tfoot = document.createElement("tfoot"); + var tr = tfoot.createChild("tr", "revealed network-summary-bar"); + var td = tr.createChild("td"); + td.setAttribute("colspan", 7); + tbody.parentNode.insertBefore(tfoot, tbody); + this._summaryBarElement = td; + }, + + _updateSummaryBar: function() + { + var requestsNumber = this._resources.length; + + if (!requestsNumber) { + if (this._summaryBarElement._isDisplayingWarning) + return; + this._summaryBarElement._isDisplayingWarning = true; + + var img = document.createElement("img"); + img.src = "Images/warningIcon.png"; + this._summaryBarElement.removeChildren(); + this._summaryBarElement.appendChild(img); + this._summaryBarElement.appendChild(document.createTextNode( + WebInspector.UIString("No requests captured. Reload the page to see detailed information on the network activity."))); + return; + } + delete this._summaryBarElement._isDisplayingWarning; + + var transferSize = 0; + var selectedRequestsNumber = 0; + var selectedTransferSize = 0; + var baseTime = -1; + var maxTime = -1; + for (var i = 0; i < this._resources.length; ++i) { + var resource = this._resources[i]; + var resourceTransferSize = (resource.cached || !resource.transferSize) ? 0 : resource.transferSize; + transferSize += resourceTransferSize; + if (!this._hiddenCategories.all || !this._hiddenCategories[resource.category.name]) { + selectedRequestsNumber++; + selectedTransferSize += resourceTransferSize; + } + if (resource.url === WebInspector.inspectedPageURL) + baseTime = resource.startTime; + if (resource.endTime > maxTime) + maxTime = resource.endTime; + } + var text = ""; + if (this._hiddenCategories.all) { + text += String.sprintf(WebInspector.UIString("%d / %d requests"), selectedRequestsNumber, requestsNumber); + text += " \u2758 " + String.sprintf(WebInspector.UIString("%s / %s transferred"), Number.bytesToString(selectedTransferSize), Number.bytesToString(transferSize)); + } else { + text += String.sprintf(WebInspector.UIString("%d requests"), requestsNumber); + text += " \u2758 " + String.sprintf(WebInspector.UIString("%s transferred"), Number.bytesToString(transferSize)); + } + if (baseTime !== -1 && this._mainResourceLoadTime !== -1 && this._mainResourceDOMContentTime !== -1 && this._mainResourceDOMContentTime > baseTime) { + text += " \u2758 " + String.sprintf(WebInspector.UIString("%s (onload: %s, DOMContentLoaded: %s)"), + Number.secondsToString(maxTime - baseTime), + Number.secondsToString(this._mainResourceLoadTime - baseTime), + Number.secondsToString(this._mainResourceDOMContentTime - baseTime)); + } + this._summaryBarElement.textContent = text; + }, + + _showCategory: function(category) + { + this._dataGrid.element.addStyleClass("filter-" + category); + delete this._hiddenCategories[category]; + }, + + _hideCategory: function(category) + { + this._dataGrid.element.removeStyleClass("filter-" + category); + this._hiddenCategories[category] = true; + }, + + _updateFilter: function(e) + { + this._removeAllNodeHighlights(); + var isMac = WebInspector.isMac(); + var selectMultiple = false; + if (isMac && e.metaKey && !e.ctrlKey && !e.altKey && !e.shiftKey) + selectMultiple = true; + if (!isMac && e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey) + selectMultiple = true; + + this._filter(e.target, selectMultiple); + this.performSearch(null, true); + this._updateSummaryBar(); + }, + + _filter: function(target, selectMultiple) + { + function unselectAll() + { + for (var i = 0; i < this._filterBarElement.childNodes.length; ++i) { + var child = this._filterBarElement.childNodes[i]; + if (!child.category) + continue; + + child.removeStyleClass("selected"); + this._hideCategory(child.category); + } + } + + if (target.category === this._filterAllElement) { + if (target.hasStyleClass("selected")) { + // We can't unselect All, so we break early here + return; + } + + // If All wasn't selected, and now is, unselect everything else. + unselectAll.call(this); + } else { + // Something other than All is being selected, so we want to unselect All. + if (this._filterAllElement.hasStyleClass("selected")) { + this._filterAllElement.removeStyleClass("selected"); + this._hideCategory("all"); + } + } + + if (!selectMultiple) { + // If multiple selection is off, we want to unselect everything else + // and just select ourselves. + unselectAll.call(this); + + target.addStyleClass("selected"); + this._showCategory(target.category); + this._updateOffscreenRows(); + return; + } + + if (target.hasStyleClass("selected")) { + // If selectMultiple is turned on, and we were selected, we just + // want to unselect ourselves. + target.removeStyleClass("selected"); + this._hideCategory(target.category); + } else { + // If selectMultiple is turned on, and we weren't selected, we just + // want to select ourselves. + target.addStyleClass("selected"); + this._showCategory(target.category); + } + this._updateOffscreenRows(); + }, + + _defaultRefreshDelay: 500, + + _scheduleRefresh: function() + { + if (this._needsRefresh) + return; + + this._needsRefresh = true; + + if (this.isShowing() && !this._refreshTimeout) + this._refreshTimeout = setTimeout(this.refresh.bind(this), this._defaultRefreshDelay); + }, + + _updateDividersIfNeeded: function(force) + { + if (!this._dataGrid) + return; + var timelineColumn = this._dataGrid.columns.timeline; + for (var i = 0; i < this._dataGrid.resizers.length; ++i) { + if (timelineColumn.ordinal === this._dataGrid.resizers[i].rightNeighboringColumnID) { + // Position timline grid location. + this._timelineGrid.element.style.left = this._dataGrid.resizers[i].style.left; + this._timelineGrid.element.style.right = "18px"; + } + } + + var proceed = true; + if (!this.isShowing()) { + this._scheduleRefresh(); + proceed = false; + } else + proceed = this._timelineGrid.updateDividers(force, this.calculator); + + if (!proceed) + return; + + if (this.calculator.startAtZero || !this.calculator.computePercentageFromEventTime) { + // If our current sorting method starts at zero, that means it shows all + // resources starting at the same point, and so onLoad event and DOMContent + // event lines really wouldn't make much sense here, so don't render them. + // Additionally, if the calculator doesn't have the computePercentageFromEventTime + // function defined, we are probably sorting by size, and event times aren't relevant + // in this case. + return; + } + + this._timelineGrid.removeEventDividers(); + if (this._mainResourceLoadTime !== -1) { + var percent = this.calculator.computePercentageFromEventTime(this._mainResourceLoadTime); + + var loadDivider = document.createElement("div"); + loadDivider.className = "network-event-divider network-red-divider"; + + var loadDividerPadding = document.createElement("div"); + loadDividerPadding.className = "network-event-divider-padding"; + loadDividerPadding.title = WebInspector.UIString("Load event fired"); + loadDividerPadding.appendChild(loadDivider); + loadDividerPadding.style.left = percent + "%"; + this._timelineGrid.addEventDivider(loadDividerPadding); + } + + if (this._mainResourceDOMContentTime !== -1) { + var percent = this.calculator.computePercentageFromEventTime(this._mainResourceDOMContentTime); + + var domContentDivider = document.createElement("div"); + domContentDivider.className = "network-event-divider network-blue-divider"; + + var domContentDividerPadding = document.createElement("div"); + domContentDividerPadding.className = "network-event-divider-padding"; + domContentDividerPadding.title = WebInspector.UIString("DOMContent event fired"); + domContentDividerPadding.appendChild(domContentDivider); + domContentDividerPadding.style.left = percent + "%"; + this._timelineGrid.addEventDivider(domContentDividerPadding); + } + }, + + _refreshIfNeeded: function() + { + if (this._needsRefresh) + this.refresh(); + }, + + _invalidateAllItems: function() + { + for (var i = 0; i < this._resources.length; ++i) { + var resource = this._resources[i]; + this._staleResources[resource.requestId] = resource; + } + }, + + get calculator() + { + return this._calculator; + }, + + set calculator(x) + { + if (!x || this._calculator === x) + return; + + this._calculator = x; + this._calculator.reset(); + + this._invalidateAllItems(); + this.refresh(); + }, + + _resourceGridNode: function(resource) + { + return this._resourceGridNodes[resource.__gridNodeId]; + }, + + _createResourceGridNode: function(resource) + { + var node = new WebInspector.NetworkDataGridNode(this, resource); + resource.__gridNodeId = this._lastResourceGridNodeId++; + this._resourceGridNodes[resource.__gridNodeId] = node; + return node; + }, + + _createStatusbarButtons: function() + { + this._preserveLogToggle = new WebInspector.StatusBarButton(WebInspector.UIString("Preserve Log upon Navigation"), "record-profile-status-bar-item"); + this._preserveLogToggle.addEventListener("click", this._onPreserveLogClicked, this); + + this._clearButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear"), "clear-status-bar-item"); + this._clearButton.addEventListener("click", this._reset, this); + + this._largerResourcesButton = new WebInspector.StatusBarButton(WebInspector.UIString("Use small resource rows."), "network-larger-resources-status-bar-item"); + this._largerResourcesButton.toggled = WebInspector.settings.resourcesLargeRows.get(); + this._largerResourcesButton.addEventListener("click", this._toggleLargerResources, this); + }, + + _onLoadEventFired: function(event) + { + this._mainResourceLoadTime = event.data || -1; + // Schedule refresh to update boundaries and draw the new line. + this._scheduleRefresh(); + }, + + _domContentLoadedEventFired: function(event) + { + this._mainResourceDOMContentTime = event.data || -1; + // Schedule refresh to update boundaries and draw the new line. + this._scheduleRefresh(); + }, + + wasShown: function() + { + this._refreshIfNeeded(); + }, + + willHide: function() + { + this._popoverHelper.hidePopover(); + }, + + refresh: function() + { + this._needsRefresh = false; + if (this._refreshTimeout) { + clearTimeout(this._refreshTimeout); + delete this._refreshTimeout; + } + + this._removeAllNodeHighlights(); + var wasScrolledToLastRow = this._dataGrid.isScrolledToLastRow(); + var boundariesChanged = false; + if (this.calculator.updateBoundariesForEventTime) { + boundariesChanged = this.calculator.updateBoundariesForEventTime(this._mainResourceLoadTime) || boundariesChanged; + boundariesChanged = this.calculator.updateBoundariesForEventTime(this._mainResourceDOMContentTime) || boundariesChanged; + } + + for (var resourceId in this._staleResources) { + var resource = this._staleResources[resourceId]; + var node = this._resourceGridNode(resource); + if (!node) { + // Create the timeline tree element and graph. + node = this._createResourceGridNode(resource); + this._dataGrid.appendChild(node); + } + node.refreshResource(); + + if (this.calculator.updateBoundaries(resource)) + boundariesChanged = true; + + if (!node.isFilteredOut()) + this._updateHighlightIfMatched(resource); + } + + if (boundariesChanged) { + // The boundaries changed, so all item graphs are stale. + this._invalidateAllItems(); + } + + for (var resourceId in this._staleResources) + this._resourceGridNode(this._staleResources[resourceId]).refreshGraph(this.calculator); + + this._staleResources = {}; + this._sortItems(); + this._updateSummaryBar(); + this._dataGrid.updateWidths(); + // FIXME: evaluate performance impact of moving this before a call to sortItems() + if (wasScrolledToLastRow) + this._dataGrid.scrollToLastRow(); + }, + + _onPreserveLogClicked: function(e) + { + this._preserveLogToggle.toggled = !this._preserveLogToggle.toggled; + }, + + _reset: function() + { + this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.ViewCleared); + + this._clearSearchMatchedList(); + if (this._popoverHelper) + this._popoverHelper.hidePopover(); + + if (this._calculator) + this._calculator.reset(); + + this._resources = []; + this._resourcesById = {}; + this._resourcesByURL = {}; + this._staleResources = {}; + this._resourceGridNodes = {}; + + if (this._dataGrid) { + this._dataGrid.removeChildren(); + this._updateDividersIfNeeded(true); + this._updateSummaryBar(); + } + + this._mainResourceLoadTime = -1; + this._mainResourceDOMContentTime = -1; + this._linkifier.reset(); + }, + + get resources() + { + return this._resources; + }, + + resourceById: function(id) + { + return this._resourcesById[id]; + }, + + _onResourceStarted: function(event) + { + this._appendResource(event.data); + }, + + _appendResource: function(resource) + { + this._resources.push(resource); + + // In case of redirect request id is reassigned to a redirected + // resource and we need to update _resourcesById ans search results. + if (this._resourcesById[resource.requestId]) { + var oldResource = resource.redirects[resource.redirects.length - 1]; + this._resourcesById[oldResource.requestId] = oldResource; + + this._updateSearchMatchedListAfterRequestIdChanged(resource.requestId, oldResource.requestId); + } + this._resourcesById[resource.requestId] = resource; + + this._resourcesByURL[resource.url] = resource; + + // Pull all the redirects of the main resource upon commit load. + if (resource.redirects) { + for (var i = 0; i < resource.redirects.length; ++i) + this._refreshResource(resource.redirects[i]); + } + + this._refreshResource(resource); + }, + + _onResourceUpdated: function(event) + { + this._refreshResource(event.data); + }, + + _refreshResource: function(resource) + { + this._staleResources[resource.requestId] = resource; + this._scheduleRefresh(); + }, + + clear: function() + { + if (this._preserveLogToggle.toggled) + return; + this._reset(); + }, + + _mainFrameNavigated: function(event) + { + if (this._preserveLogToggle.toggled) + return; + + var frame = /** @type {WebInspector.ResourceTreeFrame} */ event.data; + var loaderId = frame.loaderId; + + // Preserve provisional load resources. + var resourcesToPreserve = []; + for (var i = 0; i < this._resources.length; ++i) { + var resource = this._resources[i]; + if (resource.loaderId === loaderId) + resourcesToPreserve.push(resource); + } + + this._reset(); + + // Restore preserved items. + for (var i = 0; i < resourcesToPreserve.length; ++i) + this._appendResource(resourcesToPreserve[i]); + }, + + switchToDetailedView: function() + { + if (!this._dataGrid) + return; + if (this._dataGrid.selectedNode) + this._dataGrid.selectedNode.selected = false; + + this.element.removeStyleClass("brief-mode"); + + this._dataGrid.showColumn("method"); + this._dataGrid.showColumn("status"); + this._dataGrid.showColumn("type"); + if (Capabilities.nativeInstrumentationEnabled) + this._dataGrid.showColumn("initiator"); + this._dataGrid.showColumn("size"); + this._dataGrid.showColumn("time"); + this._dataGrid.showColumn("timeline"); + + var widths = {}; + widths.name = 20; + widths.method = 6; + widths.status = 6; + widths.type = 6; + if (Capabilities.nativeInstrumentationEnabled) + widths.initiator = 10; + widths.size = 6; + widths.time = 6; + if (Capabilities.nativeInstrumentationEnabled) + widths.timeline = 40; + else + widths.timeline = 50; + + this._dataGrid.applyColumnWidthsMap(widths); + }, + + switchToBriefView: function() + { + this.element.addStyleClass("brief-mode"); + this._removeAllNodeHighlights(); + + this._dataGrid.hideColumn("method"); + this._dataGrid.hideColumn("status"); + this._dataGrid.hideColumn("type"); + if (Capabilities.nativeInstrumentationEnabled) + this._dataGrid.hideColumn("initiator"); + this._dataGrid.hideColumn("size"); + this._dataGrid.hideColumn("time"); + this._dataGrid.hideColumn("timeline"); + + var widths = {}; + widths.name = 100; + this._dataGrid.applyColumnWidthsMap(widths); + + this._popoverHelper.hidePopover(); + }, + + _toggleLargerResources: function() + { + WebInspector.settings.resourcesLargeRows.set(!WebInspector.settings.resourcesLargeRows.get()); + this._setLargerResources(WebInspector.settings.resourcesLargeRows.get()); + }, + + _setLargerResources: function(enabled) + { + this._largerResourcesButton.toggled = enabled; + if (!enabled) { + this._largerResourcesButton.title = WebInspector.UIString("Use large resource rows."); + this._dataGrid.element.addStyleClass("small"); + this._timelineGrid.element.addStyleClass("small"); + } else { + this._largerResourcesButton.title = WebInspector.UIString("Use small resource rows."); + this._dataGrid.element.removeStyleClass("small"); + this._timelineGrid.element.removeStyleClass("small"); + } + this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.RowSizeChanged, { largeRows: enabled }); + this._updateOffscreenRows(); + }, + + _getPopoverAnchor: function(element) + { + if (!this._allowPopover) + return; + var anchor = element.enclosingNodeOrSelfWithClass("network-graph-bar") || element.enclosingNodeOrSelfWithClass("network-graph-label"); + if (!anchor) + return null; + var resource = anchor.parentElement.resource; + return resource && resource.timing ? anchor : null; + }, + + _showPopover: function(anchor, popover) + { + var resource = anchor.parentElement.resource; + var tableElement = WebInspector.ResourceTimingView.createTimingTable(resource); + popover.show(tableElement, anchor); + }, + + _contextMenu: function(event) + { + var contextMenu = new WebInspector.ContextMenu(); + var gridNode = this._dataGrid.dataGridNodeFromNode(event.target); + var resource = gridNode && gridNode._resource; + + if (resource) { + contextMenu.appendItem(WebInspector.openLinkExternallyLabel(), WebInspector.openResource.bind(WebInspector, resource.url, false)); + contextMenu.appendSeparator(); + contextMenu.appendItem(WebInspector.copyLinkAddressLabel(), this._copyLocation.bind(this, resource)); + if (resource.requestHeadersText) + contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy request headers" : "Copy Request Headers"), this._copyRequestHeaders.bind(this, resource)); + if (resource.responseHeadersText) + contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy response headers" : "Copy Response Headers"), this._copyResponseHeaders.bind(this, resource)); + contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy entry as HAR" : "Copy Entry as HAR"), this._copyResource.bind(this, resource)); + } + contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy all as HAR" : "Copy All as HAR"), this._copyAll.bind(this)); + + if (InspectorFrontendHost.canSaveAs()) { + contextMenu.appendSeparator(); + if (resource) + contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Save entry as HAR" : "Save Entry as HAR"), this._exportResource.bind(this, resource)); + contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Save all as HAR" : "Save All as HAR"), this._exportAll.bind(this)); + } + + if (this._canClearBrowserCache || this._canClearBrowserCookies) + contextMenu.appendSeparator(); + if (this._canClearBrowserCache) + contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Clear browser cache" : "Clear Browser Cache"), this._clearBrowserCache.bind(this)); + if (this._canClearBrowserCookies) + contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Clear browser cookies" : "Clear Browser Cookies"), this._clearBrowserCookies.bind(this)); + + contextMenu.show(event); + }, + + _copyAll: function() + { + var harArchive = { + log: (new WebInspector.HARLog(this._resources)).build() + }; + InspectorFrontendHost.copyText(JSON.stringify(harArchive, null, 2)); + }, + + _copyResource: function(resource) + { + var har = (new WebInspector.HAREntry(resource)).build(); + InspectorFrontendHost.copyText(JSON.stringify(har, null, 2)); + }, + + _copyLocation: function(resource) + { + InspectorFrontendHost.copyText(resource.url); + }, + + _copyRequestHeaders: function(resource) + { + InspectorFrontendHost.copyText(resource.requestHeadersText); + }, + + _copyResponseHeaders: function(resource) + { + InspectorFrontendHost.copyText(resource.responseHeadersText); + }, + + _exportAll: function() + { + var harArchive = { + log: (new WebInspector.HARLog(this._resources)).build() + }; + + InspectorFrontendHost.saveAs(WebInspector.inspectedPageDomain + ".har", JSON.stringify(harArchive, null, 2)); + }, + + _exportResource: function(resource) + { + var har = (new WebInspector.HAREntry(resource)).build(); + InspectorFrontendHost.saveAs(resource.displayName + ".har", JSON.stringify(har, null, 2)); + }, + + _clearBrowserCache: function(event) + { + if (confirm(WebInspector.UIString("Are you sure you want to clear browser cache?"))) + NetworkAgent.clearBrowserCache(); + }, + + _clearBrowserCookies: function(event) + { + if (confirm(WebInspector.UIString("Are you sure you want to clear browser cookies?"))) + NetworkAgent.clearBrowserCookies(); + }, + + _updateOffscreenRows: function() + { + var dataTableBody = this._dataGrid.dataTableBody; + var rows = dataTableBody.children; + var recordsCount = rows.length; + if (recordsCount < 2) + return; // Filler row only. + + var visibleTop = this._dataGrid.scrollContainer.scrollTop; + var visibleBottom = visibleTop + this._dataGrid.scrollContainer.offsetHeight; + + var rowHeight = 0; + + // Filler is at recordsCount - 1. + var unfilteredRowIndex = 0; + for (var i = 0; i < recordsCount - 1; ++i) { + var row = rows[i]; + + var dataGridNode = this._dataGrid.dataGridNodeFromNode(row); + if (dataGridNode.isFilteredOut()) { + row.removeStyleClass("offscreen"); + continue; + } + + if (!rowHeight) + rowHeight = row.offsetHeight; + + var rowIsVisible = unfilteredRowIndex * rowHeight < visibleBottom && (unfilteredRowIndex + 1) * rowHeight > visibleTop; + if (rowIsVisible !== row.rowIsVisible) { + if (rowIsVisible) + row.removeStyleClass("offscreen"); + else + row.addStyleClass("offscreen"); + row.rowIsVisible = rowIsVisible; + } + unfilteredRowIndex++; + } + }, + + _matchResource: function(resource) + { + if (!this._searchRegExp) + return -1; + + if ((!resource.displayName || !resource.displayName.match(this._searchRegExp)) && !resource.folder.match(this._searchRegExp)) + return -1; + + if (resource.requestId in this._matchedResourcesMap) + return this._matchedResourcesMap[resource.requestId]; + + var matchedResourceIndex = this._matchedResources.length; + this._matchedResourcesMap[resource.requestId] = matchedResourceIndex; + this._matchedResources.push(resource.requestId); + + return matchedResourceIndex; + }, + + _clearSearchMatchedList: function() + { + this._matchedResources = []; + this._matchedResourcesMap = {}; + this._highlightNthMatchedResource(-1, false); + }, + + _updateSearchMatchedListAfterRequestIdChanged: function(oldRequestId, newRequestId) + { + var resourceIndex = this._matchedResourcesMap[oldRequestId]; + if (resourceIndex) { + delete this._matchedResourcesMap[oldRequestId]; + this._matchedResourcesMap[newRequestId] = resourceIndex; + this._matchedResources[resourceIndex] = newRequestId; + } + }, + + _updateHighlightIfMatched: function(resource) + { + var matchedResourceIndex = this._matchResource(resource); + if (matchedResourceIndex === -1) + return; + + this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.SearchCountUpdated, this._matchedResources.length); + + if (this._currentMatchedResourceIndex !== -1 && this._currentMatchedResourceIndex !== matchedResourceIndex) + return; + + this._highlightNthMatchedResource(matchedResourceIndex, false); + }, + + _highlightNthMatchedResource: function(matchedResourceIndex, reveal) + { + if (this._highlightedSubstringChanges) { + revertDomChanges(this._highlightedSubstringChanges); + this._highlightedSubstringChanges = null; + } + + if (matchedResourceIndex === -1) { + this._currentMatchedResourceIndex = matchedResourceIndex; + return; + } + + var resource = this._resourcesById[this._matchedResources[matchedResourceIndex]]; + if (!resource) + return; + + var nameMatched = resource.displayName && resource.displayName.match(this._searchRegExp); + var pathMatched = resource.path && resource.folder.match(this._searchRegExp); + if (!nameMatched && pathMatched && !this._largerResourcesButton.toggled) + this._toggleLargerResources(); + + var node = this._resourceGridNode(resource); + if (node) { + this._highlightedSubstringChanges = node._highlightMatchedSubstring(this._searchRegExp); + if (reveal) + node.reveal(); + this._currentMatchedResourceIndex = matchedResourceIndex; + } + this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.SearchIndexUpdated, this._currentMatchedResourceIndex); + }, + + performSearch: function(searchQuery, sortOrFilterApplied) + { + var newMatchedResourceIndex = 0; + var currentMatchedRequestId; + if (this._currentMatchedResourceIndex !== -1) + currentMatchedRequestId = this._matchedResources[this._currentMatchedResourceIndex]; + + if (!sortOrFilterApplied) + this._searchRegExp = createPlainTextSearchRegex(searchQuery, "i"); + + this._clearSearchMatchedList(); + + var childNodes = this._dataGrid.dataTableBody.childNodes; + var resourceNodes = Array.prototype.slice.call(childNodes, 0, childNodes.length - 1); // drop the filler row. + + for (var i = 0; i < resourceNodes.length; ++i) { + var dataGridNode = this._dataGrid.dataGridNodeFromNode(resourceNodes[i]); + if (dataGridNode.isFilteredOut()) + continue; + + if (this._matchResource(dataGridNode._resource) !== -1 && dataGridNode._resource.requestId === currentMatchedRequestId) + newMatchedResourceIndex = this._matchedResources.length - 1; + } + + this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.SearchCountUpdated, this._matchedResources.length); + this._highlightNthMatchedResource(newMatchedResourceIndex, !sortOrFilterApplied); + }, + + jumpToPreviousSearchResult: function() + { + if (!this._matchedResources.length) + return; + this._highlightNthMatchedResource((this._currentMatchedResourceIndex + this._matchedResources.length - 1) % this._matchedResources.length, true); + }, + + jumpToNextSearchResult: function() + { + if (!this._matchedResources.length) + return; + this._highlightNthMatchedResource((this._currentMatchedResourceIndex + 1) % this._matchedResources.length, true); + }, + + searchCanceled: function() + { + this._clearSearchMatchedList(); + this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.SearchCountUpdated, 0); + }, + + revealAndHighlightResource: function(resource) + { + this._removeAllNodeHighlights(); + + var node = this._resourceGridNode(resource); + if (node) { + this._dataGrid.element.focus(); + node.reveal(); + this._highlightNode(node); + } + }, + + _removeAllNodeHighlights: function() + { + if (this._highlightedNode) { + this._highlightedNode.element.removeStyleClass("highlighted-row"); + delete this._highlightedNode; + } + }, + + _highlightNode: function(node) + { + node.element.addStyleClass("highlighted-row"); + this._highlightedNode = node; + } +}; + +WebInspector.NetworkLogView.prototype.__proto__ = WebInspector.View.prototype; + +WebInspector.NetworkLogView.EventTypes = { + ViewCleared: "ViewCleared", + RowSizeChanged: "RowSizeChanged", + ResourceSelected: "ResourceSelected", + SearchCountUpdated: "SearchCountUpdated", + SearchIndexUpdated: "SearchIndexUpdated" +}; + +/** + * @constructor + * @extends {WebInspector.Panel} + */ +WebInspector.NetworkPanel = function() +{ + WebInspector.Panel.call(this, "network"); + this.registerRequiredCSS("networkPanel.css"); + + this.createSplitView(); + this.splitView.hideMainElement(); + + this._networkLogView = new WebInspector.NetworkLogView(); + this._networkLogView.show(this.sidebarElement); + + this._viewsContainerElement = this.splitView.mainElement; + this._viewsContainerElement.id = "network-views"; + this._viewsContainerElement.addStyleClass("hidden"); + if (!this._networkLogView.useLargeRows) + this._viewsContainerElement.addStyleClass("small"); + + this._networkLogView.addEventListener(WebInspector.NetworkLogView.EventTypes.ViewCleared, this._onViewCleared, this); + this._networkLogView.addEventListener(WebInspector.NetworkLogView.EventTypes.RowSizeChanged, this._onRowSizeChanged, this); + this._networkLogView.addEventListener(WebInspector.NetworkLogView.EventTypes.ResourceSelected, this._onResourceSelected, this); + this._networkLogView.addEventListener(WebInspector.NetworkLogView.EventTypes.SearchCountUpdated, this._onSearchCountUpdated, this); + this._networkLogView.addEventListener(WebInspector.NetworkLogView.EventTypes.SearchIndexUpdated, this._onSearchIndexUpdated, this); + + this._closeButtonElement = document.createElement("button"); + this._closeButtonElement.id = "network-close-button"; + this._closeButtonElement.addEventListener("click", this._toggleGridMode.bind(this), false); + this._viewsContainerElement.appendChild(this._closeButtonElement); + + function viewGetter() + { + return this.visibleView; + } + WebInspector.GoToLineDialog.install(this, viewGetter.bind(this)); +} + +WebInspector.NetworkPanel.prototype = { + get toolbarItemLabel() + { + return WebInspector.UIString("Network"); + }, + + get statusBarItems() + { + return this._networkLogView.statusBarItems; + }, + + elementsToRestoreScrollPositionsFor: function() + { + return this._networkLogView.elementsToRestoreScrollPositionsFor(); + }, + + // FIXME: only used by the layout tests, should not be exposed. + _reset: function() + { + this._networkLogView._reset(); + }, + + handleShortcut: function(event) + { + if (this._viewingResourceMode && event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code) { + this._toggleGridMode(); + event.handled = true; + return; + } + + WebInspector.Panel.prototype.handleShortcut.call(this, event); + }, + + wasShown: function() + { + WebInspector.Panel.prototype.wasShown.call(this); + }, + + get resources() + { + return this._networkLogView.resources; + }, + + resourceById: function(id) + { + return this._networkLogView.resourceById(id); + }, + + _resourceByAnchor: function(anchor) + { + return anchor.requestId ? this.resourceById(anchor.requestId) : this._networkLogView._resourcesByURL[anchor.href]; + }, + + canShowAnchorLocation: function(anchor) + { + return !!this._resourceByAnchor(anchor); + }, + + showAnchorLocation: function(anchor) + { + var resource = this._resourceByAnchor(anchor); + this.revealAndHighlightResource(resource) + }, + + revealAndHighlightResource: function(resource) + { + this._toggleGridMode(); + if (resource) + this._networkLogView.revealAndHighlightResource(resource); + }, + + _onViewCleared: function(event) + { + this._closeVisibleResource(); + this._toggleGridMode(); + this._viewsContainerElement.removeChildren(); + this._viewsContainerElement.appendChild(this._closeButtonElement); + }, + + _onRowSizeChanged: function(event) + { + if (event.data.largeRows) + this._viewsContainerElement.removeStyleClass("small"); + else + this._viewsContainerElement.addStyleClass("small"); + }, + + _onSearchCountUpdated: function(event) + { + WebInspector.searchController.updateSearchMatchesCount(event.data, this); + }, + + _onSearchIndexUpdated: function(event) + { + WebInspector.searchController.updateCurrentMatchIndex(event.data, this); + }, + + _onResourceSelected: function(event) + { + this._showResource(event.data); + }, + + _showResource: function(resource) + { + if (!resource) + return; + + this._toggleViewingResourceMode(); + + if (this.visibleView) { + this.visibleView.detach(); + delete this.visibleView; + } + + var view = new WebInspector.NetworkItemView(resource); + view.show(this._viewsContainerElement); + this.visibleView = view; + }, + + _closeVisibleResource: function() + { + this.element.removeStyleClass("viewing-resource"); + + if (this.visibleView) { + this.visibleView.detach(); + delete this.visibleView; + } + }, + + _toggleGridMode: function() + { + if (this._viewingResourceMode) { + this._viewingResourceMode = false; + this.element.removeStyleClass("viewing-resource"); + this.splitView.hideMainElement(); + } + + this._networkLogView.switchToDetailedView(); + this._networkLogView.allowPopover = true; + this._networkLogView.allowResourceSelection = false; + }, + + _toggleViewingResourceMode: function() + { + if (this._viewingResourceMode) + return; + this._viewingResourceMode = true; + + this.element.addStyleClass("viewing-resource"); + this.splitView.showMainElement(); + this._networkLogView.allowPopover = false; + this._networkLogView.allowResourceSelection = true; + this._networkLogView.switchToBriefView(); + }, + + performSearch: function(searchQuery, sortOrFilterApplied) + { + this._networkLogView.performSearch(searchQuery, sortOrFilterApplied); + }, + + jumpToPreviousSearchResult: function() + { + this._networkLogView.jumpToPreviousSearchResult(); + }, + + jumpToNextSearchResult: function() + { + this._networkLogView.jumpToNextSearchResult(); + }, + + searchCanceled: function() + { + this._networkLogView.searchCanceled(); + } +} + +WebInspector.NetworkPanel.prototype.__proto__ = WebInspector.Panel.prototype; + +/** + * @constructor + */ +WebInspector.NetworkBaseCalculator = function() +{ +} + +WebInspector.NetworkBaseCalculator.prototype = { + computeBarGraphPercentages: function(item) + { + return {start: 0, middle: 0, end: (this._value(item) / this.boundarySpan) * 100}; + }, + + computeBarGraphLabels: function(item) + { + const label = this.formatValue(this._value(item)); + return {left: label, right: label, tooltip: label}; + }, + + get boundarySpan() + { + return this.maximumBoundary - this.minimumBoundary; + }, + + updateBoundaries: function(item) + { + this.minimumBoundary = 0; + + var value = this._value(item); + if (typeof this.maximumBoundary === "undefined" || value > this.maximumBoundary) { + this.maximumBoundary = value; + return true; + } + return false; + }, + + reset: function() + { + delete this.minimumBoundary; + delete this.maximumBoundary; + }, + + _value: function(item) + { + return 0; + }, + + formatValue: function(value) + { + return value.toString(); + } +} + +/** + * @constructor + * @extends {WebInspector.NetworkBaseCalculator} + */ +WebInspector.NetworkTimeCalculator = function(startAtZero) +{ + WebInspector.NetworkBaseCalculator.call(this); + this.startAtZero = startAtZero; +} + +WebInspector.NetworkTimeCalculator.prototype = { + computeBarGraphPercentages: function(resource) + { + if (resource.startTime !== -1) + var start = ((resource.startTime - this.minimumBoundary) / this.boundarySpan) * 100; + else + var start = 0; + + if (resource.responseReceivedTime !== -1) + var middle = ((resource.responseReceivedTime - this.minimumBoundary) / this.boundarySpan) * 100; + else + var middle = (this.startAtZero ? start : 100); + + if (resource.endTime !== -1) + var end = ((resource.endTime - this.minimumBoundary) / this.boundarySpan) * 100; + else + var end = (this.startAtZero ? middle : 100); + + if (this.startAtZero) { + end -= start; + middle -= start; + start = 0; + } + + return {start: start, middle: middle, end: end}; + }, + + computePercentageFromEventTime: function(eventTime) + { + // This function computes a percentage in terms of the total loading time + // of a specific event. If startAtZero is set, then this is useless, and we + // want to return 0. + if (eventTime !== -1 && !this.startAtZero) + return ((eventTime - this.minimumBoundary) / this.boundarySpan) * 100; + + return 0; + }, + + updateBoundariesForEventTime: function(eventTime) + { + if (eventTime === -1 || this.startAtZero) + return false; + + if (typeof this.maximumBoundary === "undefined" || eventTime > this.maximumBoundary) { + this.maximumBoundary = eventTime; + return true; + } + return false; + }, + + computeBarGraphLabels: function(resource) + { + var rightLabel = ""; + if (resource.responseReceivedTime !== -1 && resource.endTime !== -1) + rightLabel = this.formatValue(resource.endTime - resource.responseReceivedTime); + + var hasLatency = resource.latency > 0; + if (hasLatency) + var leftLabel = this.formatValue(resource.latency); + else + var leftLabel = rightLabel; + + if (resource.timing) + return {left: leftLabel, right: rightLabel}; + + if (hasLatency && rightLabel) { + var total = this.formatValue(resource.duration); + var tooltip = WebInspector.UIString("%s latency, %s download (%s total)", leftLabel, rightLabel, total); + } else if (hasLatency) + var tooltip = WebInspector.UIString("%s latency", leftLabel); + else if (rightLabel) + var tooltip = WebInspector.UIString("%s download", rightLabel); + + if (resource.cached) + tooltip = WebInspector.UIString("%s (from cache)", tooltip); + return {left: leftLabel, right: rightLabel, tooltip: tooltip}; + }, + + updateBoundaries: function(resource) + { + var didChange = false; + + var lowerBound; + if (this.startAtZero) + lowerBound = 0; + else + lowerBound = this._lowerBound(resource); + + if (lowerBound !== -1 && (typeof this.minimumBoundary === "undefined" || lowerBound < this.minimumBoundary)) { + this.minimumBoundary = lowerBound; + didChange = true; + } + + var upperBound = this._upperBound(resource); + if (upperBound !== -1 && (typeof this.maximumBoundary === "undefined" || upperBound > this.maximumBoundary)) { + this.maximumBoundary = upperBound; + didChange = true; + } + + return didChange; + }, + + formatValue: function(value) + { + return Number.secondsToString(value); + }, + + _lowerBound: function(resource) + { + return 0; + }, + + _upperBound: function(resource) + { + return 0; + } +} + +WebInspector.NetworkTimeCalculator.prototype.__proto__ = WebInspector.NetworkBaseCalculator.prototype; + +/** + * @constructor + * @extends {WebInspector.NetworkTimeCalculator} + */ +WebInspector.NetworkTransferTimeCalculator = function() +{ + WebInspector.NetworkTimeCalculator.call(this, false); +} + +WebInspector.NetworkTransferTimeCalculator.prototype = { + formatValue: function(value) + { + return Number.secondsToString(value); + }, + + _lowerBound: function(resource) + { + return resource.startTime; + }, + + _upperBound: function(resource) + { + return resource.endTime; + } +} + +WebInspector.NetworkTransferTimeCalculator.prototype.__proto__ = WebInspector.NetworkTimeCalculator.prototype; + +/** + * @constructor + * @extends {WebInspector.NetworkTimeCalculator} + */ +WebInspector.NetworkTransferDurationCalculator = function() +{ + WebInspector.NetworkTimeCalculator.call(this, true); +} + +WebInspector.NetworkTransferDurationCalculator.prototype = { + formatValue: function(value) + { + return Number.secondsToString(value); + }, + + _upperBound: function(resource) + { + return resource.duration; + } +} + +WebInspector.NetworkTransferDurationCalculator.prototype.__proto__ = WebInspector.NetworkTimeCalculator.prototype; + +/** + * @constructor + * @extends {WebInspector.DataGridNode} + */ +WebInspector.NetworkDataGridNode = function(parentView, resource) +{ + WebInspector.DataGridNode.call(this, {}); + this._parentView = parentView; + this._resource = resource; +} + +WebInspector.NetworkDataGridNode.prototype = { + createCells: function() + { + // Out of sight, out of mind: create nodes offscreen to save on render tree update times when running updateOffscreenRows() + this._element.addStyleClass("offscreen"); + this._nameCell = this._createDivInTD("name"); + this._methodCell = this._createDivInTD("method"); + this._statusCell = this._createDivInTD("status"); + this._typeCell = this._createDivInTD("type"); + if (Capabilities.nativeInstrumentationEnabled) + this._initiatorCell = this._createDivInTD("initiator"); + this._sizeCell = this._createDivInTD("size"); + this._timeCell = this._createDivInTD("time"); + this._createTimelineCell(); + this._nameCell.addEventListener("click", this.select.bind(this), false); + this._nameCell.addEventListener("dblclick", this._openInNewTab.bind(this), false); + }, + + isFilteredOut: function() + { + if (!this._parentView._hiddenCategories.all) + return false; + return this._resource.category.name in this._parentView._hiddenCategories; + }, + + select: function() + { + this._parentView.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.ResourceSelected, this._resource); + WebInspector.DataGridNode.prototype.select.apply(this, arguments); + }, + + _highlightMatchedSubstring: function(regexp) + { + var domChanges = []; + var matchInfo = this._nameCell.textContent.match(regexp); + highlightSearchResult(this._nameCell, matchInfo.index, matchInfo[0].length, domChanges); + return domChanges; + }, + + _openInNewTab: function() + { + InspectorFrontendHost.openInNewTab(this._resource.url); + }, + + get selectable() + { + return this._parentView.allowResourceSelection && !this.isFilteredOut(); + }, + + _createDivInTD: function(columnIdentifier) + { + var td = document.createElement("td"); + td.className = columnIdentifier + "-column"; + var div = document.createElement("div"); + td.appendChild(div); + this._element.appendChild(td); + return div; + }, + + _createTimelineCell: function() + { + this._graphElement = document.createElement("div"); + this._graphElement.className = "network-graph-side"; + + this._barAreaElement = document.createElement("div"); + // this._barAreaElement.className = "network-graph-bar-area hidden"; + this._barAreaElement.className = "network-graph-bar-area"; + this._barAreaElement.resource = this._resource; + this._graphElement.appendChild(this._barAreaElement); + + this._barLeftElement = document.createElement("div"); + this._barLeftElement.className = "network-graph-bar waiting"; + this._barAreaElement.appendChild(this._barLeftElement); + + this._barRightElement = document.createElement("div"); + this._barRightElement.className = "network-graph-bar"; + this._barAreaElement.appendChild(this._barRightElement); + + + this._labelLeftElement = document.createElement("div"); + this._labelLeftElement.className = "network-graph-label waiting"; + this._barAreaElement.appendChild(this._labelLeftElement); + + this._labelRightElement = document.createElement("div"); + this._labelRightElement.className = "network-graph-label"; + this._barAreaElement.appendChild(this._labelRightElement); + + this._graphElement.addEventListener("mouseover", this._refreshLabelPositions.bind(this), false); + + this._timelineCell = document.createElement("td"); + this._timelineCell.className = "timeline-column"; + this._element.appendChild(this._timelineCell); + this._timelineCell.appendChild(this._graphElement); + }, + + refreshResource: function() + { + this._refreshNameCell(); + + this._methodCell.setTextAndTitle(this._resource.requestMethod); + + this._refreshStatusCell(); + this._refreshTypeCell(); + if (Capabilities.nativeInstrumentationEnabled) + this._refreshInitiatorCell(); + this._refreshSizeCell(); + this._refreshTimeCell(); + + if (this._resource.cached) + this._graphElement.addStyleClass("resource-cached"); + + this._element.addStyleClass("network-item"); + if (!this._element.hasStyleClass("network-category-" + this._resource.category.name)) { + this._element.removeMatchingStyleClasses("network-category-\\w+"); + this._element.addStyleClass("network-category-" + this._resource.category.name); + } + }, + + _refreshNameCell: function() + { + this._nameCell.removeChildren(); + + if (this._resource.category === WebInspector.resourceCategories.images) { + var previewImage = document.createElement("img"); + previewImage.className = "image-network-icon-preview"; + this._resource.populateImageSource(previewImage); + + var iconElement = document.createElement("div"); + iconElement.className = "icon"; + iconElement.appendChild(previewImage); + } else { + var iconElement = document.createElement("img"); + iconElement.className = "icon"; + } + this._nameCell.appendChild(iconElement); + this._nameCell.appendChild(document.createTextNode(this._fileName())); + + + var subtitle = this._resource.displayDomain; + + if (this._resource.path) + subtitle += this._resource.folder; + + this._appendSubtitle(this._nameCell, subtitle); + this._nameCell.title = this._resource.url; + }, + + _fileName: function() + { + var fileName = this._resource.displayName; + if (this._resource.queryString) + fileName += "?" + this._resource.queryString; + return fileName; + }, + + _refreshStatusCell: function() + { + this._statusCell.removeChildren(); + + if (this._resource.failed) { + if (this._resource.canceled) + this._statusCell.setTextAndTitle(WebInspector.UIString("(canceled)")); + else + this._statusCell.setTextAndTitle(WebInspector.UIString("(failed)")); + this._statusCell.addStyleClass("network-dim-cell"); + this.element.addStyleClass("network-error-row"); + return; + } + + this._statusCell.removeStyleClass("network-dim-cell"); + this.element.removeStyleClass("network-error-row"); + + if (this._resource.statusCode) { + this._statusCell.appendChild(document.createTextNode(this._resource.statusCode)); + this._appendSubtitle(this._statusCell, this._resource.statusText); + this._statusCell.title = this._resource.statusCode + " " + this._resource.statusText; + if (this._resource.statusCode >= 400) + this.element.addStyleClass("network-error-row"); + if (this._resource.cached) + this._statusCell.addStyleClass("network-dim-cell"); + } else { + if (!this._resource.isHttpFamily() && this._resource.finished) + this._statusCell.setTextAndTitle(WebInspector.UIString("Success")); + else if (this._resource.isPingRequest()) + this._statusCell.setTextAndTitle(WebInspector.UIString("(ping)")); + else + this._statusCell.setTextAndTitle(WebInspector.UIString("(pending)")); + this._statusCell.addStyleClass("network-dim-cell"); + } + }, + + _refreshTypeCell: function() + { + if (this._resource.mimeType) { + this._typeCell.removeStyleClass("network-dim-cell"); + this._typeCell.setTextAndTitle(this._resource.mimeType); + } else if (this._resource.isPingRequest) { + this._typeCell.removeStyleClass("network-dim-cell"); + this._typeCell.setTextAndTitle(this._resource.requestContentType()); + } else { + this._typeCell.addStyleClass("network-dim-cell"); + this._typeCell.setTextAndTitle(WebInspector.UIString("Pending")); + } + }, + + _refreshInitiatorCell: function() + { + var initiator = this._resource.initiator; + if ((initiator && initiator.type !== "other") || this._resource.redirectSource) { + this._initiatorCell.removeStyleClass("network-dim-cell"); + this._initiatorCell.removeChildren(); + if (this._resource.redirectSource) { + var redirectSource = this._resource.redirectSource; + this._initiatorCell.title = redirectSource.url; + this._initiatorCell.appendChild(WebInspector.linkifyRequestAsNode(redirectSource)); + this._appendSubtitle(this._initiatorCell, WebInspector.UIString("Redirect")); + } else if (initiator.type === "script") { + var topFrame = initiator.stackTrace[0]; + // This could happen when resource loading was triggered by console. + if (!topFrame.url) { + this._initiatorCell.addStyleClass("network-dim-cell"); + this._initiatorCell.setTextAndTitle(WebInspector.UIString("Other")); + return; + } + this._initiatorCell.title = topFrame.url + ":" + topFrame.lineNumber; + var urlElement = this._parentView._linkifier.linkifyLocation(topFrame.url, topFrame.lineNumber - 1, 0); + this._initiatorCell.appendChild(urlElement); + this._appendSubtitle(this._initiatorCell, WebInspector.UIString("Script")); + } else { // initiator.type === "parser" + this._initiatorCell.title = initiator.url + ":" + initiator.lineNumber; + this._initiatorCell.appendChild(WebInspector.linkifyResourceAsNode(initiator.url, initiator.lineNumber - 1)); + this._appendSubtitle(this._initiatorCell, WebInspector.UIString("Parser")); + } + } else { + this._initiatorCell.addStyleClass("network-dim-cell"); + this._initiatorCell.setTextAndTitle(WebInspector.UIString("Other")); + } + }, + + _refreshSizeCell: function() + { + if (this._resource.cached) { + this._sizeCell.setTextAndTitle(WebInspector.UIString("(from cache)")); + this._sizeCell.addStyleClass("network-dim-cell"); + } else { + var resourceSize = typeof this._resource.resourceSize === "number" ? Number.bytesToString(this._resource.resourceSize) : "?"; + var transferSize = typeof this._resource.transferSize === "number" ? Number.bytesToString(this._resource.transferSize) : "?"; + this._sizeCell.setTextAndTitle(transferSize); + this._sizeCell.removeStyleClass("network-dim-cell"); + this._appendSubtitle(this._sizeCell, resourceSize); + } + }, + + _refreshTimeCell: function() + { + if (this._resource.duration > 0) { + this._timeCell.removeStyleClass("network-dim-cell"); + this._timeCell.setTextAndTitle(Number.secondsToString(this._resource.duration)); + this._appendSubtitle(this._timeCell, Number.secondsToString(this._resource.latency)); + } else { + this._timeCell.addStyleClass("network-dim-cell"); + this._timeCell.setTextAndTitle(WebInspector.UIString("Pending")); + } + }, + + _appendSubtitle: function(cellElement, subtitleText) + { + var subtitleElement = document.createElement("div"); + subtitleElement.className = "network-cell-subtitle"; + subtitleElement.textContent = subtitleText; + cellElement.appendChild(subtitleElement); + }, + + refreshGraph: function(calculator) + { + var percentages = calculator.computeBarGraphPercentages(this._resource); + this._percentages = percentages; + + this._barAreaElement.removeStyleClass("hidden"); + + if (!this._graphElement.hasStyleClass("network-category-" + this._resource.category.name)) { + this._graphElement.removeMatchingStyleClasses("network-category-\\w+"); + this._graphElement.addStyleClass("network-category-" + this._resource.category.name); + } + + this._barLeftElement.style.setProperty("left", percentages.start + "%"); + this._barRightElement.style.setProperty("right", (100 - percentages.end) + "%"); + + this._barLeftElement.style.setProperty("right", (100 - percentages.end) + "%"); + this._barRightElement.style.setProperty("left", percentages.middle + "%"); + + var labels = calculator.computeBarGraphLabels(this._resource); + this._labelLeftElement.textContent = labels.left; + this._labelRightElement.textContent = labels.right; + + var tooltip = (labels.tooltip || ""); + this._barLeftElement.title = tooltip; + this._labelLeftElement.title = tooltip; + this._labelRightElement.title = tooltip; + this._barRightElement.title = tooltip; + }, + + _refreshLabelPositions: function() + { + if (!this._percentages) + return; + this._labelLeftElement.style.removeProperty("left"); + this._labelLeftElement.style.removeProperty("right"); + this._labelLeftElement.removeStyleClass("before"); + this._labelLeftElement.removeStyleClass("hidden"); + + this._labelRightElement.style.removeProperty("left"); + this._labelRightElement.style.removeProperty("right"); + this._labelRightElement.removeStyleClass("after"); + this._labelRightElement.removeStyleClass("hidden"); + + const labelPadding = 10; + const barRightElementOffsetWidth = this._barRightElement.offsetWidth; + const barLeftElementOffsetWidth = this._barLeftElement.offsetWidth; + + if (this._barLeftElement) { + var leftBarWidth = barLeftElementOffsetWidth - labelPadding; + var rightBarWidth = (barRightElementOffsetWidth - barLeftElementOffsetWidth) - labelPadding; + } else { + var leftBarWidth = (barLeftElementOffsetWidth - barRightElementOffsetWidth) - labelPadding; + var rightBarWidth = barRightElementOffsetWidth - labelPadding; + } + + const labelLeftElementOffsetWidth = this._labelLeftElement.offsetWidth; + const labelRightElementOffsetWidth = this._labelRightElement.offsetWidth; + + const labelBefore = (labelLeftElementOffsetWidth > leftBarWidth); + const labelAfter = (labelRightElementOffsetWidth > rightBarWidth); + const graphElementOffsetWidth = this._graphElement.offsetWidth; + + if (labelBefore && (graphElementOffsetWidth * (this._percentages.start / 100)) < (labelLeftElementOffsetWidth + 10)) + var leftHidden = true; + + if (labelAfter && (graphElementOffsetWidth * ((100 - this._percentages.end) / 100)) < (labelRightElementOffsetWidth + 10)) + var rightHidden = true; + + if (barLeftElementOffsetWidth == barRightElementOffsetWidth) { + // The left/right label data are the same, so a before/after label can be replaced by an on-bar label. + if (labelBefore && !labelAfter) + leftHidden = true; + else if (labelAfter && !labelBefore) + rightHidden = true; + } + + if (labelBefore) { + if (leftHidden) + this._labelLeftElement.addStyleClass("hidden"); + this._labelLeftElement.style.setProperty("right", (100 - this._percentages.start) + "%"); + this._labelLeftElement.addStyleClass("before"); + } else { + this._labelLeftElement.style.setProperty("left", this._percentages.start + "%"); + this._labelLeftElement.style.setProperty("right", (100 - this._percentages.middle) + "%"); + } + + if (labelAfter) { + if (rightHidden) + this._labelRightElement.addStyleClass("hidden"); + this._labelRightElement.style.setProperty("left", this._percentages.end + "%"); + this._labelRightElement.addStyleClass("after"); + } else { + this._labelRightElement.style.setProperty("left", this._percentages.middle + "%"); + this._labelRightElement.style.setProperty("right", (100 - this._percentages.end) + "%"); + } + } +} + +WebInspector.NetworkDataGridNode.NameComparator = function(a, b) +{ + var aFileName = a._resource.displayName + (a._resource.queryString ? a._resource.queryString : ""); + var bFileName = b._resource.displayName + (b._resource.queryString ? b._resource.queryString : ""); + if (aFileName > bFileName) + return 1; + if (bFileName > aFileName) + return -1; + return 0; +} + +WebInspector.NetworkDataGridNode.SizeComparator = function(a, b) +{ + if (b._resource.cached && !a._resource.cached) + return 1; + if (a._resource.cached && !b._resource.cached) + return -1; + + if (a._resource.resourceSize === b._resource.resourceSize) + return 0; + + return a._resource.resourceSize - b._resource.resourceSize; +} + +WebInspector.NetworkDataGridNode.InitiatorComparator = function(a, b) +{ + if (!a._resource.initiator || a._resource.initiator.type === "Other") + return -1; + if (!b._resource.initiator || b._resource.initiator.type === "Other") + return 1; + + if (a._resource.initiator.url < b._resource.initiator.url) + return -1; + if (a._resource.initiator.url > b._resource.initiator.url) + return 1; + + return a._resource.initiator.lineNumber - b._resource.initiator.lineNumber; +} + +WebInspector.NetworkDataGridNode.ResourcePropertyComparator = function(propertyName, revert, a, b) +{ + var aValue = a._resource[propertyName]; + var bValue = b._resource[propertyName]; + if (aValue > bValue) + return revert ? -1 : 1; + if (bValue > aValue) + return revert ? 1 : -1; + return 0; +} + +WebInspector.NetworkDataGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype; +/* InjectedFakeWorker.js */ + +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +var InjectedFakeWorker = function(InjectedScriptHost, inspectedWindow, injectedScriptId) +{ + +Worker = function(url) +{ + var impl = new FakeWorker(this, url); + if (impl === null) + return null; + + this.isFake = true; + this.postMessage = bind(impl.postMessage, impl); + this.terminate = bind(impl.terminate, impl); + + function onmessageGetter() + { + return impl.channel.port1.onmessage; + } + function onmessageSetter(callback) + { + impl.channel.port1.onmessage = callback; + } + this.__defineGetter__("onmessage", onmessageGetter); + this.__defineSetter__("onmessage", onmessageSetter); + this.addEventListener = bind(impl.channel.port1.addEventListener, impl.channel.port1); + this.removeEventListener = bind(impl.channel.port1.removeEventListener, impl.channel.port1); + this.dispatchEvent = bind(impl.channel.port1.dispatchEvent, impl.channel.port1); +} + +function FakeWorker(worker, url) +{ + var scriptURL = this._expandURLAndCheckOrigin(document.baseURI, location.href, url); + + this._worker = worker; + this._id = InjectedScriptHost.nextWorkerId(); + this.channel = new MessageChannel(); + this._listeners = []; + this._buildWorker(scriptURL); + + InjectedScriptHost.didCreateWorker(this._id, scriptURL.url, false); +} + +FakeWorker.prototype = { + postMessage: function(msg, opt_ports) + { + if (this._frame != null) + this.channel.port1.postMessage.apply(this.channel.port1, arguments); + else if (this._pendingMessages) + this._pendingMessages.push(arguments) + else + this._pendingMessages = [ arguments ]; + }, + + terminate: function() + { + InjectedScriptHost.didDestroyWorker(this._id); + + this.channel.port1.close(); + this.channel.port2.close(); + if (this._frame != null) + this._frame.frameElement.parentNode.removeChild(this._frame.frameElement); + this._frame = null; + this._worker = null; // Break reference loop. + }, + + _buildWorker: function(url) + { + var code = this._loadScript(url.url); + var iframeElement = document.createElement("iframe"); + iframeElement.style.display = "none"; + + this._document = document; + iframeElement.onload = bind(this._onWorkerFrameLoaded, this, iframeElement, url, code); + + if (document.body) + this._attachWorkerFrameToDocument(iframeElement, url, code); + else + window.addEventListener("load", bind(this._attachWorkerFrameToDocument, this, iframeElement), false); + }, + + _attachWorkerFrameToDocument: function(iframeElement) + { + document.body.appendChild(iframeElement); + }, + + _onWorkerFrameLoaded: function(iframeElement, url, code) + { + var frame = iframeElement.contentWindow; + this._frame = frame; + this._setupWorkerContext(frame, url); + + var frameContents = '(function() { var location = __devtools.location; var window; ' + code + '})();\n' + '//@ sourceURL=' + url.url; + + frame.eval(frameContents); + if (this._pendingMessages) { + for (var msg = 0; msg < this._pendingMessages.length; ++msg) + this.postMessage.apply(this, this._pendingMessages[msg]); + delete this._pendingMessages; + } + }, + + _setupWorkerContext: function(workerFrame, url) + { + workerFrame.__devtools = { + handleException: bind(this._handleException, this), + location: url.mockLocation() + }; + + var self = this; + + function onmessageGetter() + { + return self.channel.port2.onmessage ? self.channel.port2.onmessage.originalCallback : null; + } + + function onmessageSetter(callback) + { + var wrappedCallback = bind(self._callbackWrapper, self, callback); + wrappedCallback.originalCallback = callback; + self.channel.port2.onmessage = wrappedCallback; + } + + workerFrame.__defineGetter__("onmessage", onmessageGetter); + workerFrame.__defineSetter__("onmessage", onmessageSetter); + workerFrame.addEventListener = bind(this._addEventListener, this); + workerFrame.removeEventListener = bind(this._removeEventListener, this); + workerFrame.dispatchEvent = bind(this.channel.port2.dispatchEvent, this.channel.port2); + workerFrame.postMessage = bind(this.channel.port2.postMessage, this.channel.port2); + workerFrame.importScripts = bind(this._importScripts, this, workerFrame); + workerFrame.close = bind(this.terminate, this); + }, + + _addEventListener: function(type, callback, useCapture) + { + var wrappedCallback = bind(this._callbackWrapper, this, callback); + wrappedCallback.originalCallback = callback; + wrappedCallback.type = type; + wrappedCallback.useCapture = Boolean(useCapture); + + this.channel.port2.addEventListener(type, wrappedCallback, useCapture); + this._listeners.push(wrappedCallback); + }, + + _removeEventListener: function(type, callback, useCapture) + { + var listeners = this._listeners; + for (var i = 0; i < listeners.length; ++i) { + if (listeners[i].originalCallback === callback && + listeners[i].type === type && + listeners[i].useCapture === Boolean(useCapture)) { + this.channel.port2.removeEventListener(type, listeners[i], useCapture); + listeners[i] = listeners[listeners.length - 1]; + listeners.pop(); + break; + } + } + }, + + _callbackWrapper: function(callback, msg) + { + // Shortcut -- if no exception handlers installed, avoid try/catch so as not to obscure line number. + if (!this._frame.onerror && !this._worker.onerror) { + callback(msg); + return; + } + + try { + callback(msg); + } catch (e) { + this._handleException(e, this._frame.onerror, this._worker.onerror); + } + }, + + _handleException: function(e) + { + // NB: it should be an ErrorEvent, but creating it from script is not + // currently supported, so emulate it on top of plain vanilla Event. + var errorEvent = this._document.createEvent("Event"); + errorEvent.initEvent("Event", false, false); + errorEvent.message = "Uncaught exception"; + + for (var i = 1; i < arguments.length; ++i) { + if (arguments[i] && arguments[i](errorEvent)) + return; + } + + throw e; + }, + + _importScripts: function(targetFrame) + { + for (var i = 1; i < arguments.length; ++i) { + var workerOrigin = targetFrame.__devtools.location.href; + var url = this._expandURLAndCheckOrigin(workerOrigin, workerOrigin, arguments[i]); + targetFrame.eval(this._loadScript(url.url) + "\n//@ sourceURL= " + url.url); + } + }, + + _loadScript: function(url) + { + var xhr = new XMLHttpRequest(); + xhr.open("GET", url, false); + xhr.send(null); + + var text = xhr.responseText; + if (xhr.status != 0 && xhr.status/100 !== 2) { // We're getting status === 0 when using file://. + console.error("Failed to load worker: " + url + "[" + xhr.status + "]"); + text = ""; // We've got error message, not worker code. + } + return text; + }, + + _expandURLAndCheckOrigin: function(baseURL, origin, url) + { + var scriptURL = new URL(baseURL).completeWith(url); + + if (!scriptURL.sameOrigin(origin)) + throw new DOMCoreException("SECURITY_ERR",18); + return scriptURL; + } +}; + +function URL(url) +{ + this.url = url; + this.split(); +} + +URL.prototype = { + urlRegEx: (/^(http[s]?|file):\/\/([^\/:]*)(:[\d]+)?(?:(\/[^#?]*)(\?[^#]*)?(?:#(.*))?)?$/i), + + split: function() + { + function emptyIfNull(str) + { + return str == null ? "" : str; + } + var parts = this.urlRegEx.exec(this.url); + + this.schema = parts[1]; + this.host = parts[2]; + this.port = emptyIfNull(parts[3]); + this.path = emptyIfNull(parts[4]); + this.query = emptyIfNull(parts[5]); + this.fragment = emptyIfNull(parts[6]); + }, + + mockLocation: function() + { + var host = this.host.replace(/^[^@]*@/, ""); + + return { + href: this.url, + protocol: this.schema + ":", + host: host, + hostname: host, + port: this.port, + pathname: this.path, + search: this.query, + hash: this.fragment + }; + }, + + completeWith: function(url) + { + if (url === "" || /^[^/]*:/.exec(url)) // If given absolute url, return as is now. + return new URL(url); + + var relParts = /^([^#?]*)(.*)$/.exec(url); // => [ url, path, query-andor-fragment ] + + var path = (relParts[1].slice(0, 1) === "/" ? "" : this.path.replace(/[^/]*$/, "")) + relParts[1]; + path = path.replace(/(\/\.)+(\/|$)/g, "/").replace(/[^/]*\/\.\.(\/|$)/g, ""); + + return new URL(this.schema + "://" + this.host + this.port + path + relParts[2]); + }, + + sameOrigin: function(url) + { + function normalizePort(schema, port) + { + var portNo = port.slice(1); + return (schema === "https" && portNo == 443 || schema === "http" && portNo == 80) ? "" : port; + } + + var other = new URL(url); + + return this.schema === other.schema && + this.host === other.host && + normalizePort(this.schema, this.port) === normalizePort(other.schema, other.port); + } +}; + +function DOMCoreException(name, code) +{ + function formatError() + { + return "Error: " + this.message; + } + + this.name = name; + this.message = name + ": DOM Exception " + code; + this.code = code; + this.toString = bind(formatError, this); +} + +function bind(func, thisObject) +{ + var args = Array.prototype.slice.call(arguments, 2); + return function() { return func.apply(thisObject, args.concat(Array.prototype.slice.call(arguments, 0))); }; +} + +function noop() +{ +} + +} +/* TextViewer.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * Copyright (C) 2010 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @extends {WebInspector.View} + * @constructor + */ +WebInspector.TextViewer = function(textModel, platform, url, delegate) +{ + WebInspector.View.call(this); + this.registerRequiredCSS("textViewer.css"); + + this._textModel = textModel; + this._textModel.changeListener = this._textChanged.bind(this); + this._textModel.resetUndoStack(); + this._delegate = delegate; + + this.element.className = "text-editor monospace"; + + var enterTextChangeMode = this._enterInternalTextChangeMode.bind(this); + var exitTextChangeMode = this._exitInternalTextChangeMode.bind(this); + var syncScrollListener = this._syncScroll.bind(this); + var syncDecorationsForLineListener = this._syncDecorationsForLine.bind(this); + var syncLineHeightListener = this._syncLineHeight.bind(this); + this._mainPanel = new WebInspector.TextEditorMainPanel(this._textModel, url, syncScrollListener, syncDecorationsForLineListener, enterTextChangeMode, exitTextChangeMode); + this._gutterPanel = new WebInspector.TextEditorGutterPanel(this._textModel, syncDecorationsForLineListener, syncLineHeightListener); + this.element.appendChild(this._mainPanel.element); + this.element.appendChild(this._gutterPanel.element); + + // Forward mouse wheel events from the unscrollable gutter to the main panel. + function forwardWheelEvent(event) + { + var clone = document.createEvent("WheelEvent"); + clone.initWebKitWheelEvent(event.wheelDeltaX, event.wheelDeltaY, + event.view, + event.screenX, event.screenY, + event.clientX, event.clientY, + event.ctrlKey, event.altKey, event.shiftKey, event.metaKey); + this._mainPanel.element.dispatchEvent(clone); + } + this._gutterPanel.element.addEventListener("mousewheel", forwardWheelEvent.bind(this), false); + + this.element.addEventListener("dblclick", this._doubleClick.bind(this), true); + this.element.addEventListener("keydown", this._handleKeyDown.bind(this), false); + this.element.addEventListener("contextmenu", this._contextMenu.bind(this), true); + + this._registerShortcuts(); +} + +WebInspector.TextViewer.prototype = { + set mimeType(mimeType) + { + this._mainPanel.mimeType = mimeType; + }, + + set readOnly(readOnly) + { + if (this._mainPanel.readOnly === readOnly) + return; + this._mainPanel.readOnly = readOnly; + WebInspector.markBeingEdited(this.element, !readOnly); + }, + + get readOnly() + { + return this._mainPanel.readOnly; + }, + + get textModel() + { + return this._textModel; + }, + + focus: function() + { + this._mainPanel.element.focus(); + }, + + revealLine: function(lineNumber) + { + this._mainPanel.revealLine(lineNumber); + }, + + addDecoration: function(lineNumber, decoration) + { + this._mainPanel.addDecoration(lineNumber, decoration); + this._gutterPanel.addDecoration(lineNumber, decoration); + }, + + removeDecoration: function(lineNumber, decoration) + { + this._mainPanel.removeDecoration(lineNumber, decoration); + this._gutterPanel.removeDecoration(lineNumber, decoration); + }, + + markAndRevealRange: function(range) + { + this._mainPanel.markAndRevealRange(range); + }, + + highlightLine: function(lineNumber) + { + if (typeof lineNumber !== "number" || lineNumber < 0) + return; + + lineNumber = Math.min(lineNumber, this._textModel.linesCount - 1); + this._mainPanel.highlightLine(lineNumber); + }, + + clearLineHighlight: function() + { + this._mainPanel.clearLineHighlight(); + }, + + freeCachedElements: function() + { + this._mainPanel.freeCachedElements(); + this._gutterPanel.freeCachedElements(); + }, + + elementsToRestoreScrollPositionsFor: function() + { + return [this._mainPanel.element]; + }, + + inheritScrollPositions: function(textViewer) + { + this._mainPanel.element._scrollTop = textViewer._mainPanel.element.scrollTop; + this._mainPanel.element._scrollLeft = textViewer._mainPanel.element.scrollLeft; + }, + + beginUpdates: function() + { + this._mainPanel.beginUpdates(); + this._gutterPanel.beginUpdates(); + }, + + endUpdates: function() + { + this._mainPanel.endUpdates(); + this._gutterPanel.endUpdates(); + this._updatePanelOffsets(); + }, + + onResize: function() + { + this._mainPanel.resize(); + this._gutterPanel.resize(); + this._updatePanelOffsets(); + }, + + // WebInspector.TextModel listener + _textChanged: function(oldRange, newRange, oldText, newText) + { + if (!this._internalTextChangeMode) + this._textModel.resetUndoStack(); + this._mainPanel.textChanged(oldRange, newRange); + this._gutterPanel.textChanged(oldRange, newRange); + this._updatePanelOffsets(); + }, + + _enterInternalTextChangeMode: function() + { + this._internalTextChangeMode = true; + this._delegate.beforeTextChanged(); + }, + + _exitInternalTextChangeMode: function(oldRange, newRange) + { + this._internalTextChangeMode = false; + this._delegate.afterTextChanged(oldRange, newRange); + }, + + _updatePanelOffsets: function() + { + var lineNumbersWidth = this._gutterPanel.element.offsetWidth; + if (lineNumbersWidth) + this._mainPanel.element.style.setProperty("left", lineNumbersWidth + "px"); + else + this._mainPanel.element.style.removeProperty("left"); // Use default value set in CSS. + }, + + _syncScroll: function() + { + var mainElement = this._mainPanel.element; + var gutterElement = this._gutterPanel.element; + // Handle horizontal scroll bar at the bottom of the main panel. + this._gutterPanel.syncClientHeight(mainElement.clientHeight); + gutterElement.scrollTop = mainElement.scrollTop; + }, + + _syncDecorationsForLine: function(lineNumber) + { + if (lineNumber >= this._textModel.linesCount) + return; + + var mainChunk = this._mainPanel.chunkForLine(lineNumber); + if (mainChunk.linesCount === 1 && mainChunk.decorated) { + var gutterChunk = this._gutterPanel.makeLineAChunk(lineNumber); + var height = mainChunk.height; + if (height) + gutterChunk.element.style.setProperty("height", height + "px"); + else + gutterChunk.element.style.removeProperty("height"); + } else { + var gutterChunk = this._gutterPanel.chunkForLine(lineNumber); + if (gutterChunk.linesCount === 1) + gutterChunk.element.style.removeProperty("height"); + } + }, + + _syncLineHeight: function(gutterRow) { + if (this._lineHeightSynced) + return; + if (gutterRow && gutterRow.offsetHeight) { + // Force equal line heights for the child panels. + this.element.style.setProperty("line-height", gutterRow.offsetHeight + "px"); + this._lineHeightSynced = true; + } + }, + + _doubleClick: function(event) + { + if (!this.readOnly) + return; + + var lineRow = event.target.enclosingNodeOrSelfWithClass("webkit-line-content"); + if (!lineRow) + return; // Do not trigger editing from line numbers. + + this._delegate.doubleClick(lineRow.lineNumber); + window.getSelection().collapseToStart(); + }, + + _registerShortcuts: function() + { + var keys = WebInspector.KeyboardShortcut.Keys; + var modifiers = WebInspector.KeyboardShortcut.Modifiers; + + this._shortcuts = {}; + var commitEditing = this._commitEditing.bind(this); + var cancelEditing = this._cancelEditing.bind(this); + this._shortcuts[WebInspector.KeyboardShortcut.makeKey("s", modifiers.CtrlOrMeta)] = commitEditing; + this._shortcuts[WebInspector.KeyboardShortcut.makeKey(keys.Enter.code, modifiers.CtrlOrMeta)] = commitEditing; + this._shortcuts[WebInspector.KeyboardShortcut.makeKey(keys.Esc.code)] = cancelEditing; + + var handleEnterKey = this._mainPanel.handleEnterKey.bind(this._mainPanel); + this._shortcuts[WebInspector.KeyboardShortcut.makeKey(keys.Enter.code, WebInspector.KeyboardShortcut.Modifiers.None)] = handleEnterKey; + + var handleUndo = this._mainPanel.handleUndoRedo.bind(this._mainPanel, false); + var handleRedo = this._mainPanel.handleUndoRedo.bind(this._mainPanel, true); + this._shortcuts[WebInspector.KeyboardShortcut.makeKey("z", modifiers.CtrlOrMeta)] = handleUndo; + this._shortcuts[WebInspector.KeyboardShortcut.makeKey("z", modifiers.Shift | modifiers.CtrlOrMeta)] = handleRedo; + + var handleTabKey = this._mainPanel.handleTabKeyPress.bind(this._mainPanel, false); + var handleShiftTabKey = this._mainPanel.handleTabKeyPress.bind(this._mainPanel, true); + this._shortcuts[WebInspector.KeyboardShortcut.makeKey(keys.Tab.code)] = handleTabKey; + this._shortcuts[WebInspector.KeyboardShortcut.makeKey(keys.Tab.code, modifiers.Shift)] = handleShiftTabKey; + }, + + _handleKeyDown: function(e) + { + if (this.readOnly) + return; + + var shortcutKey = WebInspector.KeyboardShortcut.makeKeyFromEvent(e); + var handler = this._shortcuts[shortcutKey]; + if (handler && handler()) { + e.preventDefault(); + e.stopPropagation(); + } + }, + + _contextMenu: function(event) + { + var contextMenu = new WebInspector.ContextMenu(); + var target = event.target.enclosingNodeOrSelfWithClass("webkit-line-number"); + if (target) + this._delegate.populateLineGutterContextMenu(contextMenu, target.lineNumber); + else { + target = this._mainPanel._enclosingLineRowOrSelf(event.target); + this._delegate.populateTextAreaContextMenu(contextMenu, target && target.lineNumber); + } + var fileName = this._delegate.suggestedFileName(); + if (fileName) + contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Save as..." : "Save As..."), InspectorFrontendHost.saveAs.bind(InspectorFrontendHost, fileName, this._textModel.text)); + + contextMenu.show(event); + }, + + _commitEditing: function() + { + if (this.readOnly) + return false; + + this._delegate.commitEditing(); + return true; + }, + + _cancelEditing: function() + { + if (this.readOnly) + return false; + + return this._delegate.cancelEditing(); + }, + + wasShown: function() + { + if (!this.readOnly) + WebInspector.markBeingEdited(this.element, true); + }, + + willHide: function() + { + if (!this.readOnly) + WebInspector.markBeingEdited(this.element, false); + } +} + +WebInspector.TextViewer.prototype.__proto__ = WebInspector.View.prototype; + +/** + * @interface + */ +WebInspector.TextViewerDelegate = function() +{ +} + +WebInspector.TextViewerDelegate.prototype = { + doubleClick: function(lineNumber) { }, + + beforeTextChanged: function() { }, + + afterTextChanged: function(oldRange, newRange) { }, + + commitEditing: function() { }, + + cancelEditing: function() { }, + + populateLineGutterContextMenu: function(contextMenu, lineNumber) { }, + + populateTextAreaContextMenu: function(contextMenu, lineNumber) { }, + + suggestedFileName: function() { } +} + +/** + * @constructor + */ +WebInspector.TextEditorChunkedPanel = function(textModel) +{ + this._textModel = textModel; + + this._defaultChunkSize = 50; + this._paintCoalescingLevel = 0; + this._domUpdateCoalescingLevel = 0; +} + +WebInspector.TextEditorChunkedPanel.prototype = { + get textModel() + { + return this._textModel; + }, + + revealLine: function(lineNumber) + { + if (lineNumber >= this._textModel.linesCount) + return; + + var chunk = this.makeLineAChunk(lineNumber); + chunk.element.scrollIntoViewIfNeeded(); + }, + + addDecoration: function(lineNumber, decoration) + { + if (lineNumber >= this._textModel.linesCount) + return; + + var chunk = this.makeLineAChunk(lineNumber); + chunk.addDecoration(decoration); + }, + + removeDecoration: function(lineNumber, decoration) + { + if (lineNumber >= this._textModel.linesCount) + return; + + var chunk = this.chunkForLine(lineNumber); + chunk.removeDecoration(decoration); + }, + + _buildChunks: function() + { + this.beginDomUpdates(); + + this._container.removeChildren(); + + this._textChunks = []; + for (var i = 0; i < this._textModel.linesCount; i += this._defaultChunkSize) { + var chunk = this._createNewChunk(i, i + this._defaultChunkSize); + this._textChunks.push(chunk); + this._container.appendChild(chunk.element); + } + + this._repaintAll(); + + this.endDomUpdates(); + }, + + makeLineAChunk: function(lineNumber) + { + var chunkNumber = this._chunkNumberForLine(lineNumber); + var oldChunk = this._textChunks[chunkNumber]; + + if (!oldChunk) { + console.error("No chunk for line number: " + lineNumber); + return; + } + + if (oldChunk.linesCount === 1) + return oldChunk; + + return this._splitChunkOnALine(lineNumber, chunkNumber, true); + }, + + _splitChunkOnALine: function(lineNumber, chunkNumber, createSuffixChunk) + { + this.beginDomUpdates(); + + var oldChunk = this._textChunks[chunkNumber]; + var wasExpanded = oldChunk.expanded; + oldChunk.expanded = false; + + var insertIndex = chunkNumber + 1; + + // Prefix chunk. + if (lineNumber > oldChunk.startLine) { + var prefixChunk = this._createNewChunk(oldChunk.startLine, lineNumber); + prefixChunk.readOnly = oldChunk.readOnly; + this._textChunks.splice(insertIndex++, 0, prefixChunk); + this._container.insertBefore(prefixChunk.element, oldChunk.element); + } + + // Line chunk. + var endLine = createSuffixChunk ? lineNumber + 1 : oldChunk.startLine + oldChunk.linesCount; + var lineChunk = this._createNewChunk(lineNumber, endLine); + lineChunk.readOnly = oldChunk.readOnly; + this._textChunks.splice(insertIndex++, 0, lineChunk); + this._container.insertBefore(lineChunk.element, oldChunk.element); + + // Suffix chunk. + if (oldChunk.startLine + oldChunk.linesCount > endLine) { + var suffixChunk = this._createNewChunk(endLine, oldChunk.startLine + oldChunk.linesCount); + suffixChunk.readOnly = oldChunk.readOnly; + this._textChunks.splice(insertIndex, 0, suffixChunk); + this._container.insertBefore(suffixChunk.element, oldChunk.element); + } + + // Remove enclosing chunk. + this._textChunks.splice(chunkNumber, 1); + this._container.removeChild(oldChunk.element); + + if (wasExpanded) { + if (prefixChunk) + prefixChunk.expanded = true; + lineChunk.expanded = true; + if (suffixChunk) + suffixChunk.expanded = true; + } + + this.endDomUpdates(); + + return lineChunk; + }, + + _scroll: function() + { + // FIXME: Replace the "2" with the padding-left value from CSS. + if (this.element.scrollLeft <= 2) + this.element.scrollLeft = 0; + + this._scheduleRepaintAll(); + if (this._syncScrollListener) + this._syncScrollListener(); + }, + + _scheduleRepaintAll: function() + { + if (this._repaintAllTimer) + clearTimeout(this._repaintAllTimer); + this._repaintAllTimer = setTimeout(this._repaintAll.bind(this), 50); + }, + + beginUpdates: function() + { + this._paintCoalescingLevel++; + }, + + endUpdates: function() + { + this._paintCoalescingLevel--; + if (!this._paintCoalescingLevel) + this._repaintAll(); + }, + + beginDomUpdates: function() + { + this._domUpdateCoalescingLevel++; + }, + + endDomUpdates: function() + { + this._domUpdateCoalescingLevel--; + }, + + _chunkNumberForLine: function(lineNumber) + { + function compareLineNumbers(value, chunk) + { + return value < chunk.startLine ? -1 : 1; + } + var insertBefore = insertionIndexForObjectInListSortedByFunction(lineNumber, this._textChunks, compareLineNumbers); + return insertBefore - 1; + }, + + chunkForLine: function(lineNumber) + { + return this._textChunks[this._chunkNumberForLine(lineNumber)]; + }, + + _findFirstVisibleChunkNumber: function(visibleFrom) + { + function compareOffsetTops(value, chunk) + { + return value < chunk.offsetTop ? -1 : 1; + } + var insertBefore = insertionIndexForObjectInListSortedByFunction(visibleFrom, this._textChunks, compareOffsetTops); + return insertBefore - 1; + }, + + _findVisibleChunks: function(visibleFrom, visibleTo) + { + var from = this._findFirstVisibleChunkNumber(visibleFrom); + for (var to = from + 1; to < this._textChunks.length; ++to) { + if (this._textChunks[to].offsetTop >= visibleTo) + break; + } + return { start: from, end: to }; + }, + + _findFirstVisibleLineNumber: function(visibleFrom) + { + var chunk = this._textChunks[this._findFirstVisibleChunkNumber(visibleFrom)]; + if (!chunk.expanded) + return chunk.startLine; + + var lineNumbers = []; + for (var i = 0; i < chunk.linesCount; ++i) { + lineNumbers.push(chunk.startLine + i); + } + + function compareLineRowOffsetTops(value, lineNumber) + { + var lineRow = chunk.getExpandedLineRow(lineNumber); + return value < lineRow.offsetTop ? -1 : 1; + } + var insertBefore = insertionIndexForObjectInListSortedByFunction(visibleFrom, lineNumbers, compareLineRowOffsetTops); + return lineNumbers[insertBefore - 1]; + }, + + _repaintAll: function() + { + delete this._repaintAllTimer; + + if (this._paintCoalescingLevel || this._dirtyLines) + return; + + var visibleFrom = this.element.scrollTop; + var visibleTo = this.element.scrollTop + this.element.clientHeight; + + if (visibleTo) { + var result = this._findVisibleChunks(visibleFrom, visibleTo); + this._expandChunks(result.start, result.end); + } + }, + + _expandChunks: function(fromIndex, toIndex) + { + // First collapse chunks to collect the DOM elements into a cache to reuse them later. + for (var i = 0; i < fromIndex; ++i) + this._textChunks[i].expanded = false; + for (var i = toIndex; i < this._textChunks.length; ++i) + this._textChunks[i].expanded = false; + for (var i = fromIndex; i < toIndex; ++i) + this._textChunks[i].expanded = true; + }, + + _totalHeight: function(firstElement, lastElement) + { + lastElement = (lastElement || firstElement).nextElementSibling; + if (lastElement) + return lastElement.offsetTop - firstElement.offsetTop; + + var offsetParent = firstElement.offsetParent; + if (offsetParent && offsetParent.scrollHeight > offsetParent.clientHeight) + return offsetParent.scrollHeight - firstElement.offsetTop; + + var total = 0; + while (firstElement && firstElement !== lastElement) { + total += firstElement.offsetHeight; + firstElement = firstElement.nextElementSibling; + } + return total; + }, + + resize: function() + { + this._repaintAll(); + } +} + +/** + * @constructor + * @extends {WebInspector.TextEditorChunkedPanel} + */ +WebInspector.TextEditorGutterPanel = function(textModel, syncDecorationsForLineListener, syncLineHeightListener) +{ + WebInspector.TextEditorChunkedPanel.call(this, textModel); + + this._syncDecorationsForLineListener = syncDecorationsForLineListener; + this._syncLineHeightListener = syncLineHeightListener; + + this.element = document.createElement("div"); + this.element.className = "text-editor-lines"; + + this._container = document.createElement("div"); + this._container.className = "inner-container"; + this.element.appendChild(this._container); + + this.element.addEventListener("scroll", this._scroll.bind(this), false); + + this.freeCachedElements(); + this._buildChunks(); +} + +WebInspector.TextEditorGutterPanel.prototype = { + freeCachedElements: function() + { + this._cachedRows = []; + }, + + _createNewChunk: function(startLine, endLine) + { + return new WebInspector.TextEditorGutterChunk(this, startLine, endLine); + }, + + textChanged: function(oldRange, newRange) + { + this.beginDomUpdates(); + + var linesDiff = newRange.linesCount - oldRange.linesCount; + if (linesDiff) { + // Remove old chunks (if needed). + for (var chunkNumber = this._textChunks.length - 1; chunkNumber >= 0 ; --chunkNumber) { + var chunk = this._textChunks[chunkNumber]; + if (chunk.startLine + chunk.linesCount <= this._textModel.linesCount) + break; + chunk.expanded = false; + this._container.removeChild(chunk.element); + } + this._textChunks.length = chunkNumber + 1; + + // Add new chunks (if needed). + var totalLines = 0; + if (this._textChunks.length) { + var lastChunk = this._textChunks[this._textChunks.length - 1]; + totalLines = lastChunk.startLine + lastChunk.linesCount; + } + for (var i = totalLines; i < this._textModel.linesCount; i += this._defaultChunkSize) { + var chunk = this._createNewChunk(i, i + this._defaultChunkSize); + this._textChunks.push(chunk); + this._container.appendChild(chunk.element); + } + this._repaintAll(); + } else { + // Decorations may have been removed, so we may have to sync those lines. + var chunkNumber = this._chunkNumberForLine(newRange.startLine); + var chunk = this._textChunks[chunkNumber]; + while (chunk && chunk.startLine <= newRange.endLine) { + if (chunk.linesCount === 1) + this._syncDecorationsForLineListener(chunk.startLine); + chunk = this._textChunks[++chunkNumber]; + } + } + + this.endDomUpdates(); + }, + + syncClientHeight: function(clientHeight) + { + if (this.element.offsetHeight > clientHeight) + this._container.style.setProperty("padding-bottom", (this.element.offsetHeight - clientHeight) + "px"); + else + this._container.style.removeProperty("padding-bottom"); + } +} + +WebInspector.TextEditorGutterPanel.prototype.__proto__ = WebInspector.TextEditorChunkedPanel.prototype; + +/** + * @constructor + */ +WebInspector.TextEditorGutterChunk = function(textViewer, startLine, endLine) +{ + this._textViewer = textViewer; + this._textModel = textViewer._textModel; + + this.startLine = startLine; + endLine = Math.min(this._textModel.linesCount, endLine); + this.linesCount = endLine - startLine; + + this._expanded = false; + + this.element = document.createElement("div"); + this.element.lineNumber = startLine; + this.element.className = "webkit-line-number"; + + if (this.linesCount === 1) { + // Single line chunks are typically created for decorations. Host line number in + // the sub-element in order to allow flexible border / margin management. + var innerSpan = document.createElement("span"); + innerSpan.className = "webkit-line-number-inner"; + innerSpan.textContent = startLine + 1; + var outerSpan = document.createElement("div"); + outerSpan.className = "webkit-line-number-outer"; + outerSpan.appendChild(innerSpan); + this.element.appendChild(outerSpan); + } else { + var lineNumbers = []; + for (var i = startLine; i < endLine; ++i) + lineNumbers.push(i + 1); + this.element.textContent = lineNumbers.join("\n"); + } +} + +WebInspector.TextEditorGutterChunk.prototype = { + addDecoration: function(decoration) + { + this._textViewer.beginDomUpdates(); + if (typeof decoration === "string") + this.element.addStyleClass(decoration); + this._textViewer.endDomUpdates(); + }, + + removeDecoration: function(decoration) + { + this._textViewer.beginDomUpdates(); + if (typeof decoration === "string") + this.element.removeStyleClass(decoration); + this._textViewer.endDomUpdates(); + }, + + get expanded() + { + return this._expanded; + }, + + set expanded(expanded) + { + if (this.linesCount === 1) + this._textViewer._syncDecorationsForLineListener(this.startLine); + + if (this._expanded === expanded) + return; + + this._expanded = expanded; + + if (this.linesCount === 1) + return; + + this._textViewer.beginDomUpdates(); + + if (expanded) { + this._expandedLineRows = []; + var parentElement = this.element.parentElement; + for (var i = this.startLine; i < this.startLine + this.linesCount; ++i) { + var lineRow = this._createRow(i); + parentElement.insertBefore(lineRow, this.element); + this._expandedLineRows.push(lineRow); + } + parentElement.removeChild(this.element); + this._textViewer._syncLineHeightListener(this._expandedLineRows[0]); + } else { + var elementInserted = false; + for (var i = 0; i < this._expandedLineRows.length; ++i) { + var lineRow = this._expandedLineRows[i]; + var parentElement = lineRow.parentElement; + if (parentElement) { + if (!elementInserted) { + elementInserted = true; + parentElement.insertBefore(this.element, lineRow); + } + parentElement.removeChild(lineRow); + } + this._textViewer._cachedRows.push(lineRow); + } + delete this._expandedLineRows; + } + + this._textViewer.endDomUpdates(); + }, + + get height() + { + if (!this._expandedLineRows) + return this._textViewer._totalHeight(this.element); + return this._textViewer._totalHeight(this._expandedLineRows[0], this._expandedLineRows[this._expandedLineRows.length - 1]); + }, + + get offsetTop() + { + return (this._expandedLineRows && this._expandedLineRows.length) ? this._expandedLineRows[0].offsetTop : this.element.offsetTop; + }, + + _createRow: function(lineNumber) + { + var lineRow = this._textViewer._cachedRows.pop() || document.createElement("div"); + lineRow.lineNumber = lineNumber; + lineRow.className = "webkit-line-number"; + lineRow.textContent = lineNumber + 1; + return lineRow; + } +} + +/** + * @constructor + * @extends {WebInspector.TextEditorChunkedPanel} + */ +WebInspector.TextEditorMainPanel = function(textModel, url, syncScrollListener, syncDecorationsForLineListener, enterTextChangeMode, exitTextChangeMode) +{ + WebInspector.TextEditorChunkedPanel.call(this, textModel); + + this._syncScrollListener = syncScrollListener; + this._syncDecorationsForLineListener = syncDecorationsForLineListener; + this._enterTextChangeMode = enterTextChangeMode; + this._exitTextChangeMode = exitTextChangeMode; + + this._url = url; + this._highlighter = new WebInspector.TextEditorHighlighter(textModel, this._highlightDataReady.bind(this)); + this._readOnly = true; + + this.element = document.createElement("div"); + this.element.className = "text-editor-contents"; + this.element.tabIndex = 0; + + this._container = document.createElement("div"); + this._container.className = "inner-container"; + this._container.tabIndex = 0; + this.element.appendChild(this._container); + + this.element.addEventListener("scroll", this._scroll.bind(this), false); + + // In WebKit the DOMNodeRemoved event is fired AFTER the node is removed, thus it should be + // attached to all DOM nodes that we want to track. Instead, we attach the DOMNodeRemoved + // listeners only on the line rows, and use DOMSubtreeModified to track node removals inside + // the line rows. For more info see: https://bugs.webkit.org/show_bug.cgi?id=55666 + // + // OPTIMIZATION. It is very expensive to listen to the DOM mutation events, thus we remove the + // listeners whenever we do any internal DOM manipulations (such as expand/collapse line rows) + // and set the listeners back when we are finished. + this._handleDOMUpdatesCallback = this._handleDOMUpdates.bind(this); + this._container.addEventListener("DOMCharacterDataModified", this._handleDOMUpdatesCallback, false); + this._container.addEventListener("DOMNodeInserted", this._handleDOMUpdatesCallback, false); + this._container.addEventListener("DOMSubtreeModified", this._handleDOMUpdatesCallback, false); + + this.freeCachedElements(); + this._buildChunks(); +} + +WebInspector.TextEditorMainPanel.prototype = { + set mimeType(mimeType) + { + this._highlighter.mimeType = mimeType; + }, + + set readOnly(readOnly) + { + if (this._readOnly === readOnly) + return; + + this.beginDomUpdates(); + this._readOnly = readOnly; + if (this._readOnly) + this._container.removeStyleClass("text-editor-editable"); + else { + this._container.addStyleClass("text-editor-editable"); + this._updateSelectionOnStartEditing(); + } + this.endDomUpdates(); + }, + + get readOnly() + { + return this._readOnly; + }, + + _updateSelectionOnStartEditing: function() + { + // focus() needs to go first for the case when the last selection was inside the editor and + // the "Edit" button was clicked. In this case we bail at the check below, but the + // editor does not receive the focus, thus "Esc" does not cancel editing until at least + // one change has been made to the editor contents. + this._container.focus(); + var selection = window.getSelection(); + if (selection.rangeCount) { + var commonAncestorContainer = selection.getRangeAt(0).commonAncestorContainer; + if (this._container.isSelfOrAncestor(commonAncestorContainer)) + return; + } + + selection.removeAllRanges(); + var range = document.createRange(); + range.setStart(this._container, 0); + range.setEnd(this._container, 0); + selection.addRange(range); + }, + + setEditableRange: function(startLine, endLine) + { + this.beginDomUpdates(); + + var firstChunkNumber = this._chunkNumberForLine(startLine); + var firstChunk = this._textChunks[firstChunkNumber]; + if (firstChunk.startLine !== startLine) { + this._splitChunkOnALine(startLine, firstChunkNumber); + firstChunkNumber += 1; + } + + var lastChunkNumber = this._textChunks.length; + if (endLine !== this._textModel.linesCount) { + lastChunkNumber = this._chunkNumberForLine(endLine); + var lastChunk = this._textChunks[lastChunkNumber]; + if (lastChunk && lastChunk.startLine !== endLine) { + this._splitChunkOnALine(endLine, lastChunkNumber); + lastChunkNumber += 1; + } + } + + for (var chunkNumber = 0; chunkNumber < firstChunkNumber; ++chunkNumber) + this._textChunks[chunkNumber].readOnly = true; + for (var chunkNumber = firstChunkNumber; chunkNumber < lastChunkNumber; ++chunkNumber) + this._textChunks[chunkNumber].readOnly = false; + for (var chunkNumber = lastChunkNumber; chunkNumber < this._textChunks.length; ++chunkNumber) + this._textChunks[chunkNumber].readOnly = true; + + this.endDomUpdates(); + }, + + clearEditableRange: function() + { + for (var chunkNumber = 0; chunkNumber < this._textChunks.length; ++chunkNumber) + this._textChunks[chunkNumber].readOnly = false; + }, + + markAndRevealRange: function(range) + { + if (this._rangeToMark) { + var markedLine = this._rangeToMark.startLine; + delete this._rangeToMark; + // Remove the marked region immediately. + if (!this._dirtyLines) { + this.beginDomUpdates(); + var chunk = this.chunkForLine(markedLine); + var wasExpanded = chunk.expanded; + chunk.expanded = false; + chunk.updateCollapsedLineRow(); + chunk.expanded = wasExpanded; + this.endDomUpdates(); + } else + this._paintLines(markedLine, markedLine + 1); + } + + if (range) { + this._rangeToMark = range; + this.revealLine(range.startLine); + var chunk = this.makeLineAChunk(range.startLine); + this._paintLine(chunk.element); + if (this._markedRangeElement) + this._markedRangeElement.scrollIntoViewIfNeeded(); + } + delete this._markedRangeElement; + }, + + highlightLine: function(lineNumber) + { + this.clearLineHighlight(); + this._highlightedLine = lineNumber; + this.revealLine(lineNumber); + this.addDecoration(lineNumber, "webkit-highlighted-line"); + }, + + clearLineHighlight: function() + { + if (typeof this._highlightedLine === "number") { + this.removeDecoration(this._highlightedLine, "webkit-highlighted-line"); + delete this._highlightedLine; + } + }, + + freeCachedElements: function() + { + this._cachedSpans = []; + this._cachedTextNodes = []; + this._cachedRows = []; + }, + + handleUndoRedo: function(redo) + { + if (this._dirtyLines) + return false; + + this.beginUpdates(); + this._enterTextChangeMode(); + + var callback = function(oldRange, newRange) { + this._exitTextChangeMode(oldRange, newRange); + this._enterTextChangeMode(); + }.bind(this); + + var range = redo ? this._textModel.redo(callback) : this._textModel.undo(callback); + if (range) + this._setCaretLocation(range.endLine, range.endColumn, true); + + this._exitTextChangeMode(null, null); + this.endUpdates(); + + return true; + }, + + handleTabKeyPress: function(shiftKey) + { + if (this._dirtyLines) + return false; + + var selection = this._getSelection(); + if (!selection) + return false; + + var range = selection.normalize(); + + this.beginUpdates(); + this._enterTextChangeMode(); + + var newRange; + if (shiftKey) + newRange = this._unindentLines(range); + else { + if (range.isEmpty()) { + newRange = this._setText(range, WebInspector.settings.textEditorIndent.get()); + newRange.startColumn = newRange.endColumn; + } else + newRange = this._indentLines(range); + + } + + this._exitTextChangeMode(range, newRange); + this.endUpdates(); + this._restoreSelection(newRange, true); + return true; + }, + + _indentLines: function(range) + { + var indent = WebInspector.settings.textEditorIndent.get(); + + if (this._lastEditedRange) + this._textModel.markUndoableState(); + + for (var lineNumber = range.startLine; lineNumber <= range.endLine; lineNumber++) + this._textModel.setText(new WebInspector.TextRange(lineNumber, 0, lineNumber, 0), indent); + + var newRange = range.clone(); + newRange.startColumn += indent.length; + newRange.endColumn += indent.length; + this._lastEditedRange = newRange; + + return newRange; + }, + + _unindentLines: function(range) + { + if (this._lastEditedRange) + this._textModel.markUndoableState(); + + var indent = WebInspector.settings.textEditorIndent.get(); + var indentLength = indent === WebInspector.TextEditorModel.Indent.TabCharacter ? 4 : indent.length; + var lineIndentRegex = new RegExp("^ {1," + indentLength + "}"); + var newRange = range.clone(); + + for (var lineNumber = range.startLine; lineNumber <= range.endLine; lineNumber++) { + var line = this._textModel.line(lineNumber); + var firstCharacter = line.charAt(0); + var lineIndentLength; + + if (firstCharacter === " ") + lineIndentLength = line.match(lineIndentRegex)[0].length; + else if (firstCharacter === "\t") + lineIndentLength = 1; + else + continue; + + this._textModel.setText(new WebInspector.TextRange(lineNumber, 0, lineNumber, lineIndentLength), ""); + + if (lineNumber === range.startLine) + newRange.startColumn = Math.max(0, newRange.startColumn - lineIndentLength); + } + + if (lineIndentLength) + newRange.endColumn = Math.max(0, newRange.endColumn - lineIndentLength); + + this._lastEditedRange = newRange; + + return newRange; + }, + + handleEnterKey: function() + { + if (this._dirtyLines) + return false; + + var range = this._getSelection(); + if (!range) + return false; + + range.normalize(); + + if (range.endColumn === 0) + return false; + + var line = this._textModel.line(range.startLine); + var linePrefix = line.substring(0, range.startColumn); + var indentMatch = linePrefix.match(/^\s+/); + var currentIndent = indentMatch ? indentMatch[0] : ""; + + var textEditorIndent = WebInspector.settings.textEditorIndent.get(); + var indent = WebInspector.TextEditorModel.endsWithBracketRegex.test(linePrefix) ? currentIndent + textEditorIndent : currentIndent; + + if (!indent) + return false; + + this.beginUpdates(); + this._enterTextChangeMode(); + + var lineBreak = this._textModel.lineBreak; + var newRange; + if (range.isEmpty() && line.substr(range.endColumn - 1, 2) === '{}') { + // {|} + // becomes + // { + // | + // } + newRange = this._setText(range, lineBreak + indent + lineBreak + currentIndent); + newRange.endLine--; + newRange.endColumn += textEditorIndent.length; + } else + newRange = this._setText(range, lineBreak + indent); + + newRange = newRange.collapseToEnd(); + + this._exitTextChangeMode(range, newRange); + this.endUpdates(); + this._restoreSelection(newRange, true); + + return true; + }, + + _splitChunkOnALine: function(lineNumber, chunkNumber, createSuffixChunk) + { + var selection = this._getSelection(); + var chunk = WebInspector.TextEditorChunkedPanel.prototype._splitChunkOnALine.call(this, lineNumber, chunkNumber, createSuffixChunk); + this._restoreSelection(selection); + return chunk; + }, + + beginDomUpdates: function() + { + WebInspector.TextEditorChunkedPanel.prototype.beginDomUpdates.call(this); + if (this._domUpdateCoalescingLevel === 1) { + this._container.removeEventListener("DOMCharacterDataModified", this._handleDOMUpdatesCallback, false); + this._container.removeEventListener("DOMNodeInserted", this._handleDOMUpdatesCallback, false); + this._container.removeEventListener("DOMSubtreeModified", this._handleDOMUpdatesCallback, false); + } + }, + + endDomUpdates: function() + { + WebInspector.TextEditorChunkedPanel.prototype.endDomUpdates.call(this); + if (this._domUpdateCoalescingLevel === 0) { + this._container.addEventListener("DOMCharacterDataModified", this._handleDOMUpdatesCallback, false); + this._container.addEventListener("DOMNodeInserted", this._handleDOMUpdatesCallback, false); + this._container.addEventListener("DOMSubtreeModified", this._handleDOMUpdatesCallback, false); + } + }, + + _enableDOMNodeRemovedListener: function(lineRow, enable) + { + if (enable) + lineRow.addEventListener("DOMNodeRemoved", this._handleDOMUpdatesCallback, false); + else + lineRow.removeEventListener("DOMNodeRemoved", this._handleDOMUpdatesCallback, false); + }, + + _buildChunks: function() + { + for (var i = 0; i < this._textModel.linesCount; ++i) + this._textModel.removeAttribute(i, "highlight"); + + WebInspector.TextEditorChunkedPanel.prototype._buildChunks.call(this); + }, + + _createNewChunk: function(startLine, endLine) + { + return new WebInspector.TextEditorMainChunk(this, startLine, endLine); + }, + + _expandChunks: function(fromIndex, toIndex) + { + var lastChunk = this._textChunks[toIndex - 1]; + var lastVisibleLine = lastChunk.startLine + lastChunk.linesCount; + + var selection = this._getSelection(); + + this._muteHighlightListener = true; + this._highlighter.highlight(lastVisibleLine); + delete this._muteHighlightListener; + + this._restorePaintLinesOperationsCredit(); + WebInspector.TextEditorChunkedPanel.prototype._expandChunks.call(this, fromIndex, toIndex); + this._adjustPaintLinesOperationsRefreshValue(); + + this._restoreSelection(selection); + }, + + _highlightDataReady: function(fromLine, toLine) + { + if (this._muteHighlightListener) + return; + this._restorePaintLinesOperationsCredit(); + this._paintLines(fromLine, toLine, true /*restoreSelection*/); + }, + + _schedulePaintLines: function(startLine, endLine) + { + if (startLine >= endLine) + return; + + if (!this._scheduledPaintLines) { + this._scheduledPaintLines = [ { startLine: startLine, endLine: endLine } ]; + this._paintScheduledLinesTimer = setTimeout(this._paintScheduledLines.bind(this), 0); + } else { + for (var i = 0; i < this._scheduledPaintLines.length; ++i) { + var chunk = this._scheduledPaintLines[i]; + if (chunk.startLine <= endLine && chunk.endLine >= startLine) { + chunk.startLine = Math.min(chunk.startLine, startLine); + chunk.endLine = Math.max(chunk.endLine, endLine); + return; + } + if (chunk.startLine > endLine) { + this._scheduledPaintLines.splice(i, 0, { startLine: startLine, endLine: endLine }); + return; + } + } + this._scheduledPaintLines.push({ startLine: startLine, endLine: endLine }); + } + }, + + _paintScheduledLines: function(skipRestoreSelection) + { + if (this._paintScheduledLinesTimer) + clearTimeout(this._paintScheduledLinesTimer); + delete this._paintScheduledLinesTimer; + + if (!this._scheduledPaintLines) + return; + + // Reschedule the timer if we can not paint the lines yet, or the user is scrolling. + if (this._dirtyLines || this._repaintAllTimer) { + this._paintScheduledLinesTimer = setTimeout(this._paintScheduledLines.bind(this), 50); + return; + } + + var scheduledPaintLines = this._scheduledPaintLines; + delete this._scheduledPaintLines; + + this._restorePaintLinesOperationsCredit(); + this._paintLineChunks(scheduledPaintLines, !skipRestoreSelection); + this._adjustPaintLinesOperationsRefreshValue(); + }, + + _restorePaintLinesOperationsCredit: function() + { + if (!this._paintLinesOperationsRefreshValue) + this._paintLinesOperationsRefreshValue = 250; + this._paintLinesOperationsCredit = this._paintLinesOperationsRefreshValue; + this._paintLinesOperationsLastRefresh = Date.now(); + }, + + _adjustPaintLinesOperationsRefreshValue: function() + { + var operationsDone = this._paintLinesOperationsRefreshValue - this._paintLinesOperationsCredit; + if (operationsDone <= 0) + return; + var timePast = Date.now() - this._paintLinesOperationsLastRefresh; + if (timePast <= 0) + return; + // Make the synchronous CPU chunk for painting the lines 50 msec. + var value = Math.floor(operationsDone / timePast * 50); + this._paintLinesOperationsRefreshValue = Number.constrain(value, 150, 1500); + }, + + /** + * @param {boolean=} restoreSelection + */ + _paintLines: function(fromLine, toLine, restoreSelection) + { + this._paintLineChunks([ { startLine: fromLine, endLine: toLine } ], restoreSelection); + }, + + _paintLineChunks: function(lineChunks, restoreSelection) + { + // First, paint visible lines, so that in case of long lines we should start highlighting + // the visible area immediately, instead of waiting for the lines above the visible area. + var visibleFrom = this.element.scrollTop; + var firstVisibleLineNumber = this._findFirstVisibleLineNumber(visibleFrom); + + var chunk; + var selection; + var invisibleLineRows = []; + for (var i = 0; i < lineChunks.length; ++i) { + var lineChunk = lineChunks[i]; + if (this._dirtyLines || this._scheduledPaintLines) { + this._schedulePaintLines(lineChunk.startLine, lineChunk.endLine); + continue; + } + for (var lineNumber = lineChunk.startLine; lineNumber < lineChunk.endLine; ++lineNumber) { + if (!chunk || lineNumber < chunk.startLine || lineNumber >= chunk.startLine + chunk.linesCount) + chunk = this.chunkForLine(lineNumber); + var lineRow = chunk.getExpandedLineRow(lineNumber); + if (!lineRow) + continue; + if (lineNumber < firstVisibleLineNumber) { + invisibleLineRows.push(lineRow); + continue; + } + if (restoreSelection && !selection) + selection = this._getSelection(); + this._paintLine(lineRow); + if (this._paintLinesOperationsCredit < 0) { + this._schedulePaintLines(lineNumber + 1, lineChunk.endLine); + break; + } + } + } + + for (var i = 0; i < invisibleLineRows.length; ++i) { + if (restoreSelection && !selection) + selection = this._getSelection(); + this._paintLine(invisibleLineRows[i]); + } + + if (restoreSelection) + this._restoreSelection(selection); + }, + + _paintLine: function(lineRow) + { + var lineNumber = lineRow.lineNumber; + if (this._dirtyLines) { + this._schedulePaintLines(lineNumber, lineNumber + 1); + return; + } + + this.beginDomUpdates(); + try { + if (this._scheduledPaintLines || this._paintLinesOperationsCredit < 0) { + this._schedulePaintLines(lineNumber, lineNumber + 1); + return; + } + + var highlight = this._textModel.getAttribute(lineNumber, "highlight"); + if (!highlight) + return; + + lineRow.removeChildren(); + var line = this._textModel.line(lineNumber); + if (!line) + lineRow.appendChild(document.createElement("br")); + + var plainTextStart = -1; + for (var j = 0; j < line.length;) { + if (j > 1000) { + // This line is too long - do not waste cycles on minified js highlighting. + if (plainTextStart === -1) + plainTextStart = j; + break; + } + var attribute = highlight[j]; + if (!attribute || !attribute.tokenType) { + if (plainTextStart === -1) + plainTextStart = j; + j++; + } else { + if (plainTextStart !== -1) { + this._appendTextNode(lineRow, line.substring(plainTextStart, j)); + plainTextStart = -1; + --this._paintLinesOperationsCredit; + } + this._appendSpan(lineRow, line.substring(j, j + attribute.length), attribute.tokenType); + j += attribute.length; + --this._paintLinesOperationsCredit; + } + } + if (plainTextStart !== -1) { + this._appendTextNode(lineRow, line.substring(plainTextStart, line.length)); + --this._paintLinesOperationsCredit; + } + if (lineRow.decorationsElement) + lineRow.appendChild(lineRow.decorationsElement); + } finally { + if (this._rangeToMark && this._rangeToMark.startLine === lineNumber) + this._markedRangeElement = highlightSearchResult(lineRow, this._rangeToMark.startColumn, this._rangeToMark.endColumn - this._rangeToMark.startColumn); + this.endDomUpdates(); + } + }, + + _releaseLinesHighlight: function(lineRow) + { + if (!lineRow) + return; + if ("spans" in lineRow) { + var spans = lineRow.spans; + for (var j = 0; j < spans.length; ++j) + this._cachedSpans.push(spans[j]); + delete lineRow.spans; + } + if ("textNodes" in lineRow) { + var textNodes = lineRow.textNodes; + for (var j = 0; j < textNodes.length; ++j) + this._cachedTextNodes.push(textNodes[j]); + delete lineRow.textNodes; + } + this._cachedRows.push(lineRow); + }, + + _getSelection: function() + { + var selection = window.getSelection(); + if (!selection.rangeCount) + return null; + // Selection may be outside of the viewer. + if (!this._container.isAncestor(selection.anchorNode) || !this._container.isAncestor(selection.focusNode)) + return null; + var start = this._selectionToPosition(selection.anchorNode, selection.anchorOffset); + var end = selection.isCollapsed ? start : this._selectionToPosition(selection.focusNode, selection.focusOffset); + return new WebInspector.TextRange(start.line, start.column, end.line, end.column); + }, + + /** + * @param {boolean=} scrollIntoView + */ + _restoreSelection: function(range, scrollIntoView) + { + if (!range) + return; + var start = this._positionToSelection(range.startLine, range.startColumn); + var end = range.isEmpty() ? start : this._positionToSelection(range.endLine, range.endColumn); + window.getSelection().setBaseAndExtent(start.container, start.offset, end.container, end.offset); + + if (scrollIntoView) { + for (var node = end.container; node; node = node.parentElement) { + if (node.scrollIntoViewIfNeeded) { + node.scrollIntoViewIfNeeded(); + break; + } + } + } + }, + + _setCaretLocation: function(line, column, scrollIntoView) + { + var range = new WebInspector.TextRange(line, column, line, column); + this._restoreSelection(range, scrollIntoView); + }, + + _selectionToPosition: function(container, offset) + { + if (container === this._container && offset === 0) + return { line: 0, column: 0 }; + if (container === this._container && offset === 1) + return { line: this._textModel.linesCount - 1, column: this._textModel.lineLength(this._textModel.linesCount - 1) }; + + var lineRow = this._enclosingLineRowOrSelf(container); + var lineNumber = lineRow.lineNumber; + if (container === lineRow && offset === 0) + return { line: lineNumber, column: 0 }; + + // This may be chunk and chunks may contain \n. + var column = 0; + var node = lineRow.nodeType === Node.TEXT_NODE ? lineRow : lineRow.traverseNextTextNode(lineRow); + while (node && node !== container) { + var text = node.textContent; + for (var i = 0; i < text.length; ++i) { + if (text.charAt(i) === "\n") { + lineNumber++; + column = 0; + } else + column++; + } + node = node.traverseNextTextNode(lineRow); + } + + if (node === container && offset) { + var text = node.textContent; + for (var i = 0; i < offset; ++i) { + if (text.charAt(i) === "\n") { + lineNumber++; + column = 0; + } else + column++; + } + } + return { line: lineNumber, column: column }; + }, + + _positionToSelection: function(line, column) + { + var chunk = this.chunkForLine(line); + // One-lined collapsed chunks may still stay highlighted. + var lineRow = chunk.linesCount === 1 ? chunk.element : chunk.getExpandedLineRow(line); + if (lineRow) + var rangeBoundary = lineRow.rangeBoundaryForOffset(column); + else { + var offset = column; + for (var i = chunk.startLine; i < line; ++i) + offset += this._textModel.lineLength(i) + 1; // \n + lineRow = chunk.element; + if (lineRow.firstChild) + var rangeBoundary = { container: lineRow.firstChild, offset: offset }; + else + var rangeBoundary = { container: lineRow, offset: 0 }; + } + return rangeBoundary; + }, + + _enclosingLineRowOrSelf: function(element) + { + var lineRow = element.enclosingNodeOrSelfWithClass("webkit-line-content"); + if (lineRow) + return lineRow; + + for (lineRow = element; lineRow; lineRow = lineRow.parentElement) { + if (lineRow.parentElement === this._container) + return lineRow; + } + return null; + }, + + _appendSpan: function(element, content, className) + { + if (className === "html-resource-link" || className === "html-external-link") { + element.appendChild(this._createLink(content, className === "html-external-link")); + return; + } + + var span = this._cachedSpans.pop() || document.createElement("span"); + span.className = "webkit-" + className; + span.textContent = content; + element.appendChild(span); + if (!("spans" in element)) + element.spans = []; + element.spans.push(span); + }, + + _appendTextNode: function(element, text) + { + var textNode = this._cachedTextNodes.pop(); + if (textNode) + textNode.nodeValue = text; + else + textNode = document.createTextNode(text); + element.appendChild(textNode); + if (!("textNodes" in element)) + element.textNodes = []; + element.textNodes.push(textNode); + }, + + _createLink: function(content, isExternal) + { + var quote = content.charAt(0); + if (content.length > 1 && (quote === "\"" || quote === "'")) + content = content.substring(1, content.length - 1); + else + quote = null; + + var a = WebInspector.linkifyURLAsNode(this._rewriteHref(content), content, undefined, isExternal); + var span = document.createElement("span"); + span.className = "webkit-html-attribute-value"; + if (quote) + span.appendChild(document.createTextNode(quote)); + span.appendChild(a); + if (quote) + span.appendChild(document.createTextNode(quote)); + return span; + }, + + /** + * @param {boolean=} isExternal + */ + _rewriteHref: function(hrefValue, isExternal) + { + if (!this._url || !hrefValue || hrefValue.indexOf("://") > 0) + return hrefValue; + return WebInspector.completeURL(this._url, hrefValue); + }, + + _handleDOMUpdates: function(e) + { + if (this._domUpdateCoalescingLevel) + return; + + var target = e.target; + if (target === this._container) + return; + + var lineRow = this._enclosingLineRowOrSelf(target); + if (!lineRow) + return; + + if (lineRow.decorationsElement && lineRow.decorationsElement.isSelfOrAncestor(target)) { + if (this._syncDecorationsForLineListener) + this._syncDecorationsForLineListener(lineRow.lineNumber); + return; + } + + if (this._readOnly) + return; + + if (target === lineRow && e.type === "DOMNodeInserted") { + // Ensure that the newly inserted line row has no lineNumber. + delete lineRow.lineNumber; + } + + var startLine = 0; + for (var row = lineRow; row; row = row.previousSibling) { + if (typeof row.lineNumber === "number") { + startLine = row.lineNumber; + break; + } + } + + var endLine = startLine + 1; + for (var row = lineRow.nextSibling; row; row = row.nextSibling) { + if (typeof row.lineNumber === "number" && row.lineNumber > startLine) { + endLine = row.lineNumber; + break; + } + } + + if (target === lineRow && e.type === "DOMNodeRemoved") { + // Now this will no longer be valid. + delete lineRow.lineNumber; + } + + if (this._dirtyLines) { + this._dirtyLines.start = Math.min(this._dirtyLines.start, startLine); + this._dirtyLines.end = Math.max(this._dirtyLines.end, endLine); + } else { + this._dirtyLines = { start: startLine, end: endLine }; + setTimeout(this._applyDomUpdates.bind(this), 0); + // Remove marked ranges, if any. + this.markAndRevealRange(null); + } + }, + + _applyDomUpdates: function() + { + if (!this._dirtyLines) + return; + + // Check if the editor had been set readOnly by the moment when this async callback got executed. + if (this._readOnly) { + delete this._dirtyLines; + return; + } + + // This is a "foreign" call outside of this class. Should be before we delete the dirty lines flag. + this._enterTextChangeMode(); + + var dirtyLines = this._dirtyLines; + delete this._dirtyLines; + + var firstChunkNumber = this._chunkNumberForLine(dirtyLines.start); + var startLine = this._textChunks[firstChunkNumber].startLine; + var endLine = this._textModel.linesCount; + + // Collect lines. + var firstLineRow; + if (firstChunkNumber) { + var chunk = this._textChunks[firstChunkNumber - 1]; + firstLineRow = chunk.expanded ? chunk.getExpandedLineRow(chunk.startLine + chunk.linesCount - 1) : chunk.element; + firstLineRow = firstLineRow.nextSibling; + } else + firstLineRow = this._container.firstChild; + + var lines = []; + for (var lineRow = firstLineRow; lineRow; lineRow = lineRow.nextSibling) { + if (typeof lineRow.lineNumber === "number" && lineRow.lineNumber >= dirtyLines.end) { + endLine = lineRow.lineNumber; + break; + } + // Update with the newest lineNumber, so that the call to the _getSelection method below should work. + lineRow.lineNumber = startLine + lines.length; + this._collectLinesFromDiv(lines, lineRow); + } + + // Try to decrease the range being replaced, if possible. + var startOffset = 0; + while (startLine < dirtyLines.start && startOffset < lines.length) { + if (this._textModel.line(startLine) !== lines[startOffset]) + break; + ++startOffset; + ++startLine; + } + + var endOffset = lines.length; + while (endLine > dirtyLines.end && endOffset > startOffset) { + if (this._textModel.line(endLine - 1) !== lines[endOffset - 1]) + break; + --endOffset; + --endLine; + } + + lines = lines.slice(startOffset, endOffset); + + // Try to decrease the range being replaced by column offsets, if possible. + var startColumn = 0; + var endColumn = this._textModel.lineLength(endLine - 1); + if (lines.length > 0) { + var line1 = this._textModel.line(startLine); + var line2 = lines[0]; + while (line1[startColumn] && line1[startColumn] === line2[startColumn]) + ++startColumn; + lines[0] = line2.substring(startColumn); + + line1 = this._textModel.line(endLine - 1); + line2 = lines[lines.length - 1]; + for (var i = 0; i < endColumn && i < line2.length; ++i) { + if (startLine === endLine - 1 && endColumn - i <= startColumn) + break; + if (line1[endColumn - i - 1] !== line2[line2.length - i - 1]) + break; + } + if (i) { + endColumn -= i; + lines[lines.length - 1] = line2.substring(0, line2.length - i); + } + } + + var selection = this._getSelection(); + + if (lines.length === 0 && endLine < this._textModel.linesCount) + var oldRange = new WebInspector.TextRange(startLine, 0, endLine, 0); + else if (lines.length === 0 && startLine > 0) + var oldRange = new WebInspector.TextRange(startLine - 1, this._textModel.lineLength(startLine - 1), endLine - 1, this._textModel.lineLength(endLine - 1)); + else + var oldRange = new WebInspector.TextRange(startLine, startColumn, endLine - 1, endColumn); + + var newRange = this._setText(oldRange, lines.join("\n")); + + this._paintScheduledLines(true); + this._restoreSelection(selection); + + this._exitTextChangeMode(oldRange, newRange); + }, + + textChanged: function(oldRange, newRange) + { + this.beginDomUpdates(); + this._removeDecorationsInRange(oldRange); + this._updateChunksForRanges(oldRange, newRange); + this._updateHighlightsForRange(newRange); + this.endDomUpdates(); + }, + + _setText: function(range, text) + { + if (this._lastEditedRange && (!text || text.indexOf("\n") !== -1 || this._lastEditedRange.endLine !== range.startLine || this._lastEditedRange.endColumn !== range.startColumn)) + this._textModel.markUndoableState(); + + var newRange = this._textModel.setText(range, text); + this._lastEditedRange = newRange; + + return newRange; + }, + + _removeDecorationsInRange: function(range) + { + for (var i = this._chunkNumberForLine(range.startLine); i < this._textChunks.length; ++i) { + var chunk = this._textChunks[i]; + if (chunk.startLine > range.endLine) + break; + chunk.removeAllDecorations(); + } + }, + + _updateChunksForRanges: function(oldRange, newRange) + { + // Update the chunks in range: firstChunkNumber <= index <= lastChunkNumber + var firstChunkNumber = this._chunkNumberForLine(oldRange.startLine); + var lastChunkNumber = firstChunkNumber; + while (lastChunkNumber + 1 < this._textChunks.length) { + if (this._textChunks[lastChunkNumber + 1].startLine > oldRange.endLine) + break; + ++lastChunkNumber; + } + + var startLine = this._textChunks[firstChunkNumber].startLine; + var linesCount = this._textChunks[lastChunkNumber].startLine + this._textChunks[lastChunkNumber].linesCount - startLine; + var linesDiff = newRange.linesCount - oldRange.linesCount; + linesCount += linesDiff; + + if (linesDiff) { + // Lines shifted, update the line numbers of the chunks below. + for (var chunkNumber = lastChunkNumber + 1; chunkNumber < this._textChunks.length; ++chunkNumber) + this._textChunks[chunkNumber].startLine += linesDiff; + } + + var firstLineRow; + if (firstChunkNumber) { + var chunk = this._textChunks[firstChunkNumber - 1]; + firstLineRow = chunk.expanded ? chunk.getExpandedLineRow(chunk.startLine + chunk.linesCount - 1) : chunk.element; + firstLineRow = firstLineRow.nextSibling; + } else + firstLineRow = this._container.firstChild; + + // Most frequent case: a chunk remained the same. + for (var chunkNumber = firstChunkNumber; chunkNumber <= lastChunkNumber; ++chunkNumber) { + var chunk = this._textChunks[chunkNumber]; + if (chunk.startLine + chunk.linesCount > this._textModel.linesCount) + break; + var lineNumber = chunk.startLine; + for (var lineRow = firstLineRow; lineRow && lineNumber < chunk.startLine + chunk.linesCount; lineRow = lineRow.nextSibling) { + if (lineRow.lineNumber !== lineNumber || lineRow !== chunk.getExpandedLineRow(lineNumber) || lineRow.textContent !== this._textModel.line(lineNumber) || !lineRow.firstChild) + break; + ++lineNumber; + } + if (lineNumber < chunk.startLine + chunk.linesCount) + break; + chunk.updateCollapsedLineRow(); + ++firstChunkNumber; + firstLineRow = lineRow; + startLine += chunk.linesCount; + linesCount -= chunk.linesCount; + } + + if (firstChunkNumber > lastChunkNumber && linesCount === 0) + return; + + // Maybe merge with the next chunk, so that we should not create 1-sized chunks when appending new lines one by one. + var chunk = this._textChunks[lastChunkNumber + 1]; + var linesInLastChunk = linesCount % this._defaultChunkSize; + if (chunk && !chunk.decorated && linesInLastChunk > 0 && linesInLastChunk + chunk.linesCount <= this._defaultChunkSize) { + ++lastChunkNumber; + linesCount += chunk.linesCount; + } + + var scrollTop = this.element.scrollTop; + var scrollLeft = this.element.scrollLeft; + + // Delete all DOM elements that were either controlled by the old chunks, or have just been inserted. + var firstUnmodifiedLineRow = null; + chunk = this._textChunks[lastChunkNumber + 1]; + if (chunk) + firstUnmodifiedLineRow = chunk.expanded ? chunk.getExpandedLineRow(chunk.startLine) : chunk.element; + + while (firstLineRow && firstLineRow !== firstUnmodifiedLineRow) { + var lineRow = firstLineRow; + firstLineRow = firstLineRow.nextSibling; + this._container.removeChild(lineRow); + } + + // Replace old chunks with the new ones. + for (var chunkNumber = firstChunkNumber; linesCount > 0; ++chunkNumber) { + var chunkLinesCount = Math.min(this._defaultChunkSize, linesCount); + var newChunk = this._createNewChunk(startLine, startLine + chunkLinesCount); + this._container.insertBefore(newChunk.element, firstUnmodifiedLineRow); + + if (chunkNumber <= lastChunkNumber) + this._textChunks[chunkNumber] = newChunk; + else + this._textChunks.splice(chunkNumber, 0, newChunk); + startLine += chunkLinesCount; + linesCount -= chunkLinesCount; + } + if (chunkNumber <= lastChunkNumber) + this._textChunks.splice(chunkNumber, lastChunkNumber - chunkNumber + 1); + + this.element.scrollTop = scrollTop; + this.element.scrollLeft = scrollLeft; + }, + + _updateHighlightsForRange: function(range) + { + var visibleFrom = this.element.scrollTop; + var visibleTo = this.element.scrollTop + this.element.clientHeight; + + var result = this._findVisibleChunks(visibleFrom, visibleTo); + var chunk = this._textChunks[result.end - 1]; + var lastVisibleLine = chunk.startLine + chunk.linesCount; + + lastVisibleLine = Math.max(lastVisibleLine, range.endLine + 1); + lastVisibleLine = Math.min(lastVisibleLine, this._textModel.linesCount); + + var updated = this._highlighter.updateHighlight(range.startLine, lastVisibleLine); + if (!updated) { + // Highlights for the chunks below are invalid, so just collapse them. + for (var i = this._chunkNumberForLine(range.startLine); i < this._textChunks.length; ++i) + this._textChunks[i].expanded = false; + } + + this._repaintAll(); + }, + + _collectLinesFromDiv: function(lines, element) + { + var textContents = []; + var node = element.nodeType === Node.TEXT_NODE ? element : element.traverseNextNode(element); + while (node) { + if (element.decorationsElement === node) { + node = node.nextSibling; + continue; + } + if (node.nodeName.toLowerCase() === "br") + textContents.push("\n"); + else if (node.nodeType === Node.TEXT_NODE) + textContents.push(node.textContent); + node = node.traverseNextNode(element); + } + + var textContent = textContents.join(""); + // The last \n (if any) does not "count" in a DIV. + textContent = textContent.replace(/\n$/, ""); + + textContents = textContent.split("\n"); + for (var i = 0; i < textContents.length; ++i) + lines.push(textContents[i]); + } +} + +WebInspector.TextEditorMainPanel.prototype.__proto__ = WebInspector.TextEditorChunkedPanel.prototype; + +/** + * @constructor + */ +WebInspector.TextEditorMainChunk = function(textViewer, startLine, endLine) +{ + this._textViewer = textViewer; + this._textModel = textViewer._textModel; + + this.element = document.createElement("div"); + this.element.lineNumber = startLine; + this.element.className = "webkit-line-content"; + this._textViewer._enableDOMNodeRemovedListener(this.element, true); + + this._startLine = startLine; + endLine = Math.min(this._textModel.linesCount, endLine); + this.linesCount = endLine - startLine; + + this._expanded = false; + this._readOnly = false; + + this.updateCollapsedLineRow(); +} + +WebInspector.TextEditorMainChunk.prototype = { + addDecoration: function(decoration) + { + this._textViewer.beginDomUpdates(); + if (typeof decoration === "string") + this.element.addStyleClass(decoration); + else { + if (!this.element.decorationsElement) { + this.element.decorationsElement = document.createElement("div"); + this.element.decorationsElement.className = "webkit-line-decorations"; + this.element.appendChild(this.element.decorationsElement); + } + this.element.decorationsElement.appendChild(decoration); + } + this._textViewer.endDomUpdates(); + }, + + removeDecoration: function(decoration) + { + this._textViewer.beginDomUpdates(); + if (typeof decoration === "string") + this.element.removeStyleClass(decoration); + else if (this.element.decorationsElement) + this.element.decorationsElement.removeChild(decoration); + this._textViewer.endDomUpdates(); + }, + + removeAllDecorations: function() + { + this._textViewer.beginDomUpdates(); + this.element.className = "webkit-line-content"; + if (this.element.decorationsElement) { + this.element.removeChild(this.element.decorationsElement); + delete this.element.decorationsElement; + } + this._textViewer.endDomUpdates(); + }, + + get decorated() + { + return this.element.className !== "webkit-line-content" || !!(this.element.decorationsElement && this.element.decorationsElement.firstChild); + }, + + get startLine() + { + return this._startLine; + }, + + set startLine(startLine) + { + this._startLine = startLine; + this.element.lineNumber = startLine; + if (this._expandedLineRows) { + for (var i = 0; i < this._expandedLineRows.length; ++i) + this._expandedLineRows[i].lineNumber = startLine + i; + } + }, + + get expanded() + { + return this._expanded; + }, + + set expanded(expanded) + { + if (this._expanded === expanded) + return; + + this._expanded = expanded; + + if (this.linesCount === 1) { + if (expanded) + this._textViewer._paintLine(this.element); + return; + } + + this._textViewer.beginDomUpdates(); + + if (expanded) { + this._expandedLineRows = []; + var parentElement = this.element.parentElement; + for (var i = this.startLine; i < this.startLine + this.linesCount; ++i) { + var lineRow = this._createRow(i); + this._textViewer._enableDOMNodeRemovedListener(lineRow, true); + this._updateElementReadOnlyState(lineRow); + parentElement.insertBefore(lineRow, this.element); + this._expandedLineRows.push(lineRow); + } + this._textViewer._enableDOMNodeRemovedListener(this.element, false); + parentElement.removeChild(this.element); + this._textViewer._paintLines(this.startLine, this.startLine + this.linesCount); + } else { + var elementInserted = false; + for (var i = 0; i < this._expandedLineRows.length; ++i) { + var lineRow = this._expandedLineRows[i]; + this._textViewer._enableDOMNodeRemovedListener(lineRow, false); + var parentElement = lineRow.parentElement; + if (parentElement) { + if (!elementInserted) { + elementInserted = true; + this._textViewer._enableDOMNodeRemovedListener(this.element, true); + parentElement.insertBefore(this.element, lineRow); + } + parentElement.removeChild(lineRow); + } + this._textViewer._releaseLinesHighlight(lineRow); + } + delete this._expandedLineRows; + } + + this._textViewer.endDomUpdates(); + }, + + set readOnly(readOnly) + { + if (this._readOnly === readOnly) + return; + + this._readOnly = readOnly; + this._updateElementReadOnlyState(this.element); + if (this._expandedLineRows) { + for (var i = 0; i < this._expandedLineRows.length; ++i) + this._updateElementReadOnlyState(this._expandedLineRows[i]); + } + }, + + get readOnly() + { + return this._readOnly; + }, + + _updateElementReadOnlyState: function(element) + { + if (this._readOnly) + element.addStyleClass("text-editor-read-only"); + else + element.removeStyleClass("text-editor-read-only"); + }, + + get height() + { + if (!this._expandedLineRows) + return this._textViewer._totalHeight(this.element); + return this._textViewer._totalHeight(this._expandedLineRows[0], this._expandedLineRows[this._expandedLineRows.length - 1]); + }, + + get offsetTop() + { + return (this._expandedLineRows && this._expandedLineRows.length) ? this._expandedLineRows[0].offsetTop : this.element.offsetTop; + }, + + _createRow: function(lineNumber) + { + var lineRow = this._textViewer._cachedRows.pop() || document.createElement("div"); + lineRow.lineNumber = lineNumber; + lineRow.className = "webkit-line-content"; + lineRow.textContent = this._textModel.line(lineNumber); + if (!lineRow.textContent) + lineRow.appendChild(document.createElement("br")); + return lineRow; + }, + + getExpandedLineRow: function(lineNumber) + { + if (!this._expanded || lineNumber < this.startLine || lineNumber >= this.startLine + this.linesCount) + return null; + if (!this._expandedLineRows) + return this.element; + return this._expandedLineRows[lineNumber - this.startLine]; + }, + + updateCollapsedLineRow: function() + { + if (this.linesCount === 1 && this._expanded) + return; + + var lines = []; + for (var i = this.startLine; i < this.startLine + this.linesCount; ++i) + lines.push(this._textModel.line(i)); + + this.element.removeChildren(); + this.element.textContent = lines.join("\n"); + + // The last empty line will get swallowed otherwise. + if (!lines[lines.length - 1]) + this.element.appendChild(document.createElement("br")); + } +} +/* SourceFrame.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @extends {WebInspector.View} + * @constructor + */ +WebInspector.SourceFrame = function(url) +{ + WebInspector.View.call(this); + this.element.addStyleClass("script-view"); + + this._url = url; + + this._textModel = new WebInspector.TextEditorModel(); + + var textViewerDelegate = new WebInspector.TextViewerDelegateForSourceFrame(this); + this._textViewer = new WebInspector.TextViewer(this._textModel, WebInspector.platform(), this._url, textViewerDelegate); + + this._editButton = new WebInspector.StatusBarButton(WebInspector.UIString("Edit"), "edit-source-status-bar-item"); + this._editButton.addEventListener("click", this._editButtonClicked.bind(this), this); + + this._currentSearchResultIndex = -1; + this._searchResults = []; + + this._messages = []; + this._rowMessages = {}; + this._messageBubbles = {}; + + if (WebInspector.experimentsSettings.sourceFrameAlwaysEditable.isEnabled()) + this.startEditing(); +} + +WebInspector.SourceFrame.Events = { + Loaded: "loaded" +} + +WebInspector.SourceFrame.createSearchRegex = function(query) +{ + var regex; + + // First try creating regex if user knows the / / hint. + try { + if (/^\/.*\/$/.test(query)) + regex = new RegExp(query.substring(1, query.length - 1)); + } catch (e) { + // Silent catch. + } + + // Otherwise just do case-insensitive search. + if (!regex) + regex = createPlainTextSearchRegex(query, "i"); + + return regex; +} + +WebInspector.SourceFrame.prototype = { + wasShown: function() + { + this._ensureContentLoaded(); + this._textViewer.show(this.element); + if (this._wasHiddenWhileEditing) + this.setReadOnly(false); + }, + + willHide: function() + { + WebInspector.View.prototype.willHide.call(this); + if (this.loaded) + this._textViewer.freeCachedElements(); + + this._clearLineHighlight(); + if (!this._textViewer.readOnly) + this._wasHiddenWhileEditing = true; + this.setReadOnly(true); + }, + + focus: function() + { + this._textViewer.focus(); + }, + + get statusBarItems() + { + return WebInspector.experimentsSettings.sourceFrameAlwaysEditable.isEnabled() ? [] : [this._editButton.element]; + }, + + get loaded() + { + return this._loaded; + }, + + hasContent: function() + { + return true; + }, + + get textViewer() + { + return this._textViewer; + }, + + _ensureContentLoaded: function() + { + if (!this._contentRequested) { + this._contentRequested = true; + this.requestContent(this.setContent.bind(this)); + } + }, + + requestContent: function(callback) + { + }, + + /** + * @param {TextDiff} diffData + */ + markDiff: function(diffData) + { + if (this._diffLines && this.loaded) + this._removeDiffDecorations(); + + this._diffLines = diffData; + if (this.loaded) + this._updateDiffDecorations(); + }, + + addMessage: function(msg) + { + this._messages.push(msg); + if (this.loaded) + this.addMessageToSource(msg.line - 1, msg); + }, + + clearMessages: function() + { + for (var line in this._messageBubbles) { + var bubble = this._messageBubbles[line]; + bubble.parentNode.removeChild(bubble); + } + + this._messages = []; + this._rowMessages = {}; + this._messageBubbles = {}; + + this._textViewer.doResize(); + }, + + get textModel() + { + return this._textModel; + }, + + canHighlightLine: function(line) + { + return true; + }, + + highlightLine: function(line) + { + if (this.loaded) + this._textViewer.highlightLine(line); + else + this._lineToHighlight = line; + }, + + _clearLineHighlight: function() + { + if (this.loaded) + this._textViewer.clearLineHighlight(); + else + delete this._lineToHighlight; + }, + + _saveViewerState: function() + { + this._viewerState = { + textModelContent: this._textModel.text, + messages: this._messages, + diffLines: this._diffLines, + }; + }, + + _restoreViewerState: function() + { + if (!this._viewerState) + return; + this._textModel.setText(null, this._viewerState.textModelContent); + + this._messages = this._viewerState.messages; + this._diffLines = this._viewerState.diffLines; + this._setTextViewerDecorations(); + + delete this._viewerState; + }, + + beforeTextChanged: function() + { + if (!this._viewerState) + this._saveViewerState(); + + WebInspector.searchController.cancelSearch(); + this.clearMessages(); + }, + + afterTextChanged: function(oldRange, newRange) + { + }, + + setContent: function(mimeType, content) + { + this._textViewer.mimeType = mimeType; + + this._loaded = true; + this._textModel.setText(null, content); + + this._textViewer.beginUpdates(); + + this._setTextViewerDecorations(); + + if (typeof this._lineToHighlight === "number") { + this.highlightLine(this._lineToHighlight); + delete this._lineToHighlight; + } + + if (this._delayedFindSearchMatches) { + this._delayedFindSearchMatches(); + delete this._delayedFindSearchMatches; + } + + this.dispatchEventToListeners(WebInspector.SourceFrame.Events.Loaded); + + this._textViewer.endUpdates(); + + if (!this.canEditSource()) + this._editButton.disabled = true; + }, + + _setTextViewerDecorations: function() + { + this._rowMessages = {}; + this._messageBubbles = {}; + + this._textViewer.beginUpdates(); + + this._addExistingMessagesToSource(); + this._updateDiffDecorations(); + + this._textViewer.doResize(); + + this._textViewer.endUpdates(); + }, + + performSearch: function(query, callback) + { + // Call searchCanceled since it will reset everything we need before doing a new search. + this.searchCanceled(); + + function doFindSearchMatches(query) + { + this._currentSearchResultIndex = -1; + this._searchResults = []; + + var regex = WebInspector.SourceFrame.createSearchRegex(query); + this._searchResults = this._collectRegexMatches(regex); + + callback(this, this._searchResults.length); + } + + if (this.loaded) + doFindSearchMatches.call(this, query); + else + this._delayedFindSearchMatches = doFindSearchMatches.bind(this, query); + + this._ensureContentLoaded(); + }, + + searchCanceled: function() + { + delete this._delayedFindSearchMatches; + if (!this.loaded) + return; + + this._currentSearchResultIndex = -1; + this._searchResults = []; + this._textViewer.markAndRevealRange(null); + }, + + hasSearchResults: function() + { + return this._searchResults.length > 0; + }, + + jumpToFirstSearchResult: function() + { + this.jumpToSearchResult(0); + }, + + jumpToLastSearchResult: function() + { + this.jumpToSearchResult(this._searchResults.length - 1); + }, + + jumpToNextSearchResult: function() + { + this.jumpToSearchResult(this._currentSearchResultIndex + 1); + }, + + jumpToPreviousSearchResult: function() + { + this.jumpToSearchResult(this._currentSearchResultIndex - 1); + }, + + showingFirstSearchResult: function() + { + return this._searchResults.length && this._currentSearchResultIndex === 0; + }, + + showingLastSearchResult: function() + { + return this._searchResults.length && this._currentSearchResultIndex === (this._searchResults.length - 1); + }, + + get currentSearchResultIndex() + { + return this._currentSearchResultIndex; + }, + + jumpToSearchResult: function(index) + { + if (!this.loaded || !this._searchResults.length) + return; + this._currentSearchResultIndex = (index + this._searchResults.length) % this._searchResults.length; + this._textViewer.markAndRevealRange(this._searchResults[this._currentSearchResultIndex]); + }, + + _collectRegexMatches: function(regexObject) + { + var ranges = []; + for (var i = 0; i < this._textModel.linesCount; ++i) { + var line = this._textModel.line(i); + var offset = 0; + do { + var match = regexObject.exec(line); + if (match) { + if (match[0].length) + ranges.push(new WebInspector.TextRange(i, offset + match.index, i, offset + match.index + match[0].length)); + offset += match.index + 1; + line = line.substring(match.index + 1); + } + } while (match && line); + } + return ranges; + }, + + _updateDiffDecorations: function() + { + if (!this._diffLines) + return; + + function addDecorations(textViewer, lines, className) + { + for (var i = 0; i < lines.length; ++i) + textViewer.addDecoration(lines[i], className); + } + addDecorations(this._textViewer, this._diffLines.added, "webkit-added-line"); + addDecorations(this._textViewer, this._diffLines.removed, "webkit-removed-line"); + addDecorations(this._textViewer, this._diffLines.changed, "webkit-changed-line"); + }, + + _removeDiffDecorations: function() + { + function removeDecorations(textViewer, lines, className) + { + for (var i = 0; i < lines.length; ++i) + textViewer.removeDecoration(lines[i], className); + } + removeDecorations(this._textViewer, this._diffLines.added, "webkit-added-line"); + removeDecorations(this._textViewer, this._diffLines.removed, "webkit-removed-line"); + removeDecorations(this._textViewer, this._diffLines.changed, "webkit-changed-line"); + }, + + _addExistingMessagesToSource: function() + { + var length = this._messages.length; + for (var i = 0; i < length; ++i) + this.addMessageToSource(this._messages[i].line - 1, this._messages[i]); + }, + + addMessageToSource: function(lineNumber, msg) + { + if (lineNumber >= this._textModel.linesCount) + lineNumber = this._textModel.linesCount - 1; + if (lineNumber < 0) + lineNumber = 0; + + var messageBubbleElement = this._messageBubbles[lineNumber]; + if (!messageBubbleElement || messageBubbleElement.nodeType !== Node.ELEMENT_NODE || !messageBubbleElement.hasStyleClass("webkit-html-message-bubble")) { + messageBubbleElement = document.createElement("div"); + messageBubbleElement.className = "webkit-html-message-bubble"; + this._messageBubbles[lineNumber] = messageBubbleElement; + this._textViewer.addDecoration(lineNumber, messageBubbleElement); + } + + var rowMessages = this._rowMessages[lineNumber]; + if (!rowMessages) { + rowMessages = []; + this._rowMessages[lineNumber] = rowMessages; + } + + for (var i = 0; i < rowMessages.length; ++i) { + if (rowMessages[i].consoleMessage.isEqual(msg)) { + rowMessages[i].repeatCount = msg.totalRepeatCount; + this._updateMessageRepeatCount(rowMessages[i]); + return; + } + } + + var rowMessage = { consoleMessage: msg }; + rowMessages.push(rowMessage); + + var imageURL; + switch (msg.level) { + case WebInspector.ConsoleMessage.MessageLevel.Error: + messageBubbleElement.addStyleClass("webkit-html-error-message"); + imageURL = "Images/errorIcon.png"; + break; + case WebInspector.ConsoleMessage.MessageLevel.Warning: + messageBubbleElement.addStyleClass("webkit-html-warning-message"); + imageURL = "Images/warningIcon.png"; + break; + } + + var messageLineElement = document.createElement("div"); + messageLineElement.className = "webkit-html-message-line"; + messageBubbleElement.appendChild(messageLineElement); + + // Create the image element in the Inspector's document so we can use relative image URLs. + var image = document.createElement("img"); + image.src = imageURL; + image.className = "webkit-html-message-icon"; + messageLineElement.appendChild(image); + messageLineElement.appendChild(document.createTextNode(msg.message)); + + rowMessage.element = messageLineElement; + rowMessage.repeatCount = msg.totalRepeatCount; + this._updateMessageRepeatCount(rowMessage); + }, + + _updateMessageRepeatCount: function(rowMessage) + { + if (rowMessage.repeatCount < 2) + return; + + if (!rowMessage.repeatCountElement) { + var repeatCountElement = document.createElement("span"); + rowMessage.element.appendChild(repeatCountElement); + rowMessage.repeatCountElement = repeatCountElement; + } + + rowMessage.repeatCountElement.textContent = WebInspector.UIString(" (repeated %d times)", rowMessage.repeatCount); + }, + + populateLineGutterContextMenu: function(contextMenu, lineNumber) + { + }, + + populateTextAreaContextMenu: function(contextMenu, lineNumber) + { + if (!window.getSelection().isCollapsed) + return; + WebInspector.populateResourceContextMenu(contextMenu, this._url, lineNumber); + }, + + suggestedFileName: function() + { + }, + + inheritScrollPositions: function(sourceFrame) + { + this._textViewer.inheritScrollPositions(sourceFrame._textViewer); + }, + + _editButtonClicked: function() + { + if (!this.canEditSource()) + return; + + const shouldStartEditing = !this._editButton.toggled; + if (shouldStartEditing) + this.startEditing(); + else + this.commitEditing(); + }, + + canEditSource: function() + { + return false; + }, + + startEditing: function() + { + if (!this.canEditSource()) + return false; + + if (this._commitEditingInProgress) + return false; + + this.setReadOnly(false); + return true; + }, + + commitEditing: function() + { + if (!this._viewerState) { + // No editing was actually done. + this.setReadOnly(true); + return; + } + + this._commitEditingInProgress = true; + this._textViewer.readOnly = true; + this._editButton.toggled = false; + this.editContent(this._textModel.text, this.didEditContent.bind(this)); + }, + + didEditContent: function(error) + { + this._commitEditingInProgress = false; + this._textViewer.readOnly = false; + + if (error) { + if (error.message) + WebInspector.log(error.message, WebInspector.ConsoleMessage.MessageLevel.Error, true); + return; + } + + delete this._viewerState; + }, + + editContent: function(newContent, callback) + { + }, + + cancelEditing: function() + { + if (WebInspector.experimentsSettings.sourceFrameAlwaysEditable.isEnabled()) + return false; + + this._restoreViewerState(); + this.setReadOnly(true); + return true; + }, + + get readOnly() + { + return this._textViewer.readOnly; + }, + + setReadOnly: function(readOnly) + { + if (readOnly && WebInspector.experimentsSettings.sourceFrameAlwaysEditable.isEnabled()) + return; + this._textViewer.readOnly = readOnly; + this._editButton.toggled = !readOnly; + } +} + +WebInspector.SourceFrame.prototype.__proto__ = WebInspector.View.prototype; + + +/** + * @implements {WebInspector.TextViewerDelegate} + * @constructor + */ +WebInspector.TextViewerDelegateForSourceFrame = function(sourceFrame) +{ + this._sourceFrame = sourceFrame; +} + +WebInspector.TextViewerDelegateForSourceFrame.prototype = { + doubleClick: function(lineNumber) + { + this._sourceFrame.startEditing(lineNumber); + }, + + beforeTextChanged: function() + { + this._sourceFrame.beforeTextChanged(); + }, + + afterTextChanged: function(oldRange, newRange) + { + this._sourceFrame.afterTextChanged(oldRange, newRange); + }, + + commitEditing: function() + { + this._sourceFrame.commitEditing(); + }, + + cancelEditing: function() + { + return this._sourceFrame.cancelEditing(); + }, + + populateLineGutterContextMenu: function(contextMenu, lineNumber) + { + this._sourceFrame.populateLineGutterContextMenu(contextMenu, lineNumber); + }, + + populateTextAreaContextMenu: function(contextMenu, lineNumber) + { + this._sourceFrame.populateTextAreaContextMenu(contextMenu, lineNumber); + }, + + suggestedFileName: function() + { + return this._sourceFrame.suggestedFileName(); + } +} + +WebInspector.TextViewerDelegateForSourceFrame.prototype.__proto__ = WebInspector.TextViewerDelegate.prototype; +/* ResourceView.js */ + +/* + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) IBM Corp. 2009 All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @extends {WebInspector.View} + * @constructor + */ +WebInspector.ResourceView = function(resource) +{ + WebInspector.View.call(this); + this.registerRequiredCSS("resourceView.css"); + + this.element.addStyleClass("resource-view"); + this.resource = resource; +} + +WebInspector.ResourceView.prototype = { + hasContent: function() + { + return false; + } +} + +WebInspector.ResourceView.prototype.__proto__ = WebInspector.View.prototype; + +WebInspector.ResourceView.hasTextContent = function(resource) +{ + switch (resource.category) { + case WebInspector.resourceCategories.documents: + case WebInspector.resourceCategories.scripts: + case WebInspector.resourceCategories.xhr: + case WebInspector.resourceCategories.stylesheets: + return true; + case WebInspector.resourceCategories.other: + return resource.content && !resource.contentEncoded; + default: + return false; + } +} + +WebInspector.ResourceView.nonSourceViewForResource = function(resource) +{ + switch (resource.category) { + case WebInspector.resourceCategories.images: + return new WebInspector.ImageView(resource); + case WebInspector.resourceCategories.fonts: + return new WebInspector.FontView(resource); + default: + return new WebInspector.ResourceView(resource); + } +} + +/** + * @extends {WebInspector.SourceFrame} + * @constructor + */ +WebInspector.ResourceSourceFrame = function(resource) +{ + this._resource = resource; + WebInspector.SourceFrame.call(this, resource.url); +} + +//This is a map from resource.type to mime types +//found in WebInspector.SourceTokenizer.Registry. +WebInspector.ResourceSourceFrame.DefaultMIMETypeForResourceType = { + 0: "text/html", + 1: "text/css", + 4: "text/javascript" +} + +WebInspector.ResourceSourceFrame.mimeTypeForResource = function(resource) { + return WebInspector.ResourceSourceFrame.DefaultMIMETypeForResourceType[resource.type] || resource.mimeType; +} + +WebInspector.ResourceSourceFrame.prototype = { + get resource() + { + return this._resource; + }, + + requestContent: function(callback) + { + function contentLoaded(text) + { + var mimeType = WebInspector.ResourceSourceFrame.mimeTypeForResource(this.resource); + callback(mimeType, text); + } + + this.resource.requestContent(contentLoaded.bind(this)); + }, + + suggestedFileName: function() + { + return this.resource.displayName; + } +} + +WebInspector.ResourceSourceFrame.prototype.__proto__ = WebInspector.SourceFrame.prototype; + +/** + * @constructor + * @extends {WebInspector.ResourceSourceFrame} + */ +WebInspector.EditableResourceSourceFrame = function(resource) +{ + WebInspector.ResourceSourceFrame.call(this, resource); +} + +WebInspector.EditableResourceSourceFrame.prototype = { + canEditSource: function() + { + return this.resource.isEditable() && !this._commitEditingInProgress; + }, + + editContent: function(newText, callback) + { + this._clearIncrementalUpdateTimer(); + var majorChange = true; + this.resource.setContent(newText, majorChange, callback); + }, + + cancelEditing: function() + { + if (WebInspector.experimentsSettings.sourceFrameAlwaysEditable.isEnabled()) + return false; + + this._clearIncrementalUpdateTimer(); + const majorChange = false; + if (this._viewerState) + this.resource.setContent(this._viewerState.textModelContent, majorChange); + WebInspector.SourceFrame.prototype.cancelEditing.call(this); + return true; + }, + + afterTextChanged: function(oldRange, newRange) + { + function commitIncrementalEdit() + { + var majorChange = false; + this.resource.setContent(this._textModel.text, majorChange, function() {}); + } + const updateTimeout = 200; + this._incrementalUpdateTimer = setTimeout(commitIncrementalEdit.bind(this), updateTimeout); + }, + + _clearIncrementalUpdateTimer: function() + { + if (this._incrementalUpdateTimer) + clearTimeout(this._incrementalUpdateTimer); + delete this._incrementalUpdateTimer; + }, +} + +WebInspector.EditableResourceSourceFrame.prototype.__proto__ = WebInspector.ResourceSourceFrame.prototype; + +/** + * @extends {WebInspector.ResourceSourceFrame} + * @constructor + */ +WebInspector.ResourceRevisionSourceFrame = function(revision) +{ + WebInspector.ResourceSourceFrame.call(this, revision.resource); + this._revision = revision; +} + +WebInspector.ResourceRevisionSourceFrame.prototype = { + get resource() + { + return this._revision.resource; + }, + + requestContent: function(callback) + { + function contentLoaded(text) + { + var mimeType = WebInspector.ResourceSourceFrame.mimeTypeForResource(this.resource); + callback(mimeType, text); + } + + this._revision.requestContent(contentLoaded.bind(this)); + }, +} + +WebInspector.ResourceRevisionSourceFrame.prototype.__proto__ = WebInspector.ResourceSourceFrame.prototype; +/* JavaScriptSourceFrame.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.SourceFrame} + * @param {WebInspector.ScriptsPanel} scriptsPanel + * @param {WebInspector.DebuggerPresentationModel} model + * @param {WebInspector.UISourceCode} uiSourceCode + */ +WebInspector.JavaScriptSourceFrame = function(scriptsPanel, model, uiSourceCode) +{ + this._scriptsPanel = scriptsPanel; + this._model = model; + this._uiSourceCode = uiSourceCode; + this._popoverObjectGroup = "popover"; + this._breakpoints = {}; + + WebInspector.SourceFrame.call(this, uiSourceCode.url); + + this._popoverHelper = new WebInspector.ObjectPopoverHelper(this.textViewer.element, + this._getPopoverAnchor.bind(this), this._onShowPopover.bind(this), this._onHidePopover.bind(this), true); + + this.textViewer.element.addEventListener("mousedown", this._onMouseDown.bind(this), true); + this._uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.ContentChanged, this._onContentChanged, this); + this.addEventListener(WebInspector.SourceFrame.Events.Loaded, this._onTextViewerContentLoaded, this); +} + +WebInspector.JavaScriptSourceFrame.prototype = { + get uiSourceCode() + { + return this._uiSourceCode; + }, + + // View events + willHide: function() + { + WebInspector.SourceFrame.prototype.willHide.call(this); + this._popoverHelper.hidePopover(); + }, + + // SourceFrame overrides + requestContent: function(callback) + { + this._uiSourceCode.requestContent(callback); + }, + + canEditSource: function() + { + return this._model.canEditScriptSource(this._uiSourceCode); + }, + + suggestedFileName: function() + { + return this._uiSourceCode.fileName || "untitled.js"; + }, + + editContent: function(newContent, callback) + { + this._model.setScriptSource(this._uiSourceCode, newContent, callback); + }, + + _onContentChanged: function() + { + if (!this.textViewer.readOnly) + return; + this.requestContent(this.setContent.bind(this)); + }, + + setReadOnly: function(readOnly) + { + if (this._popoverHelper && !readOnly) + this._popoverHelper.hidePopover(); + WebInspector.SourceFrame.prototype.setReadOnly.call(this, readOnly); + if (readOnly) + this._scriptsPanel.setScriptSourceIsBeingEdited(this._uiSourceCode, false); + }, + + populateLineGutterContextMenu: function(contextMenu, lineNumber) + { + contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Continue to here" : "Continue to Here"), this._model.continueToLine.bind(this._model, this._uiSourceCode, lineNumber)); + + var breakpoint = this._model.findBreakpoint(this._uiSourceCode, lineNumber); + if (!breakpoint) { + // This row doesn't have a breakpoint: We want to show Add Breakpoint and Add and Edit Breakpoint. + contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Add breakpoint" : "Add Breakpoint"), this._setBreakpoint.bind(this, lineNumber, "", true)); + + function addConditionalBreakpoint() + { + this.addBreakpoint(lineNumber, true, true, true); + function didEditBreakpointCondition(committed, condition) + { + this.removeBreakpoint(lineNumber); + if (committed) + this._setBreakpoint(lineNumber, condition, true); + } + this._editBreakpointCondition(lineNumber, "", didEditBreakpointCondition.bind(this)); + } + contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Add conditional breakpoint…" : "Add Conditional Breakpoint…"), addConditionalBreakpoint.bind(this)); + } else { + // This row has a breakpoint, we want to show edit and remove breakpoint, and either disable or enable. + contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Remove breakpoint" : "Remove Breakpoint"), this._model.removeBreakpoint.bind(this._model, this._uiSourceCode, lineNumber)); + + function editBreakpointCondition() + { + function didEditBreakpointCondition(committed, condition) + { + if (committed) + this._model.updateBreakpoint(this._uiSourceCode, lineNumber, condition, breakpoint.enabled); + } + this._editBreakpointCondition(lineNumber, breakpoint.condition, didEditBreakpointCondition.bind(this)); + } + contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Edit breakpoint…" : "Edit Breakpoint…"), editBreakpointCondition.bind(this)); + function setBreakpointEnabled(enabled) + { + this._model.updateBreakpoint(this._uiSourceCode, lineNumber, breakpoint.condition, enabled); + } + if (breakpoint.enabled) + contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Disable breakpoint" : "Disable Breakpoint"), setBreakpointEnabled.bind(this, false)); + else + contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Enable breakpoint" : "Enable Breakpoint"), setBreakpointEnabled.bind(this, true)); + } + }, + + populateTextAreaContextMenu: function(contextMenu, lineNumber) + { + WebInspector.SourceFrame.prototype.populateTextAreaContextMenu.call(this, contextMenu, lineNumber); + var selection = window.getSelection(); + if (selection.type === "Range" && !selection.isCollapsed) { + var addToWatchLabel = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Add to watch" : "Add to Watch"); + contextMenu.appendItem(addToWatchLabel, this._scriptsPanel.addToWatch.bind(this._scriptsPanel, selection.toString())); + var evaluateLabel = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Evaluate in console" : "Evaluate in Console"); + contextMenu.appendItem(evaluateLabel, WebInspector.evaluateInConsole.bind(WebInspector, selection.toString())); + } + }, + + afterTextChanged: function(oldRange, newRange) + { + if (!oldRange || !newRange) + return; + + // Adjust execution line number. + if (typeof this._executionLineNumber === "number") { + var newExecutionLineNumber = this._lineNumberAfterEditing(this._executionLineNumber, oldRange, newRange); + this.clearExecutionLine(); + this.setExecutionLine(newExecutionLineNumber, true); + } + + // Adjust breakpoints. + var oldBreakpoints = this._breakpoints; + this._breakpoints = {}; + for (var lineNumber in oldBreakpoints) { + lineNumber = Number(lineNumber); + var breakpoint = oldBreakpoints[lineNumber]; + var newLineNumber = this._lineNumberAfterEditing(lineNumber, oldRange, newRange); + if (lineNumber === newLineNumber) + this._breakpoints[lineNumber] = breakpoint; + else { + this.removeBreakpoint(lineNumber); + this.addBreakpoint(newLineNumber, breakpoint.resolved, breakpoint.conditional, breakpoint.enabled); + } + } + }, + + beforeTextChanged: function() + { + if (!this._javaScriptSourceFrameState) { + this._javaScriptSourceFrameState = { + executionLineNumber: this._executionLineNumber, + breakpoints: this._breakpoints + } + this._scriptsPanel.setScriptSourceIsBeingEdited(this._uiSourceCode, true); + } + WebInspector.SourceFrame.prototype.beforeTextChanged.call(this); + }, + + cancelEditing: function() + { + if (WebInspector.experimentsSettings.sourceFrameAlwaysEditable.isEnabled()) + return false; + + WebInspector.SourceFrame.prototype.cancelEditing.call(this); + + if (!this._javaScriptSourceFrameState) + return true; + + if (typeof this._javaScriptSourceFrameState.executionLineNumber === "number") { + this.clearExecutionLine(); + this.setExecutionLine(this._javaScriptSourceFrameState.executionLineNumber); + } + + var oldBreakpoints = this._breakpoints; + this._breakpoints = {}; + for (var lineNumber in oldBreakpoints) + this.removeBreakpoint(Number(lineNumber)); + + var newBreakpoints = this._javaScriptSourceFrameState.breakpoints; + for (var lineNumber in newBreakpoints) { + lineNumber = Number(lineNumber); + var breakpoint = newBreakpoints[lineNumber]; + this.addBreakpoint(lineNumber, breakpoint.resolved, breakpoint.conditional, breakpoint.enabled); + } + + delete this._javaScriptSourceFrameState; + return true; + }, + + didEditContent: function(error) + { + WebInspector.SourceFrame.prototype.didEditContent.call(this, error); + if (error) + return; + + var newBreakpoints = {}; + for (var lineNumber in this._breakpoints) { + newBreakpoints[lineNumber] = this._breakpoints[lineNumber]; + this.removeBreakpoint(Number(lineNumber)); + } + + for (var lineNumber in this._javaScriptSourceFrameState.breakpoints) + this._model.removeBreakpoint(this._uiSourceCode, Number(lineNumber)); + + for (var lineNumber in newBreakpoints) { + var breakpoint = newBreakpoints[lineNumber]; + this._setBreakpoint(Number(lineNumber), breakpoint.condition, breakpoint.enabled); + } + this._scriptsPanel.setScriptSourceIsBeingEdited(this._uiSourceCode, false); + delete this._javaScriptSourceFrameState; + }, + + // Popover callbacks + _shouldShowPopover: function(element) + { + if (!this._model.paused) + return false; + if (!element.enclosingNodeOrSelfWithClass("webkit-line-content")) + return false; + if (window.getSelection().type === "Range") + return false; + + // We are interested in identifiers and "this" keyword. + if (element.hasStyleClass("webkit-javascript-keyword")) + return element.textContent === "this"; + + return element.hasStyleClass("webkit-javascript-ident"); + }, + + _getPopoverAnchor: function(element) + { + if (!this._shouldShowPopover(element)) + return; + return element; + }, + + _onShowPopover: function(element, showCallback) + { + if (!this.readOnly) { + this._popoverHelper.hidePopover(); + return; + } + this._highlightElement = this._highlightExpression(element); + + function showObjectPopover(result, wasThrown) + { + if (!this._model.paused) { + this._popoverHelper.hidePopover(); + return; + } + showCallback(WebInspector.RemoteObject.fromPayload(result), wasThrown); + // Popover may have been removed by showCallback(). + if (this._highlightElement) + this._highlightElement.addStyleClass("source-frame-eval-expression"); + } + + var selectedCallFrame = this._model.selectedCallFrame; + selectedCallFrame.evaluate(this._highlightElement.textContent, this._popoverObjectGroup, false, false, showObjectPopover.bind(this)); + }, + + _onHidePopover: function() + { + // Replace higlight element with its contents inplace. + var highlightElement = this._highlightElement; + if (!highlightElement) + return; + // FIXME: the text editor should maintain highlight on its own. The check below is a workaround for + // the case when highlight element is detached from DOM by the TextViewer when re-building the DOM. + var parentElement = highlightElement.parentElement; + if (parentElement) { + var child = highlightElement.firstChild; + while (child) { + var nextSibling = child.nextSibling; + parentElement.insertBefore(child, highlightElement); + child = nextSibling; + } + parentElement.removeChild(highlightElement); + } + delete this._highlightElement; + RuntimeAgent.releaseObjectGroup(this._popoverObjectGroup); + }, + + _highlightExpression: function(element) + { + // Collect tokens belonging to evaluated expression. + var tokens = [ element ]; + var token = element.previousSibling; + while (token && (token.className === "webkit-javascript-ident" || token.className === "webkit-javascript-keyword" || token.textContent.trim() === ".")) { + tokens.push(token); + token = token.previousSibling; + } + tokens.reverse(); + + // Wrap them with highlight element. + var parentElement = element.parentElement; + var nextElement = element.nextSibling; + var container = document.createElement("span"); + for (var i = 0; i < tokens.length; ++i) + container.appendChild(tokens[i]); + parentElement.insertBefore(container, nextElement); + return container; + }, + + addBreakpoint: function(lineNumber, resolved, conditional, enabled) + { + this._breakpoints[lineNumber] = { + resolved: resolved, + conditional: conditional, + enabled: enabled + }; + this.textViewer.beginUpdates(); + this.textViewer.addDecoration(lineNumber, "webkit-breakpoint"); + if (!enabled) + this.textViewer.addDecoration(lineNumber, "webkit-breakpoint-disabled"); + if (conditional) + this.textViewer.addDecoration(lineNumber, "webkit-breakpoint-conditional"); + this.textViewer.endUpdates(); + }, + + removeBreakpoint: function(lineNumber) + { + delete this._breakpoints[lineNumber]; + this.textViewer.beginUpdates(); + this.textViewer.removeDecoration(lineNumber, "webkit-breakpoint"); + this.textViewer.removeDecoration(lineNumber, "webkit-breakpoint-disabled"); + this.textViewer.removeDecoration(lineNumber, "webkit-breakpoint-conditional"); + this.textViewer.endUpdates(); + }, + + _setBreakpoint: function(lineNumber, condition, enabled) + { + this._model.setBreakpoint(this._uiSourceCode, lineNumber, condition, enabled); + this._scriptsPanel.activateBreakpoints(); + }, + + _onMouseDown: function(event) + { + if (event.button != 0 || event.altKey || event.ctrlKey || event.metaKey) + return; + var target = event.target.enclosingNodeOrSelfWithClass("webkit-line-number"); + if (!target) + return; + var lineNumber = target.lineNumber; + + var breakpoint = this._model.findBreakpoint(this._uiSourceCode, lineNumber); + if (breakpoint) { + if (event.shiftKey) + this._model.updateBreakpoint(this._uiSourceCode, lineNumber, breakpoint.condition, !breakpoint.enabled); + else + this._model.removeBreakpoint(this._uiSourceCode, lineNumber); + } else + this._setBreakpoint(lineNumber, "", true); + event.preventDefault(); + }, + + _editBreakpointCondition: function(lineNumber, condition, callback) + { + this._conditionElement = this._createConditionElement(lineNumber); + this.textViewer.addDecoration(lineNumber, this._conditionElement); + + function finishEditing(committed, element, newText) + { + this.textViewer.removeDecoration(lineNumber, this._conditionElement); + delete this._conditionEditorElement; + delete this._conditionElement; + callback(committed, newText); + } + + var config = new WebInspector.EditingConfig(finishEditing.bind(this, true), finishEditing.bind(this, false)); + WebInspector.startEditing(this._conditionEditorElement, config); + this._conditionEditorElement.value = condition; + this._conditionEditorElement.select(); + }, + + _createConditionElement: function(lineNumber) + { + var conditionElement = document.createElement("div"); + conditionElement.className = "source-frame-breakpoint-condition"; + + var labelElement = document.createElement("label"); + labelElement.className = "source-frame-breakpoint-message"; + labelElement.htmlFor = "source-frame-breakpoint-condition"; + labelElement.appendChild(document.createTextNode(WebInspector.UIString("The breakpoint on line %d will stop only if this expression is true:", lineNumber))); + conditionElement.appendChild(labelElement); + + var editorElement = document.createElement("input"); + editorElement.id = "source-frame-breakpoint-condition"; + editorElement.className = "monospace"; + editorElement.type = "text"; + conditionElement.appendChild(editorElement); + this._conditionEditorElement = editorElement; + + return conditionElement; + }, + + /** + * @param {boolean=} skipRevealLine + */ + setExecutionLine: function(lineNumber, skipRevealLine) + { + this._executionLineNumber = lineNumber; + if (this.loaded) { + this.textViewer.addDecoration(lineNumber, "webkit-execution-line"); + if (!skipRevealLine) + this.textViewer.revealLine(lineNumber); + } + }, + + clearExecutionLine: function() + { + if (this.loaded) + this.textViewer.removeDecoration(this._executionLineNumber, "webkit-execution-line"); + delete this._executionLineNumber; + }, + + _lineNumberAfterEditing: function(lineNumber, oldRange, newRange) + { + var shiftOffset = lineNumber <= oldRange.startLine ? 0 : newRange.linesCount - oldRange.linesCount; + + // Special case of editing the line itself. We should decide whether the line number should move below or not. + if (lineNumber === oldRange.startLine) { + var whiteSpacesRegex = /^[\s\xA0]*$/; + for (var i = 0; lineNumber + i <= newRange.endLine; ++i) { + if (!whiteSpacesRegex.test(this._textModel.line(lineNumber + i))) { + shiftOffset = i; + break; + } + } + } + + var newLineNumber = Math.max(0, lineNumber + shiftOffset); + if (oldRange.startLine < lineNumber && lineNumber < oldRange.endLine) + newLineNumber = oldRange.startLine; + return newLineNumber; + }, + + _onTextViewerContentLoaded: function() + { + if (typeof this._executionLineNumber === "number") + this.setExecutionLine(this._executionLineNumber); + } +} + +WebInspector.JavaScriptSourceFrame.prototype.__proto__ = WebInspector.SourceFrame.prototype; +/* SplitView.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY GOOGLE INC. AND ITS CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE INC. + * OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.View} + * @param {string=} sidebarPosition + * @param {string=} sidebarWidthSettingName + * @param {number=} defaultSidebarWidth + */ +WebInspector.SplitView = function(sidebarPosition, sidebarWidthSettingName, defaultSidebarWidth) +{ + WebInspector.View.call(this); + this.registerRequiredCSS("splitView.css"); + + this.element.className = "split-view"; + + this._leftElement = document.createElement("div"); + this._leftElement.className = "split-view-contents"; + this.element.appendChild(this._leftElement); + + this._rightElement = document.createElement("div"); + this._rightElement.className = "split-view-contents"; + this.element.appendChild(this._rightElement); + + this.sidebarResizerElement = document.createElement("div"); + this.sidebarResizerElement.className = "split-view-resizer"; + this.installResizer(this.sidebarResizerElement); + this._resizable = true; + this.element.appendChild(this.sidebarResizerElement); + + defaultSidebarWidth = defaultSidebarWidth || 200; + this._savedSidebarWidth = defaultSidebarWidth; + + this._sidebarWidthSettingName = sidebarWidthSettingName; + if (this._sidebarWidthSettingName) + WebInspector.settings[this._sidebarWidthSettingName] = WebInspector.settings.createSetting(this._sidebarWidthSettingName, undefined); + + this._minimalSidebarWidth = Preferences.minSidebarWidth; + this._minimalMainWidth = 0; + this._minimalSidebarWidthPercent = 0; + this._minimalMainWidthPercent = 50; + + this._mainElementHidden = false; + this._sidebarElementHidden = false; + + this._innerSetSidebarPosition(sidebarPosition || WebInspector.SplitView.SidebarPosition.Left); +} + +WebInspector.SplitView.EventTypes = { + Resized: "Resized", +} + +/** + * @enum {string} + */ +WebInspector.SplitView.SidebarPosition = { + Left: "Left", + Right: "Right" +} + +WebInspector.SplitView.prototype = { + /** + * @type {boolean} + */ + get hasLeftSidebar() + { + return this._sidebarPosition === WebInspector.SplitView.SidebarPosition.Left; + }, + + /** + * @type {Element} + */ + get mainElement() + { + return this.hasLeftSidebar ? this._rightElement : this._leftElement; + }, + + /** + * @type {Element} + */ + get sidebarElement() + { + return this.hasLeftSidebar ? this._leftElement : this._rightElement; + }, + + /** + * @type {boolean} + */ + get resizable() + { + return this._resizable && !this._mainElementHidden && !this._sidebarElementHidden + }, + + /** + * @type {boolean} + */ + set resizable(resizable) + { + if (this._resizable === resizable) + return; + this._resizable = resizable; + this._updateResizer(resizable); + }, + + _updateResizer: function() + { + if (this.resizable) + this.sidebarResizerElement.removeStyleClass("hidden"); + else + this.sidebarResizerElement.addStyleClass("hidden"); + }, + + /** + * @type {string} + */ + set sidebarPosition(sidebarPosition) + { + if (this._sidebarPosition === sidebarPosition) + return; + this._innerSetSidebarPosition(sidebarPosition); + this._restoreSidebarWidth(); + }, + + /** + * @param {string} sidebarPosition + */ + _innerSetSidebarPosition: function(sidebarPosition) + { + this._sidebarPosition = sidebarPosition; + + this._leftElement.style.left = 0; + this._rightElement.style.right = 0; + if (this.hasLeftSidebar) { + this._leftElement.addStyleClass("split-view-sidebar-left"); + this._rightElement.removeStyleClass("split-view-sidebar-right"); + this._leftElement.style.removeProperty("right"); + this._rightElement.style.removeProperty("width"); + this.sidebarResizerElement.style.removeProperty("right"); + } else { + this._rightElement.addStyleClass("split-view-sidebar-right"); + this._leftElement.removeStyleClass("split-view-sidebar-left"); + this._leftElement.style.removeProperty("width"); + this._rightElement.style.removeProperty("left"); + this.sidebarResizerElement.style.removeProperty("left"); + } + }, + + /** + * @param {number} width + */ + set minimalSidebarWidth(width) + { + this._minimalSidebarWidth = width; + }, + + /** + * @param {number} width + */ + set minimalMainWidth(width) + { + this._minimalMainWidth = width; + }, + + /** + * @param {number} widthPercent + */ + set minimalSidebarWidthPercent(widthPercent) + { + this._minimalSidebarWidthPercent = widthPercent; + }, + + /** + * @param {number} widthPercent + */ + set minimalMainWidthPercent(widthPercent) + { + this._minimalMainWidthPercent = widthPercent; + }, + + /** + * @param {number} width + */ + setMainWidth: function(width) + { + this.setSidebarWidth(this._totalWidth - width); + }, + + /** + * @param {number} width + */ + setSidebarWidth: function(width) + { + if (this._sidebarWidth === width) + return; + + this._innerSetSidebarWidth(width); + this.saveSidebarWidth(); + }, + + /** + * @param {number} width + */ + _innerSetSidebarWidth: function(width) + { + if (this.hasLeftSidebar) + this._innerSetLeftSidebarWidth(width); + else + this._innerSetRightSidebarWidth(width); + this._sidebarWidth = width; + this.doResize(); + this.dispatchEventToListeners(WebInspector.SplitView.EventTypes.Resized, this._sidebarWidth); + }, + + /** + * @param {number} width + */ + _innerSetLeftSidebarWidth: function(width) + { + this._leftElement.style.width = width + "px"; + this._rightElement.style.left = width + "px"; + this.sidebarResizerElement.style.left = (width - 3) + "px"; + }, + + /** + * @param {number} width + */ + _innerSetRightSidebarWidth: function(width) + { + this._rightElement.style.width = width + "px"; + this._leftElement.style.right = width + "px"; + this.sidebarResizerElement.style.right = (width - 3) + "px"; + }, + + /** + * @param {number} width + */ + _setSidebarWidthEnsuringConstraints: function(width) + { + var minWidth = Math.max(this._minimalSidebarWidth, this._totalWidth * this._minimalSidebarWidthPercent); + var maxWidth = Math.min(this._totalWidth - this._minimalMainWidth, this._totalWidth * (100 - this._minimalMainWidthPercent) / 100 ); + width = Number.constrain(width, minWidth, maxWidth); + + this.setSidebarWidth(width); + }, + + hideMainElement: function() + { + if (this._mainElementHidden) + return; + + if (this._sidebarElementHidden) + this.showSidebarElement(); + + this.mainElement.addStyleClass("hidden"); + this.sidebarElement.addStyleClass("maximized"); + + if (this.hasLeftSidebar) + this.sidebarElement.style.right = "0px"; + else + this.sidebarElement.style.left = "0px"; + + this._mainElementHidden = true; + this._updateResizer(); + this._restoreSidebarWidth(); + }, + + showMainElement: function() + { + if (!this._mainElementHidden) + return; + + this.mainElement.removeStyleClass("hidden"); + this.sidebarElement.removeStyleClass("maximized"); + + if (this.hasLeftSidebar) + this.sidebarElement.style.right = ""; + else + this.sidebarElement.style.left = ""; + + this._mainElementHidden = false; + this._updateResizer(); + this._restoreSidebarWidth(); + }, + + hideSidebarElement: function() + { + if (this._sidebarElementHidden) + return; + + if (this._mainElementHidden) + this.showMainElement(); + + this.sidebarElement.addStyleClass("hidden"); + this._sidebarElementHidden = true; + this._updateResizer(); + this._restoreSidebarWidth(); + }, + + showSidebarElement: function() + { + if (!this._sidebarElementHidden) + return; + + this.sidebarElement.removeStyleClass("hidden"); + this._sidebarElementHidden = false; + this._updateResizer(); + this._restoreSidebarWidth(); + }, + + wasShown: function() + { + this._totalWidth = this.element.offsetWidth; + this._restoreSidebarWidth(); + }, + + onResize: function() + { + this._totalWidth = this.element.offsetWidth; + + if (this._mainElementHidden) + this._sidebarWidth = this._totalWidth; + }, + + /** + * @param {Event} event + */ + _startResizerDragging: function(event) + { + if (!this._resizable) + return; + + var leftWidth = this.hasLeftSidebar ? this._sidebarWidth : this._totalWidth - this._sidebarWidth; + this._dragOffset = leftWidth - event.pageX; + + WebInspector.elementDragStart(this.sidebarResizerElement, this._resizerDragging.bind(this), this._endResizerDragging.bind(this), event, "ew-resize"); + }, + + /** + * @param {Event} event + */ + _resizerDragging: function(event) + { + var leftWidth = event.pageX + this._dragOffset + var rightWidth = this._totalWidth - leftWidth; + var sidebarWidth = this.hasLeftSidebar ? leftWidth : rightWidth; + + this._setSidebarWidthEnsuringConstraints(sidebarWidth); + event.preventDefault(); + }, + + /** + * @param {Event} event + */ + _endResizerDragging: function(event) + { + delete this._dragOffset; + WebInspector.elementDragEnd(event); + }, + + /** + * @param {Element} resizerElement + */ + installResizer: function(resizerElement) + { + resizerElement.addEventListener("mousedown", this._startResizerDragging.bind(this), false); + }, + + /** + * @return {number} + */ + preferredSidebarWidth: function() + { + if (!this._sidebarWidthSettingName) + return this._savedSidebarWidth; + + return WebInspector.settings[this._sidebarWidthSettingName].get() || this._savedSidebarWidth; + }, + + _restoreSidebarWidth: function() + { + if (this._mainElementHidden) { + this.sidebarElement.style.width = ""; + this._sidebarWidth = this._totalWidth; + return; + } + + if (this._sidebarElementHidden) { + this._innerSetSidebarWidth(0); + return; + } + + // Ensure restore satisfies constraints. + this._setSidebarWidthEnsuringConstraints(this.preferredSidebarWidth()); + }, + + saveSidebarWidth: function() + { + this._savedSidebarWidth = this._sidebarWidth; + if (!this._sidebarWidthSettingName) + return; + + WebInspector.settings[this._sidebarWidthSettingName].set(this._sidebarWidth); + }, + + /** + * @return {Array.} + */ + elementsToRestoreScrollPositionsFor: function() + { + return [ this.mainElement, this.sidebarElement ]; + } +} + +WebInspector.SplitView.prototype.__proto__ = WebInspector.View.prototype; +/* TabbedEditorContainer.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY GOOGLE INC. AND ITS CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE INC. + * OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @implements {WebInspector.ScriptsPanel.EditorContainer} + * @extends {WebInspector.Object} + * @constructor + */ +WebInspector.TabbedEditorContainer = function() +{ + this._tabbedPane = new WebInspector.TabbedPane(); + this._tabbedPane.closeableTabs = true; + this._tabbedPane.element.id = "scripts-editor-container-tabbed-pane"; + + this._tabbedPane.addEventListener(WebInspector.TabbedPane.EventTypes.TabClosed, this._tabClosed, this); + this._tabbedPane.addEventListener(WebInspector.TabbedPane.EventTypes.TabSelected, this._tabSelected, this); + + this._titles = new Map(); + this._tooltips = new Map(); + this._tabIds = new Map(); +} + +WebInspector.TabbedEditorContainer._tabId = 0; + +WebInspector.TabbedEditorContainer.prototype = { + /** + * @type {WebInspector.SourceFrame} + */ + get currentSourceFrame() + { + return this._tabbedPane.visibleView; + }, + + /** + * @param {Element} parentElement + */ + show: function(parentElement) + { + this._tabbedPane.show(parentElement); + }, + + /** + * @param {string} title + * @param {WebInspector.SourceFrame} sourceFrame + * @param {string} tooltip + */ + showSourceFrame: function(title, sourceFrame, tooltip) + { + var tabId = this._tabIds.get(sourceFrame) || this._appendSourceFrameTab(title, sourceFrame, tooltip); + this._tabbedPane.selectTab(tabId); + }, + + /** + * @param {string} title + * @param {WebInspector.SourceFrame} sourceFrame + * @param {string} tooltip + */ + _appendSourceFrameTab: function(title, sourceFrame, tooltip) + { + var tabId = this._generateTabId(); + this._tabIds.put(sourceFrame, tabId) + this._titles.put(sourceFrame, title) + this._tooltips.put(sourceFrame, tooltip) + + this._tabbedPane.appendTab(tabId, title, sourceFrame, tooltip); + return tabId; + }, + + /** + * @param {WebInspector.SourceFrame} sourceFrame + */ + _removeSourceFrameTab: function(sourceFrame) + { + var tabId = this._tabIds.get(sourceFrame); + + if (tabId) + this._tabbedPane.closeTab(tabId); + }, + + /** + * @param {WebInspector.Event} event + */ + _tabClosed: function(event) + { + var sourceFrame = /** @type {WebInspector.UISourceCode} */ event.data.view; + this._tabIds.remove(sourceFrame); + this._titles.remove(sourceFrame); + this._tooltips.remove(sourceFrame); + }, + + /** + * @param {WebInspector.Event} event + */ + _tabSelected: function(event) + { + var sourceFrame = /** @type {WebInspector.UISourceCode} */ event.data.view; + this.dispatchEventToListeners(WebInspector.ScriptsPanel.EditorContainer.Events.EditorSelected, sourceFrame); + }, + + /** + * @param {WebInspector.SourceFrame} oldSourceFrame + * @param {string} title + * @param {WebInspector.SourceFrame} sourceFrame + * @param {string} tooltip + */ + _replaceSourceFrameTab: function(oldSourceFrame, title, sourceFrame, tooltip) + { + var tabId = this._tabIds.get(oldSourceFrame); + + if (tabId) { + this._tabIds.remove(oldSourceFrame); + this._titles.remove(oldSourceFrame); + this._tooltips.remove(oldSourceFrame); + + this._tabIds.put(sourceFrame, tabId); + this._titles.put(sourceFrame, title); + this._tooltips.put(sourceFrame, tooltip); + + this._tabbedPane.changeTabTitle(tabId, title); + this._tabbedPane.changeTabView(tabId, sourceFrame); + this._tabbedPane.changeTabTooltip(tabId, tooltip); + } + }, + + /** + * @param {WebInspector.SourceFrame} sourceFrame + * @return {boolean} + */ + isSourceFrameOpen: function(sourceFrame) + { + return !!this._tabIds.get(sourceFrame); + }, + + /** + * @param {Array.} oldSourceFrames + * @param {string} title + * @param {WebInspector.SourceFrame} sourceFrame + * @param {string} tooltip + */ + replaceSourceFrames: function(oldSourceFrames, title, sourceFrame, tooltip) + { + var mainSourceFrame; + for (var i = 0; i < oldSourceFrames.length; ++i) { + var tabId = this._tabIds.get(oldSourceFrames[i]); + if (tabId && (!mainSourceFrame || this._tabbedPane.selectedTabId === tabId)) { + mainSourceFrame = oldSourceFrames[i]; + break; + } + } + + if (mainSourceFrame) + this._replaceSourceFrameTab(mainSourceFrame, title, sourceFrame, tooltip); + + for (var i = 0; i < oldSourceFrames.length; ++i) + this._removeSourceFrameTab(oldSourceFrames[i]); + }, + + /** + * @param {WebInspector.SourceFrame} sourceFrame + * @param {boolean} isDirty + */ + setSourceFrameIsDirty: function(sourceFrame, isDirty) + { + var tabId = this._tabIds.get(sourceFrame); + if (tabId) { + var title = this._titles.get(sourceFrame); + if (isDirty) + title += "*"; + this._tabbedPane.changeTabTitle(tabId, title); + } + }, + + reset: function() + { + this._tabbedPane.closeAllTabs(); + this._titles = new Map(); + this._tooltips = new Map(); + this._tabIds = new Map(); + }, + + /** + * @return {string} + */ + _generateTabId: function() + { + return "tab_" + (WebInspector.TabbedEditorContainer._tabId++); + } +} + +WebInspector.TabbedEditorContainer.prototype.__proto__ = WebInspector.Object.prototype;/* ScriptsPanel.js */ + +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.Panel} + */ +WebInspector.ScriptsPanel = function(presentationModel) +{ + WebInspector.Panel.call(this, "scripts"); + this.registerRequiredCSS("scriptsPanel.css"); + + WebInspector.settings.pauseOnExceptionStateString = WebInspector.settings.createSetting("pauseOnExceptionStateString", WebInspector.ScriptsPanel.PauseOnExceptionsState.DontPauseOnExceptions); + + this._presentationModel = presentationModel; + + function viewGetter() + { + return this.visibleView; + } + WebInspector.GoToLineDialog.install(this, viewGetter.bind(this)); + WebInspector.JavaScriptOutlineDialog.install(this, viewGetter.bind(this)); + + this.debugToolbar = this._createDebugToolbar(); + + const initialDebugSidebarWidth = 225; + const maximalDebugSidebarWidthPercent = 50; + this.createSplitView(this.element, WebInspector.SplitView.SidebarPosition.Right, initialDebugSidebarWidth); + this.splitView.element.id = "scripts-split-view"; + this.splitView.minimalSidebarWidth = Preferences.minScriptsSidebarWidth; + this.splitView.minimalMainWidthPercent = 100 - maximalDebugSidebarWidthPercent; + + this.sidebarElement.appendChild(this.debugToolbar); + + this.debugSidebarResizeWidgetElement = document.createElement("div"); + this.debugSidebarResizeWidgetElement.id = "scripts-debug-sidebar-resizer-widget"; + this.splitView.installResizer(this.debugSidebarResizeWidgetElement); + + if (WebInspector.experimentsSettings.useScriptsNavigator.isEnabled()) { + const initialNavigatorWidth = 225; + const minimalViewsContainerWidthPercent = 50; + this.editorView = new WebInspector.SplitView(WebInspector.SplitView.SidebarPosition.Left, "scriptsPanelNavigatorSidebarWidth", initialNavigatorWidth); + + this.editorView.minimalSidebarWidth = Preferences.minScriptsSidebarWidth; + this.editorView.minimalMainWidthPercent = minimalViewsContainerWidthPercent; + this.editorView.show(this.splitView.mainElement); + + this._fileSelector = new WebInspector.ScriptsNavigator(this._presentationModel); + + this._fileSelector.show(this.editorView.sidebarElement); + + this._navigatorResizeWidgetElement = document.createElement("div"); + this._navigatorResizeWidgetElement.id = "scripts-navigator-resizer-widget"; + this.editorView.installResizer(this._navigatorResizeWidgetElement); + this.editorView.sidebarElement.appendChild(this._navigatorResizeWidgetElement); + + this._editorContainer = new WebInspector.TabbedEditorContainer(); + this._editorContainer.show(this.editorView.mainElement); + WebInspector.OpenResourceDialog.install(this, this._presentationModel, this.editorView.mainElement); + } else { + this._fileSelector = new WebInspector.ScriptsPanel.ComboBoxFileSelector(this._presentationModel); + this._fileSelector.show(this.splitView.mainElement); + + this._editorContainer = new WebInspector.ScriptsPanel.SingleFileEditorContainer(); + this._editorContainer.show(this.splitView.mainElement); + WebInspector.OpenResourceDialog.install(this, this._presentationModel, this.splitView.mainElement); + } + this._fileSelector.addEventListener(WebInspector.ScriptsPanel.FileSelector.Events.FileSelected, this._fileSelected, this); + this._fileSelector.addEventListener(WebInspector.ScriptsPanel.FileSelector.Events.ReleasedFocusAfterSelection, this._fileSelectorReleasedFocus, this); + this._editorContainer.addEventListener(WebInspector.ScriptsPanel.EditorContainer.Events.EditorSelected, this._editorSelected, this); + + this.splitView.mainElement.appendChild(this.debugSidebarResizeWidgetElement); + + this.sidebarPanes = {}; + this.sidebarPanes.watchExpressions = new WebInspector.WatchExpressionsSidebarPane(); + this.sidebarPanes.callstack = new WebInspector.CallStackSidebarPane(this._presentationModel); + this.sidebarPanes.scopechain = new WebInspector.ScopeChainSidebarPane(); + this.sidebarPanes.jsBreakpoints = new WebInspector.JavaScriptBreakpointsSidebarPane(this._presentationModel, this._showSourceLine.bind(this)); + if (Capabilities.nativeInstrumentationEnabled) { + this.sidebarPanes.domBreakpoints = WebInspector.domBreakpointsSidebarPane; + this.sidebarPanes.xhrBreakpoints = new WebInspector.XHRBreakpointsSidebarPane(); + this.sidebarPanes.eventListenerBreakpoints = new WebInspector.EventListenerBreakpointsSidebarPane(); + } + + if (Preferences.exposeWorkersInspection && !WebInspector.WorkerManager.isWorkerFrontend()) { + WorkerAgent.setWorkerInspectionEnabled(true); + this.sidebarPanes.workerList = new WebInspector.WorkerListSidebarPane(WebInspector.workerManager); + } else + this.sidebarPanes.workers = new WebInspector.WorkersSidebarPane(); + + this._debugSidebarContentsElement = document.createElement("div"); + this._debugSidebarContentsElement.id = "scripts-debug-sidebar-contents"; + this.sidebarElement.appendChild(this._debugSidebarContentsElement); + + for (var pane in this.sidebarPanes) + this._debugSidebarContentsElement.appendChild(this.sidebarPanes[pane].element); + + this.sidebarPanes.callstack.expanded = true; + + this.sidebarPanes.scopechain.expanded = true; + this.sidebarPanes.jsBreakpoints.expanded = true; + + var helpSection = WebInspector.shortcutsScreen.section(WebInspector.UIString("Scripts Panel")); + this.sidebarPanes.callstack.registerShortcuts(helpSection, this.registerShortcut.bind(this)); + var evaluateInConsoleShortcut = WebInspector.KeyboardShortcut.makeDescriptor("e", WebInspector.KeyboardShortcut.Modifiers.Shift | WebInspector.KeyboardShortcut.Modifiers.Ctrl); + helpSection.addKey(evaluateInConsoleShortcut.name, WebInspector.UIString("Evaluate selection in console")); + this.registerShortcut(evaluateInConsoleShortcut.key, this._evaluateSelectionInConsole.bind(this)); + + var scriptOutlineShortcut = WebInspector.JavaScriptOutlineDialog.createShortcut(); + helpSection.addKey(scriptOutlineShortcut.name, WebInspector.UIString("Go to Function")); + + var openResourceShortcut = WebInspector.OpenResourceDialog.createShortcut(); + helpSection.addKey(openResourceShortcut.name, WebInspector.UIString("Open Script")); + + var panelEnablerHeading = WebInspector.UIString("You need to enable debugging before you can use the Scripts panel."); + var panelEnablerDisclaimer = WebInspector.UIString("Enabling debugging will make scripts run slower."); + var panelEnablerButton = WebInspector.UIString("Enable Debugging"); + + this.panelEnablerView = new WebInspector.PanelEnablerView("scripts", panelEnablerHeading, panelEnablerDisclaimer, panelEnablerButton); + this.panelEnablerView.addEventListener("enable clicked", this.enableDebugging, this); + + this.enableToggleButton = new WebInspector.StatusBarButton("", "enable-toggle-status-bar-item"); + this.enableToggleButton.addEventListener("click", this.toggleDebugging, this); + if (!Capabilities.debuggerCausesRecompilation) + this.enableToggleButton.element.addStyleClass("hidden"); + + this._pauseOnExceptionButton = new WebInspector.StatusBarButton("", "scripts-pause-on-exceptions-status-bar-item", 3); + this._pauseOnExceptionButton.addEventListener("click", this._togglePauseOnExceptions, this); + + this._toggleFormatSourceButton = new WebInspector.StatusBarButton(WebInspector.UIString("Pretty print"), "scripts-toggle-pretty-print-status-bar-item"); + this._toggleFormatSourceButton.toggled = false; + this._toggleFormatSourceButton.addEventListener("click", this._toggleFormatSource, this); + + this._scriptViewStatusBarItemsContainer = document.createElement("div"); + this._scriptViewStatusBarItemsContainer.style.display = "inline-block"; + + this._debuggerEnabled = !Capabilities.debuggerCausesRecompilation; + + this._reset(false); + + WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.DebuggerWasEnabled, this._debuggerWasEnabled, this); + WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.DebuggerWasDisabled, this._debuggerWasDisabled, this); + + this._presentationModel.addEventListener(WebInspector.DebuggerPresentationModel.Events.UISourceCodeAdded, this._uiSourceCodeAdded, this) + this._presentationModel.addEventListener(WebInspector.DebuggerPresentationModel.Events.UISourceCodeReplaced, this._uiSourceCodeReplaced, this); + this._presentationModel.addEventListener(WebInspector.DebuggerPresentationModel.Events.UISourceCodeRemoved, this._uiSourceCodeRemoved, this); + this._presentationModel.addEventListener(WebInspector.DebuggerPresentationModel.Events.ConsoleMessageAdded, this._consoleMessageAdded, this); + this._presentationModel.addEventListener(WebInspector.DebuggerPresentationModel.Events.ConsoleMessagesCleared, this._consoleMessagesCleared, this); + this._presentationModel.addEventListener(WebInspector.DebuggerPresentationModel.Events.BreakpointAdded, this._breakpointAdded, this); + this._presentationModel.addEventListener(WebInspector.DebuggerPresentationModel.Events.BreakpointRemoved, this._breakpointRemoved, this); + this._presentationModel.addEventListener(WebInspector.DebuggerPresentationModel.Events.DebuggerPaused, this._debuggerPaused, this); + this._presentationModel.addEventListener(WebInspector.DebuggerPresentationModel.Events.DebuggerResumed, this._debuggerResumed, this); + this._presentationModel.addEventListener(WebInspector.DebuggerPresentationModel.Events.CallFrameSelected, this._callFrameSelected, this); + this._presentationModel.addEventListener(WebInspector.DebuggerPresentationModel.Events.ConsoleCommandEvaluatedInSelectedCallFrame, this._consoleCommandEvaluatedInSelectedCallFrame, this); + this._presentationModel.addEventListener(WebInspector.DebuggerPresentationModel.Events.ExecutionLineChanged, this._executionLineChanged, this); + this._presentationModel.addEventListener(WebInspector.DebuggerPresentationModel.Events.DebuggerReset, this._reset.bind(this, false)); + + var enableDebugger = !Capabilities.debuggerCausesRecompilation || WebInspector.settings.debuggerEnabled.get(); + if (enableDebugger) + WebInspector.debuggerModel.enableDebugger(); + + WebInspector.advancedSearchController.registerSearchScope(new WebInspector.ScriptsSearchScope()); + + this._sourceFramesByUISourceCode = new Map(); +} + +// Keep these in sync with WebCore::ScriptDebugServer +WebInspector.ScriptsPanel.PauseOnExceptionsState = { + DontPauseOnExceptions : "none", + PauseOnAllExceptions : "all", + PauseOnUncaughtExceptions: "uncaught" +}; + +WebInspector.ScriptsPanel.prototype = { + get toolbarItemLabel() + { + return WebInspector.UIString("Scripts"); + }, + + get statusBarItems() + { + return [this.enableToggleButton.element, this._pauseOnExceptionButton.element, this._toggleFormatSourceButton.element, this._scriptViewStatusBarItemsContainer]; + }, + + get defaultFocusedElement() + { + return this._fileSelector.defaultFocusedElement; + }, + + get paused() + { + return this._paused; + }, + + wasShown: function() + { + WebInspector.Panel.prototype.wasShown.call(this); + if (Capabilities.nativeInstrumentationEnabled) + this._debugSidebarContentsElement.insertBefore(this.sidebarPanes.domBreakpoints.element, this.sidebarPanes.xhrBreakpoints.element); + this.sidebarPanes.watchExpressions.show(); + }, + + breakpointsActivated: function() + { + return this.toggleBreakpointsButton.toggled; + }, + + activateBreakpoints: function() + { + if (!this.breakpointsActivated) + this._toggleBreakpointsClicked(); + }, + + _didBuildOutlineChunk: function(event) + { + WebInspector.JavaScriptOutlineDialog.didAddChunk(event.data); + if (event.data.total === event.data.index) { + if (this._outlineWorker) { + this._outlineWorker.terminate(); + delete this._outlineWorker; + } + } + }, + + _uiSourceCodeAdded: function(event) + { + var uiSourceCode = /** @type {WebInspector.UISourceCode} */ event.data; + + if (!uiSourceCode.url) { + // Anonymous sources are shown only when stepping. + return; + } + + this._fileSelector.addUISourceCode(uiSourceCode); + + var lastViewedURL = WebInspector.settings.lastViewedScriptFile.get(); + if (!this._initialViewSelectionProcessed) { + this._initialViewSelectionProcessed = true; + // Option we just added is the only option in files select. + // We have to show corresponding source frame immediately. + this._showAndRevealInFileSelector(uiSourceCode); + // Restore original value of lastViewedScriptFile because + // source frame was shown as a result of initial load. + WebInspector.settings.lastViewedScriptFile.set(lastViewedURL); + } else if (uiSourceCode.url === lastViewedURL) + this._showAndRevealInFileSelector(uiSourceCode); + }, + + _uiSourceCodeRemoved: function(event) + { + var uiSourceCode = /** @type {WebInspector.UISourceCode} */ event.data; + this._removeSourceFrame(uiSourceCode); + }, + + /** + * @param {WebInspector.UISourceCode} uiSourceCode + * @param {boolean} inEditMode + */ + setScriptSourceIsBeingEdited: function(uiSourceCode, inEditMode) + { + this._fileSelector.setScriptSourceIsDirty(uiSourceCode, inEditMode); + var sourceFrame = this._sourceFramesByUISourceCode.get(uiSourceCode) + if (sourceFrame) + this._editorContainer.setSourceFrameIsDirty(sourceFrame, inEditMode); + }, + + _consoleMessagesCleared: function() + { + var uiSourceCodes = this._sourceFramesByUISourceCode; + for (var i = 0; i < uiSourceCodes.length; ++i) { + var sourceFrame = this._sourceFramesByUISourceCode.get(uiSourceCodes[i]) + sourceFrame.clearMessages(); + } + }, + + _consoleMessageAdded: function(event) + { + var message = event.data; + + var sourceFrame = this._sourceFramesByUISourceCode.get(message.uiSourceCode) + if (sourceFrame && sourceFrame.loaded) + sourceFrame.addMessageToSource(message.lineNumber, message.originalMessage); + }, + + _breakpointAdded: function(event) + { + var breakpoint = event.data; + + var sourceFrame = this._sourceFramesByUISourceCode.get(breakpoint.uiSourceCode) + if (sourceFrame && sourceFrame.loaded) + sourceFrame.addBreakpoint(breakpoint.lineNumber, breakpoint.resolved, breakpoint.condition, breakpoint.enabled); + + this.sidebarPanes.jsBreakpoints.addBreakpoint(breakpoint); + }, + + _breakpointRemoved: function(event) + { + var breakpoint = event.data; + + var sourceFrame = this._sourceFramesByUISourceCode.get(breakpoint.uiSourceCode) + if (sourceFrame && sourceFrame.loaded) + sourceFrame.removeBreakpoint(breakpoint.lineNumber); + + this.sidebarPanes.jsBreakpoints.removeBreakpoint(breakpoint.uiSourceCode, breakpoint.lineNumber); + }, + + _consoleCommandEvaluatedInSelectedCallFrame: function(event) + { + this.sidebarPanes.scopechain.update(this._presentationModel.selectedCallFrame); + }, + + _debuggerPaused: function(event) + { + var callFrames = event.data.callFrames; + var details = event.data.details; + + this._paused = true; + this._waitingToPause = false; + this._stepping = false; + + this._updateDebuggerButtons(); + + WebInspector.inspectorView.setCurrentPanel(this); + + this.sidebarPanes.callstack.update(callFrames); + this._updateCallFrame(this._presentationModel.selectedCallFrame); + + if (details.reason === WebInspector.DebuggerModel.BreakReason.DOM) { + this.sidebarPanes.domBreakpoints.highlightBreakpoint(details.auxData); + function didCreateBreakpointHitStatusMessage(element) + { + this.sidebarPanes.callstack.setStatus(element); + } + this.sidebarPanes.domBreakpoints.createBreakpointHitStatusMessage(details.auxData, didCreateBreakpointHitStatusMessage.bind(this)); + } else if (details.reason === WebInspector.DebuggerModel.BreakReason.EventListener) { + var eventName = details.auxData.eventName; + this.sidebarPanes.eventListenerBreakpoints.highlightBreakpoint(details.auxData.eventName); + var eventNameForUI = WebInspector.EventListenerBreakpointsSidebarPane.eventNameForUI(eventName); + this.sidebarPanes.callstack.setStatus(WebInspector.UIString("Paused on a \"%s\" Event Listener.", eventNameForUI)); + } else if (details.reason === WebInspector.DebuggerModel.BreakReason.XHR) { + this.sidebarPanes.xhrBreakpoints.highlightBreakpoint(details.auxData["breakpointURL"]); + this.sidebarPanes.callstack.setStatus(WebInspector.UIString("Paused on a XMLHttpRequest.")); + } else if (details.reason === WebInspector.DebuggerModel.BreakReason.Exception) { + this.sidebarPanes.callstack.setStatus(WebInspector.UIString("Paused on exception: '%s'.", details.auxData.description)); + } else { + function didGetUILocation(uiLocation) + { + if (!this._presentationModel.findBreakpoint(uiLocation.uiSourceCode, uiLocation.lineNumber)) + return; + this.sidebarPanes.jsBreakpoints.highlightBreakpoint(uiLocation.uiSourceCode, uiLocation.lineNumber); + this.sidebarPanes.callstack.setStatus(WebInspector.UIString("Paused on a JavaScript breakpoint.")); + } + callFrames[0].uiLocation(didGetUILocation.bind(this)); + } + + window.focus(); + InspectorFrontendHost.bringToFront(); + }, + + _debuggerResumed: function() + { + this._paused = false; + this._waitingToPause = false; + this._stepping = false; + + this._clearInterface(); + }, + + _debuggerWasEnabled: function() + { + this._setPauseOnExceptions(WebInspector.settings.pauseOnExceptionStateString.get()); + + if (this._debuggerEnabled) + return; + + this._debuggerEnabled = true; + this._reset(true); + }, + + _debuggerWasDisabled: function() + { + if (!this._debuggerEnabled) + return; + + this._debuggerEnabled = false; + this._reset(true); + }, + + _reset: function(preserveItems) + { + delete this.currentQuery; + this.searchCanceled(); + + this._debuggerResumed(); + + delete this._initialViewSelectionProcessed; + + this._editorContainer.reset(); + this._updateScriptViewStatusBarItems(); + + this.sidebarPanes.jsBreakpoints.reset(); + this.sidebarPanes.watchExpressions.reset(); + if (!preserveItems && this.sidebarPanes.workers) + this.sidebarPanes.workers.reset(); + }, + + get visibleView() + { + return this._editorContainer.currentSourceFrame; + }, + + _updateScriptViewStatusBarItems: function() + { + this._scriptViewStatusBarItemsContainer.removeChildren(); + + var sourceFrame = this.visibleView; + if (sourceFrame) { + var statusBarItems = sourceFrame.statusBarItems || []; + for (var i = 0; i < statusBarItems.length; ++i) + this._scriptViewStatusBarItemsContainer.appendChild(statusBarItems[i]); + } + }, + + canShowAnchorLocation: function(anchor) + { + return this._debuggerEnabled && anchor.uiSourceCode; + }, + + showAnchorLocation: function(anchor) + { + this._showSourceLine(anchor.uiSourceCode, anchor.lineNumber); + }, + + showFunctionDefinition: function(functionLocation) + { + WebInspector.showPanelForAnchorNavigation(this); + var uiLocation = this._presentationModel.rawLocationToUILocation(functionLocation); + this._showSourceLine(uiLocation.uiSourceCode, uiLocation.lineNumber); + }, + + showUISourceCode: function(uiSourceCode) + { + this._showSourceLine(uiSourceCode); + }, + + /** + * @param {WebInspector.UISourceCode} uiSourceCode + * @param {number=} lineNumber + */ + _showSourceLine: function(uiSourceCode, lineNumber) + { + var sourceFrame = this._showAndRevealInFileSelector(uiSourceCode); + if (typeof lineNumber === "number") + sourceFrame.highlightLine(lineNumber); + sourceFrame.focus(); + }, + + /** + * @param {WebInspector.UISourceCode} uiSourceCode + * @return {WebInspector.SourceFrame} + */ + _showFile: function(uiSourceCode) + { + var sourceFrame = this._sourceFramesByUISourceCode.get(uiSourceCode) || this._createSourceFrame(uiSourceCode); + + this._editorContainer.showSourceFrame(uiSourceCode.displayName, sourceFrame, uiSourceCode.url); + this._updateScriptViewStatusBarItems(); + + if (uiSourceCode.url) + WebInspector.settings.lastViewedScriptFile.set(uiSourceCode.url); + + return sourceFrame; + }, + + /** + * @param {WebInspector.UISourceCode} uiSourceCode + * @return {WebInspector.SourceFrame} + */ + _showAndRevealInFileSelector: function(uiSourceCode) + { + if (!this._fileSelector.isScriptSourceAdded(uiSourceCode)) + return null; + + this._fileSelector.revealUISourceCode(uiSourceCode); + return this._showFile(uiSourceCode); + }, + + requestVisibleScriptOutline: function() + { + function contentCallback(mimeType, content) + { + if (this._outlineWorker) + this._outlineWorker.terminate(); + this._outlineWorker = new Worker("ScriptFormatterWorker.js"); + this._outlineWorker.onmessage = this._didBuildOutlineChunk.bind(this); + const method = "outline"; + this._outlineWorker.postMessage({ method: method, params: { content: content, id: this.visibleView.uiSourceCode.id } }); + } + + if (this.visibleView.uiSourceCode) + this.visibleView.uiSourceCode.requestContent(contentCallback.bind(this)); + }, + + /** + * @param {WebInspector.UISourceCode} uiSourceCode + */ + _createSourceFrame: function(uiSourceCode) + { + var sourceFrame = new WebInspector.JavaScriptSourceFrame(this, this._presentationModel, uiSourceCode); + + sourceFrame._uiSourceCode = uiSourceCode; + sourceFrame.addEventListener(WebInspector.SourceFrame.Events.Loaded, this._sourceFrameLoaded, this); + this._sourceFramesByUISourceCode.put(uiSourceCode, sourceFrame); + return sourceFrame; + }, + + /** + * @param {WebInspector.UISourceCode} uiSourceCode + */ + _removeSourceFrame: function(uiSourceCode) + { + var sourceFrame = this._sourceFramesByUISourceCode.get(uiSourceCode); + if (!sourceFrame) + return; + this._sourceFramesByUISourceCode.remove(uiSourceCode); + sourceFrame.detach(); + sourceFrame.removeEventListener(WebInspector.SourceFrame.Events.Loaded, this._sourceFrameLoaded, this); + }, + + /** + * @param {Event} event + */ + _uiSourceCodeReplaced: function(event) + { + var oldUISourceCodeList = /** @type {Array.} */ event.data.oldUISourceCodeList; + var uiSourceCodeList = /** @type {Array.} */ event.data.uiSourceCodeList; + + var addedToFileSelector = false; + var sourceFrames = []; + for (var i = 0; i < oldUISourceCodeList.length; ++i) { + var uiSourceCode = oldUISourceCodeList[i]; + var oldSourceFrame = this._sourceFramesByUISourceCode.get(uiSourceCode); + + if (this._fileSelector.isScriptSourceAdded(uiSourceCode)) { + addedToFileSelector = true; + + if (oldSourceFrame) + sourceFrames.push(oldSourceFrame); + this._removeSourceFrame(uiSourceCode); + } + } + + if (addedToFileSelector) { + this._fileSelector.replaceUISourceCodes(oldUISourceCodeList, uiSourceCodeList); + + var shouldReplace = false; + for (var i = 0; i < sourceFrames.length; ++i) { + if (this._editorContainer.isSourceFrameOpen(sourceFrames[i])) { + shouldReplace = true; + break; + } + } + + if (shouldReplace) { + var newUISourceCode = uiSourceCodeList[0]; + var sourceFrame = this._sourceFramesByUISourceCode.get(newUISourceCode) || this._createSourceFrame(newUISourceCode); + this._editorContainer.replaceSourceFrames(sourceFrames, newUISourceCode.displayName, sourceFrame, newUISourceCode.url); + } + } + }, + + _sourceFrameLoaded: function(event) + { + var sourceFrame = /** @type {WebInspector.JavaScriptSourceFrame} */ event.target; + var uiSourceCode = sourceFrame._uiSourceCode; + + var messages = this._presentationModel.messagesForUISourceCode(uiSourceCode); + for (var i = 0; i < messages.length; ++i) { + var message = messages[i]; + sourceFrame.addMessageToSource(message.lineNumber, message.originalMessage); + } + + var breakpoints = this._presentationModel.breakpointsForUISourceCode(uiSourceCode); + for (var i = 0; i < breakpoints.length; ++i) { + var breakpoint = breakpoints[i]; + sourceFrame.addBreakpoint(breakpoint.lineNumber, breakpoint.resolved, breakpoint.condition, breakpoint.enabled); + } + }, + + _clearCurrentExecutionLine: function() + { + if (this._executionSourceFrame) + this._executionSourceFrame.clearExecutionLine(); + delete this._executionSourceFrame; + }, + + _executionLineChanged: function(event) + { + var uiLocation = event.data; + + this._updateExecutionLine(uiLocation); + }, + + _updateExecutionLine: function(uiLocation) + { + this._clearCurrentExecutionLine(); + if (!uiLocation) + return; + + // Anonymous scripts are not added to files select by default. + this._fileSelector.addUISourceCode(uiLocation.uiSourceCode); + + var sourceFrame = this._showAndRevealInFileSelector(uiLocation.uiSourceCode); + sourceFrame.setExecutionLine(uiLocation.lineNumber); + this._executionSourceFrame = sourceFrame; + }, + + _callFrameSelected: function(event) + { + var callFrame = event.data; + + if (!callFrame) + return; + + this._updateCallFrame(callFrame); + }, + + _updateCallFrame: function(callFrame) + { + this.sidebarPanes.scopechain.update(callFrame); + this.sidebarPanes.watchExpressions.refreshExpressions(); + this.sidebarPanes.callstack.selectedCallFrame = callFrame; + this._updateExecutionLine(this._presentationModel.executionLineLocation); + }, + + _editorSelected: function(event) + { + var sourceFrame = /** @type {WebInspector.SourceFrame} */ event.data; + this._fileSelector.revealUISourceCode(sourceFrame._uiSourceCode); + }, + + _fileSelected: function(event) + { + var uiSourceCode = /** @type {WebInspector.UISourceCode} */ event.data; + this._showFile(uiSourceCode); + }, + + _fileSelectorReleasedFocus: function(event) + { + var uiSourceCode = /** @type {WebInspector.UISourceCode} */ event.data; + var sourceFrame = this._sourceFramesByUISourceCode.get(uiSourceCode); + if (sourceFrame) + sourceFrame.focus(); + }, + + _setPauseOnExceptions: function(pauseOnExceptionsState) + { + pauseOnExceptionsState = pauseOnExceptionsState || WebInspector.ScriptsPanel.PauseOnExceptionsState.DontPauseOnExceptions; + function callback(error) + { + if (error) + return; + if (pauseOnExceptionsState == WebInspector.ScriptsPanel.PauseOnExceptionsState.DontPauseOnExceptions) + this._pauseOnExceptionButton.title = WebInspector.UIString("Don't pause on exceptions.\nClick to Pause on all exceptions."); + else if (pauseOnExceptionsState == WebInspector.ScriptsPanel.PauseOnExceptionsState.PauseOnAllExceptions) + this._pauseOnExceptionButton.title = WebInspector.UIString("Pause on all exceptions.\nClick to Pause on uncaught exceptions."); + else if (pauseOnExceptionsState == WebInspector.ScriptsPanel.PauseOnExceptionsState.PauseOnUncaughtExceptions) + this._pauseOnExceptionButton.title = WebInspector.UIString("Pause on uncaught exceptions.\nClick to Not pause on exceptions."); + + this._pauseOnExceptionButton.state = pauseOnExceptionsState; + WebInspector.settings.pauseOnExceptionStateString.set(pauseOnExceptionsState); + } + DebuggerAgent.setPauseOnExceptions(pauseOnExceptionsState, callback.bind(this)); + }, + + _updateDebuggerButtons: function() + { + if (this._debuggerEnabled) { + this.enableToggleButton.title = WebInspector.UIString("Debugging enabled. Click to disable."); + this.enableToggleButton.toggled = true; + this._pauseOnExceptionButton.visible = true; + this.panelEnablerView.detach(); + } else { + this.enableToggleButton.title = WebInspector.UIString("Debugging disabled. Click to enable."); + this.enableToggleButton.toggled = false; + this._pauseOnExceptionButton.visible = false; + this.panelEnablerView.show(this.element); + } + + if (this._paused) { + this.pauseButton.addStyleClass("paused"); + + this.pauseButton.disabled = false; + this.stepOverButton.disabled = false; + this.stepIntoButton.disabled = false; + this.stepOutButton.disabled = false; + + this.debuggerStatusElement.textContent = WebInspector.UIString("Paused"); + } else { + this.pauseButton.removeStyleClass("paused"); + + this.pauseButton.disabled = this._waitingToPause; + this.stepOverButton.disabled = true; + this.stepIntoButton.disabled = true; + this.stepOutButton.disabled = true; + + if (this._waitingToPause) + this.debuggerStatusElement.textContent = WebInspector.UIString("Pausing"); + else if (this._stepping) + this.debuggerStatusElement.textContent = WebInspector.UIString("Stepping"); + else + this.debuggerStatusElement.textContent = ""; + } + }, + + _clearInterface: function() + { + this.sidebarPanes.callstack.update(null); + this.sidebarPanes.scopechain.update(null); + this.sidebarPanes.jsBreakpoints.clearBreakpointHighlight(); + if (Capabilities.nativeInstrumentationEnabled) { + this.sidebarPanes.domBreakpoints.clearBreakpointHighlight(); + this.sidebarPanes.eventListenerBreakpoints.clearBreakpointHighlight(); + this.sidebarPanes.xhrBreakpoints.clearBreakpointHighlight(); + } + + this._clearCurrentExecutionLine(); + this._updateDebuggerButtons(); + }, + + get debuggingEnabled() + { + return this._debuggerEnabled; + }, + + enableDebugging: function() + { + if (this._debuggerEnabled) + return; + this.toggleDebugging(this.panelEnablerView.alwaysEnabled); + }, + + disableDebugging: function() + { + if (!this._debuggerEnabled) + return; + this.toggleDebugging(this.panelEnablerView.alwaysEnabled); + }, + + toggleDebugging: function(optionalAlways) + { + this._paused = false; + this._waitingToPause = false; + this._stepping = false; + + if (this._debuggerEnabled) { + WebInspector.settings.debuggerEnabled.set(false); + WebInspector.debuggerModel.disableDebugger(); + } else { + WebInspector.settings.debuggerEnabled.set(!!optionalAlways); + WebInspector.debuggerModel.enableDebugger(); + } + }, + + _togglePauseOnExceptions: function() + { + var nextStateMap = {}; + var stateEnum = WebInspector.ScriptsPanel.PauseOnExceptionsState; + nextStateMap[stateEnum.DontPauseOnExceptions] = stateEnum.PauseOnAllExceptions; + nextStateMap[stateEnum.PauseOnAllExceptions] = stateEnum.PauseOnUncaughtExceptions; + nextStateMap[stateEnum.PauseOnUncaughtExceptions] = stateEnum.DontPauseOnExceptions; + this._setPauseOnExceptions(nextStateMap[this._pauseOnExceptionButton.state]); + }, + + _togglePause: function() + { + if (this._paused) { + this._paused = false; + this._waitingToPause = false; + DebuggerAgent.resume(); + } else { + this._stepping = false; + this._waitingToPause = true; + DebuggerAgent.pause(); + } + + this._clearInterface(); + }, + + _stepOverClicked: function() + { + if (!this._paused) + return; + + this._paused = false; + this._stepping = true; + + this._clearInterface(); + + DebuggerAgent.stepOver(); + }, + + _stepIntoClicked: function() + { + if (!this._paused) + return; + + this._paused = false; + this._stepping = true; + + this._clearInterface(); + + DebuggerAgent.stepInto(); + }, + + _stepOutClicked: function() + { + if (!this._paused) + return; + + this._paused = false; + this._stepping = true; + + this._clearInterface(); + + DebuggerAgent.stepOut(); + }, + + _toggleBreakpointsClicked: function() + { + this.toggleBreakpointsButton.toggled = !this.toggleBreakpointsButton.toggled; + if (this.toggleBreakpointsButton.toggled) { + DebuggerAgent.setBreakpointsActive(true); + this.toggleBreakpointsButton.title = WebInspector.UIString("Deactivate all breakpoints."); + WebInspector.inspectorView.element.removeStyleClass("breakpoints-deactivated"); + } else { + DebuggerAgent.setBreakpointsActive(false); + this.toggleBreakpointsButton.title = WebInspector.UIString("Activate all breakpoints."); + WebInspector.inspectorView.element.addStyleClass("breakpoints-deactivated"); + } + }, + + _evaluateSelectionInConsole: function() + { + var selection = window.getSelection(); + if (selection.type === "Range" && !selection.isCollapsed) + WebInspector.evaluateInConsole(selection.toString()); + }, + + _createDebugToolbar: function() + { + var debugToolbar = document.createElement("div"); + debugToolbar.className = "status-bar"; + debugToolbar.id = "scripts-debug-toolbar"; + + var title, handler, shortcuts; + var platformSpecificModifier = WebInspector.KeyboardShortcut.Modifiers.CtrlOrMeta; + + // Continue. + title = WebInspector.UIString("Pause script execution (%s)."); + handler = this._togglePause.bind(this); + shortcuts = []; + shortcuts.push(WebInspector.KeyboardShortcut.makeDescriptor(WebInspector.KeyboardShortcut.Keys.F8)); + shortcuts.push(WebInspector.KeyboardShortcut.makeDescriptor(WebInspector.KeyboardShortcut.Keys.Slash, platformSpecificModifier)); + this.pauseButton = this._createButtonAndRegisterShortcuts("scripts-pause", title, handler, shortcuts, WebInspector.UIString("Pause/Continue")); + debugToolbar.appendChild(this.pauseButton); + + // Step over. + title = WebInspector.UIString("Step over next function call (%s)."); + handler = this._stepOverClicked.bind(this); + shortcuts = []; + shortcuts.push(WebInspector.KeyboardShortcut.makeDescriptor(WebInspector.KeyboardShortcut.Keys.F10)); + shortcuts.push(WebInspector.KeyboardShortcut.makeDescriptor(WebInspector.KeyboardShortcut.Keys.SingleQuote, platformSpecificModifier)); + this.stepOverButton = this._createButtonAndRegisterShortcuts("scripts-step-over", title, handler, shortcuts, WebInspector.UIString("Step over")); + debugToolbar.appendChild(this.stepOverButton); + + // Step into. + title = WebInspector.UIString("Step into next function call (%s)."); + handler = this._stepIntoClicked.bind(this); + shortcuts = []; + shortcuts.push(WebInspector.KeyboardShortcut.makeDescriptor(WebInspector.KeyboardShortcut.Keys.F11)); + shortcuts.push(WebInspector.KeyboardShortcut.makeDescriptor(WebInspector.KeyboardShortcut.Keys.Semicolon, platformSpecificModifier)); + this.stepIntoButton = this._createButtonAndRegisterShortcuts("scripts-step-into", title, handler, shortcuts, WebInspector.UIString("Step into")); + debugToolbar.appendChild(this.stepIntoButton); + + // Step out. + title = WebInspector.UIString("Step out of current function (%s)."); + handler = this._stepOutClicked.bind(this); + shortcuts = []; + shortcuts.push(WebInspector.KeyboardShortcut.makeDescriptor(WebInspector.KeyboardShortcut.Keys.F11, WebInspector.KeyboardShortcut.Modifiers.Shift)); + shortcuts.push(WebInspector.KeyboardShortcut.makeDescriptor(WebInspector.KeyboardShortcut.Keys.Semicolon, WebInspector.KeyboardShortcut.Modifiers.Shift | platformSpecificModifier)); + this.stepOutButton = this._createButtonAndRegisterShortcuts("scripts-step-out", title, handler, shortcuts, WebInspector.UIString("Step out")); + debugToolbar.appendChild(this.stepOutButton); + + this.toggleBreakpointsButton = new WebInspector.StatusBarButton(WebInspector.UIString("Deactivate all breakpoints."), "toggle-breakpoints"); + this.toggleBreakpointsButton.toggled = true; + this.toggleBreakpointsButton.addEventListener("click", this._toggleBreakpointsClicked, this); + debugToolbar.appendChild(this.toggleBreakpointsButton.element); + + this.debuggerStatusElement = document.createElement("div"); + this.debuggerStatusElement.id = "scripts-debugger-status"; + debugToolbar.appendChild(this.debuggerStatusElement); + + return debugToolbar; + }, + + _createButtonAndRegisterShortcuts: function(buttonId, buttonTitle, handler, shortcuts, shortcutDescription) + { + var button = document.createElement("button"); + button.className = "status-bar-item"; + button.id = buttonId; + button.title = String.vsprintf(buttonTitle, [shortcuts[0].name]); + button.disabled = true; + button.appendChild(document.createElement("img")); + button.addEventListener("click", handler, false); + + var shortcutNames = []; + for (var i = 0; i < shortcuts.length; ++i) { + this.registerShortcut(shortcuts[i].key, handler); + shortcutNames.push(shortcuts[i].name); + } + var section = WebInspector.shortcutsScreen.section(WebInspector.UIString("Scripts Panel")); + section.addAlternateKeys(shortcutNames, shortcutDescription); + + return button; + }, + + searchCanceled: function() + { + if (this._searchView) + this._searchView.searchCanceled(); + + delete this._searchView; + delete this._searchQuery; + }, + + performSearch: function(query) + { + WebInspector.searchController.updateSearchMatchesCount(0, this); + + if (!this.visibleView) + return; + + // Call searchCanceled since it will reset everything we need before doing a new search. + this.searchCanceled(); + + this._searchView = this.visibleView; + this._searchQuery = query; + + function finishedCallback(view, searchMatches) + { + if (!searchMatches) + return; + + WebInspector.searchController.updateSearchMatchesCount(searchMatches, this); + view.jumpToFirstSearchResult(); + WebInspector.searchController.updateCurrentMatchIndex(view.currentSearchResultIndex, this); + } + + this._searchView.performSearch(query, finishedCallback.bind(this)); + }, + + jumpToNextSearchResult: function() + { + if (!this._searchView) + return; + + if (this._searchView !== this.visibleView) { + this.performSearch(this._searchQuery); + return; + } + + if (this._searchView.showingLastSearchResult()) + this._searchView.jumpToFirstSearchResult(); + else + this._searchView.jumpToNextSearchResult(); + WebInspector.searchController.updateCurrentMatchIndex(this._searchView.currentSearchResultIndex, this); + }, + + jumpToPreviousSearchResult: function() + { + if (!this._searchView) + return; + + if (this._searchView !== this.visibleView) { + this.performSearch(this._searchQuery); + if (this._searchView) + this._searchView.jumpToLastSearchResult(); + return; + } + + if (this._searchView.showingFirstSearchResult()) + this._searchView.jumpToLastSearchResult(); + else + this._searchView.jumpToPreviousSearchResult(); + WebInspector.searchController.updateCurrentMatchIndex(this._searchView.currentSearchResultIndex, this); + }, + + _toggleFormatSource: function() + { + this._toggleFormatSourceButton.toggled = !this._toggleFormatSourceButton.toggled; + this._presentationModel.setFormatSource(this._toggleFormatSourceButton.toggled); + }, + + addToWatch: function(expression) + { + this.sidebarPanes.watchExpressions.addExpression(expression); + } +} + +WebInspector.ScriptsPanel.prototype.__proto__ = WebInspector.Panel.prototype; + +/** + * @interface + */ +WebInspector.ScriptsPanel.FileSelector = function() { } + +WebInspector.ScriptsPanel.FileSelector.Events = { + FileSelected: "FileSelected", + ReleasedFocusAfterSelection: "ReleasedFocusAfterSelection" +} + +WebInspector.ScriptsPanel.FileSelector.prototype = { + /** + * @type {Element} + */ + get defaultFocusedElement() { }, + + /** + * @param {Element} element + */ + show: function(element) { }, + + /** + * @param {WebInspector.UISourceCode} uiSourceCode + */ + addUISourceCode: function(uiSourceCode) { }, + + /** + * @param {WebInspector.UISourceCode} uiSourceCode + * @return {boolean} + */ + isScriptSourceAdded: function(uiSourceCode) { }, + + /** + * @param {WebInspector.UISourceCode} uiSourceCode + */ + revealUISourceCode: function(uiSourceCode) { return false; }, + + /** + * @param {WebInspector.UISourceCode} uiSourceCode + * @param {boolean} isDirty + */ + setScriptSourceIsDirty: function(uiSourceCode, isDirty) { }, + + /** + * @param {Array.} oldUISourceCodeList + * @param {Array.} uiSourceCodeList + */ + replaceUISourceCodes: function(oldUISourceCodeList, uiSourceCodeList) { } +} + +/** + * @interface + */ +WebInspector.ScriptsPanel.EditorContainer = function() { } + +WebInspector.ScriptsPanel.EditorContainer.Events = { + EditorSelected: "EditorSelected" +} + +WebInspector.ScriptsPanel.EditorContainer.prototype = { + /** + * @type {WebInspector.SourceFrame} + */ + get currentSourceFrame() { }, + + /** + * @param {Element} element + */ + show: function(element) { }, + + /** + * @param {string} title + * @param {WebInspector.SourceFrame} sourceFrame + * @param {string} tooltip + */ + showSourceFrame: function(title, sourceFrame, tooltip) { }, + + /** + * @param {WebInspector.SourceFrame} sourceFrame + * @return {boolean} + */ + isSourceFrameOpen: function(sourceFrame) { return false; }, + + /** + * @param {Array.} oldSourceFrames + * @param {string} title + * @param {WebInspector.SourceFrame} sourceFrame + * @param {string} tooltip + */ + replaceSourceFrames: function(oldSourceFrames, title, sourceFrame, tooltip) { }, + + /** + * @param {WebInspector.SourceFrame} sourceFrame + * @param {boolean} isDirty + */ + setSourceFrameIsDirty: function(sourceFrame, isDirty) { }, + + reset: function() { } +} + +/** + * @implements {WebInspector.ScriptsPanel.FileSelector} + * @extends {WebInspector.Object} + * @constructor + */ +WebInspector.ScriptsPanel.ComboBoxFileSelector = function(presentationModel) +{ + WebInspector.Object.call(this); + this.editorToolbar = this._createEditorToolbar(); + + this._presentationModel = presentationModel; + WebInspector.settings.showScriptFolders.addChangeListener(this._showScriptFoldersSettingChanged.bind(this)); + WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.DebuggerWasDisabled, this._reset, this); + this._presentationModel.addEventListener(WebInspector.DebuggerPresentationModel.Events.DebuggerReset, this._reset, this); + + this._backForwardList = []; +} + +WebInspector.ScriptsPanel.ComboBoxFileSelector.prototype = { + get defaultFocusedElement() + { + return this._filesSelectElement; + }, + + /** + * @param {Element} element + */ + show: function(element) + { + element.appendChild(this.editorToolbar); + }, + + /** + * @param {WebInspector.UISourceCode} uiSourceCode + */ + addUISourceCode: function(uiSourceCode) + { + if (uiSourceCode._option) + return; + + this._addOptionToFilesSelect(uiSourceCode); + }, + + /** + * @param {WebInspector.UISourceCode} uiSourceCode + * @return {boolean} + */ + isScriptSourceAdded: function(uiSourceCode) + { + return !!uiSourceCode._option; + }, + + /** + * @param {WebInspector.UISourceCode} uiSourceCode + */ + revealUISourceCode: function(uiSourceCode) + { + this._innerRevealUISourceCode(uiSourceCode, true); + }, + + /** + * @param {WebInspector.UISourceCode} uiSourceCode + * @param {boolean} addToHistory + */ + _innerRevealUISourceCode: function(uiSourceCode, addToHistory) + { + if (!uiSourceCode._option) + return; + + if (addToHistory) + this._addToHistory(uiSourceCode); + + this._updateBackAndForwardButtons(); + this._filesSelectElement.selectedIndex = uiSourceCode._option.index; + + return; + }, + + /** + * @param {WebInspector.UISourceCode} uiSourceCode + */ + _addToHistory: function(uiSourceCode) + { + var oldIndex = this._currentBackForwardIndex; + if (oldIndex >= 0) + this._backForwardList.splice(oldIndex + 1, this._backForwardList.length - oldIndex); + + // Check for a previous entry of the same object in _backForwardList. If one is found, remove it. + var previousEntryIndex = this._backForwardList.indexOf(uiSourceCode); + if (previousEntryIndex !== -1) + this._backForwardList.splice(previousEntryIndex, 1); + + this._backForwardList.push(uiSourceCode); + this._currentBackForwardIndex = this._backForwardList.length - 1; + }, + + /** + * @param {Array.} oldUISourceCodeList + * @param {Array.} uiSourceCodeList + */ + replaceUISourceCodes: function(oldUISourceCodeList, uiSourceCodeList) + { + var visible = false; + var selectedOption = this._filesSelectElement[this._filesSelectElement.selectedIndex]; + var selectedUISourceCode = selectedOption ? selectedOption._uiSourceCode : null; + for (var i = 0; i < oldUISourceCodeList.length; ++i) { + var uiSourceCode = oldUISourceCodeList[i]; + if (selectedUISourceCode === oldUISourceCodeList[i]) + visible = true; + var option = uiSourceCode._option; + // FIXME: find out why we are getting here with option detached. + if (option && this._filesSelectElement === option.parentElement) + this._filesSelectElement.removeChild(option); + } + + for (var i = 0; i < uiSourceCodeList.length; ++i) + this._addOptionToFilesSelect(uiSourceCodeList[i]); + + if (visible) + this._filesSelectElement.selectedIndex = uiSourceCodeList[0]._option.index; + }, + + _showScriptFoldersSettingChanged: function() + { + var selectedOption = this._filesSelectElement[this._filesSelectElement.selectedIndex]; + var uiSourceCode = selectedOption ? selectedOption._uiSourceCode : null; + + var options = Array.prototype.slice.call(this._filesSelectElement); + this._resetFilesSelect(); + for (var i = 0; i < options.length; ++i) { + if (options[i]._uiSourceCode) + this._addOptionToFilesSelect(options[i]._uiSourceCode); + } + + if (uiSourceCode) { + var index = uiSourceCode._option.index; + if (typeof index === "number") + this._filesSelectElement.selectedIndex = index; + } + }, + + _reset: function() + { + this._backForwardList = []; + this._currentBackForwardIndex = -1; + this._updateBackAndForwardButtons(); + this._resetFilesSelect(); + }, + + /** + * @param {WebInspector.UISourceCode} uiSourceCode + * @param {boolean} isDirty + */ + setScriptSourceIsDirty: function(uiSourceCode, isDirty) + { + var option = uiSourceCode._option; + if (!option) + return; + if (isDirty) + option.text = option.text.replace(/[^*]$/, "$&*"); + else + option.text = option.text.replace(/[*]$/, ""); + }, + + /** + * @return {Element} + */ + _createEditorToolbar: function() + { + var editorToolbar = document.createElement("div"); + editorToolbar.className = "status-bar"; + editorToolbar.id = "scripts-editor-toolbar"; + + this.backButton = document.createElement("button"); + this.backButton.className = "status-bar-item"; + this.backButton.id = "scripts-back"; + this.backButton.title = WebInspector.UIString("Show the previous script resource."); + this.backButton.disabled = true; + this.backButton.appendChild(document.createElement("img")); + this.backButton.addEventListener("click", this._goBack.bind(this), false); + editorToolbar.appendChild(this.backButton); + + this.forwardButton = document.createElement("button"); + this.forwardButton.className = "status-bar-item"; + this.forwardButton.id = "scripts-forward"; + this.forwardButton.title = WebInspector.UIString("Show the next script resource."); + this.forwardButton.disabled = true; + this.forwardButton.appendChild(document.createElement("img")); + this.forwardButton.addEventListener("click", this._goForward.bind(this), false); + editorToolbar.appendChild(this.forwardButton); + + this._filesSelectElement = document.createElement("select"); + this._filesSelectElement.className = "status-bar-item"; + this._filesSelectElement.id = "scripts-files"; + this._filesSelectElement.addEventListener("change", this._filesSelectChanged.bind(this, true), false); + this._filesSelectElement.addEventListener("keyup", this._filesSelectChanged.bind(this, false), false); + editorToolbar.appendChild(this._filesSelectElement); + + return editorToolbar; + }, + + /** + * @param {WebInspector.UISourceCode} uiSourceCode + */ + _addOptionToFilesSelect: function(uiSourceCode) + { + var showScriptFolders = WebInspector.settings.showScriptFolders.get(); + + var select = this._filesSelectElement; + if (!select.domainOptions) + select.domainOptions = {}; + if (!select.folderOptions) + select.folderOptions = {}; + + var option = document.createElement("option"); + option._uiSourceCode = uiSourceCode; + var parsedURL = uiSourceCode.url.asParsedURL(); + + const indent = WebInspector.isMac() ? "" : "\u00a0\u00a0\u00a0\u00a0"; + + option.displayName = uiSourceCode.displayName; + + var contentScriptPrefix = uiSourceCode.isContentScript ? "2:" : "0:"; + var folderName = uiSourceCode.folderName; + var domain = uiSourceCode.domain; + + if (uiSourceCode.isContentScript && domain) { + // Render extension domain as a path in structured view + folderName = domain + (folderName ? folderName : ""); + domain = ""; + } + + var folderNameForSorting = contentScriptPrefix + (domain ? domain + "\t\t" : "") + folderName; + + if (showScriptFolders) { + option.text = indent + uiSourceCode.displayName; + option.nameForSorting = folderNameForSorting + "\t/\t" + uiSourceCode.fileName; // Use '\t' to make files stick to their folder. + } else { + option.text = uiSourceCode.displayName; + // Content script should contain its domain name as a prefix + if (uiSourceCode.isContentScript && folderName) + option.text = folderName + "/" + option.text; + option.nameForSorting = contentScriptPrefix + option.text; + } + option.title = uiSourceCode.url; + + if (uiSourceCode.isContentScript) + option.addStyleClass("extension-script"); + + function insertOrdered(option) + { + function optionCompare(a, b) + { + return a.nameForSorting.localeCompare(b.nameForSorting); + } + var insertionIndex = insertionIndexForObjectInListSortedByFunction(option, select.childNodes, optionCompare); + select.insertBefore(option, insertionIndex < 0 ? null : select.childNodes.item(insertionIndex)); + } + + insertOrdered(option); + + if (uiSourceCode.isContentScript && !select.contentScriptSection) { + var contentScriptSection = document.createElement("option"); + contentScriptSection.text = "\u2014 " + WebInspector.UIString("Content scripts") + " \u2014"; + contentScriptSection.disabled = true; + contentScriptSection.nameForSorting = "1/ContentScriptSeparator"; + select.contentScriptSection = contentScriptSection; + insertOrdered(contentScriptSection); + } + + if (showScriptFolders && !uiSourceCode.isContentScript && domain && !select.domainOptions[domain]) { + var domainOption = document.createElement("option"); + domainOption.text = "\u2014 " + domain + " \u2014"; + domainOption.nameForSorting = "0:" + domain; + domainOption.disabled = true; + select.domainOptions[domain] = domainOption; + insertOrdered(domainOption); + } + + if (showScriptFolders && folderName && !select.folderOptions[folderNameForSorting]) { + var folderOption = document.createElement("option"); + folderOption.text = folderName; + folderOption.nameForSorting = folderNameForSorting; + folderOption.disabled = true; + select.folderOptions[folderNameForSorting] = folderOption; + insertOrdered(folderOption); + } + + option._uiSourceCode = uiSourceCode; + uiSourceCode._option = option; + }, + + _resetFilesSelect: function() + { + this._filesSelectElement.removeChildren(); + this._filesSelectElement.domainOptions = {}; + this._filesSelectElement.folderOptions = {}; + delete this._filesSelectElement.contentScriptSection; + }, + + _updateBackAndForwardButtons: function() + { + this.backButton.disabled = this._currentBackForwardIndex <= 0; + this.forwardButton.disabled = this._currentBackForwardIndex >= (this._backForwardList.length - 1); + }, + + _goBack: function() + { + if (this._currentBackForwardIndex <= 0) { + console.error("Can't go back from index " + this._currentBackForwardIndex); + return; + } + + var uiSourceCode = this._backForwardList[--this._currentBackForwardIndex]; + this._innerRevealUISourceCode(uiSourceCode, false); + this.dispatchEventToListeners(WebInspector.ScriptsPanel.FileSelector.Events.FileSelected, uiSourceCode); + }, + + _goForward: function() + { + if (this._currentBackForwardIndex >= this._backForwardList.length - 1) { + console.error("Can't go forward from index " + this._currentBackForwardIndex); + return; + } + + var uiSourceCode = this._backForwardList[++this._currentBackForwardIndex]; + this._innerRevealUISourceCode(uiSourceCode, false); + this.dispatchEventToListeners(WebInspector.ScriptsPanel.FileSelector.Events.FileSelected, uiSourceCode); + }, + + /** + * @param {boolean} focusSource + */ + _filesSelectChanged: function(focusSource) + { + if (this._filesSelectElement.selectedIndex === -1) + return; + + var uiSourceCode = this._filesSelectElement[this._filesSelectElement.selectedIndex]._uiSourceCode; + this._innerRevealUISourceCode(uiSourceCode, true); + this.dispatchEventToListeners(WebInspector.ScriptsPanel.FileSelector.Events.FileSelected, uiSourceCode); + if (focusSource) + this.dispatchEventToListeners(WebInspector.ScriptsPanel.FileSelector.Events.ReleasedFocusAfterSelection, uiSourceCode); + } +} + +WebInspector.ScriptsPanel.ComboBoxFileSelector.prototype.__proto__ = WebInspector.Object.prototype; + +/** + * @implements {WebInspector.ScriptsPanel.EditorContainer} + * @extends {WebInspector.Object} + * @constructor + */ +WebInspector.ScriptsPanel.SingleFileEditorContainer = function() +{ + this.element = document.createElement("div"); + this.element.className = "scripts-views-container"; +} + +WebInspector.ScriptsPanel.SingleFileEditorContainer.prototype = { + /** + * @type {WebInspector.SourceFrame} + */ + get currentSourceFrame() + { + return this._currentSourceFrame; + }, + + /** + * @param {Element} element + */ + show: function(element) + { + element.appendChild(this.element); + }, + + /** + * @param {string} title + * @param {WebInspector.SourceFrame} sourceFrame + * @param {string} tooltip + */ + showSourceFrame: function(title, sourceFrame, tooltip) + { + if (this._currentSourceFrame === sourceFrame) + return; + + if (this._currentSourceFrame) + this._currentSourceFrame.detach(); + + this._currentSourceFrame = sourceFrame; + + if (sourceFrame) + sourceFrame.show(this.element); + }, + + /** + * @param {WebInspector.SourceFrame} sourceFrame + * @return {boolean} + */ + isSourceFrameOpen: function(sourceFrame) + { + return this._currentSourceFrame && this._currentSourceFrame === sourceFrame; + }, + + /** + * @param {Array.} oldSourceFrames + * @param {string} title + * @param {WebInspector.SourceFrame} sourceFrame + * @param {string} tooltip + */ + replaceSourceFrames: function(oldSourceFrames, title, sourceFrame, tooltip) + { + this._currentSourceFrame.detach(); + this._currentSourceFrame = null; + this.showSourceFrame(title, sourceFrame, tooltip); + }, + + /** + * @param {WebInspector.SourceFrame} sourceFrame + * @param {boolean} isDirty + */ + setSourceFrameIsDirty: function(sourceFrame, isDirty) + { + // Do nothing. + }, + + reset: function() + { + if (this._currentSourceFrame) { + this._currentSourceFrame.detach(); + this._currentSourceFrame = null; + } + } +} + +WebInspector.ScriptsPanel.SingleFileEditorContainer.prototype.__proto__ = WebInspector.Object.prototype; +/* ScriptsNavigator.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY GOOGLE INC. AND ITS CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE INC. + * OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @implements {WebInspector.ScriptsPanel.FileSelector} + * @extends {WebInspector.Object} + * @constructor + */ +WebInspector.ScriptsNavigator = function(presentationModel) +{ + WebInspector.Object.call(this); + + this._tabbedPane = new WebInspector.TabbedPane(); + this._tabbedPane.shrinkableTabs = true; + + this._presentationModel = presentationModel; + + this._tabbedPane.element.id = "scripts-navigator-tabbed-pane"; + + this._treeSearchBox = document.createElement("div"); + this._treeSearchBox.id = "scripts-navigator-tree-search-box"; + + this._navigatorScriptsTreeElement = document.createElement("ol"); + var scriptsView = new WebInspector.View(); + scriptsView.element.addStyleClass("outline-disclosure"); + scriptsView.element.addStyleClass("navigator"); + scriptsView.element.appendChild(this._navigatorScriptsTreeElement); + this._navigatorScriptsTree = new WebInspector.NavigatorTreeOutline(this, this._navigatorScriptsTreeElement); + this._tabbedPane.appendTab(WebInspector.ScriptsNavigator.ScriptsTab, WebInspector.UIString("Scripts"), scriptsView); + this._tabbedPane.selectTab(WebInspector.ScriptsNavigator.ScriptsTab); + + this._navigatorContentScriptsTreeElement = document.createElement("ol"); + var contentScriptsView = new WebInspector.View(); + contentScriptsView.element.addStyleClass("outline-disclosure"); + contentScriptsView.element.addStyleClass("navigator"); + contentScriptsView.element.appendChild(this._navigatorContentScriptsTreeElement); + this._navigatorContentScriptsTree = new WebInspector.NavigatorTreeOutline(this, this._navigatorContentScriptsTreeElement); + this._tabbedPane.appendTab(WebInspector.ScriptsNavigator.ContentScriptsTab, WebInspector.UIString("Content scripts"), contentScriptsView); + + this._folderTreeElements = {}; + + this._scriptTreeElementsByUISourceCode = new Map(); + + WebInspector.settings.showScriptFolders.addChangeListener(this._showScriptFoldersSettingChanged.bind(this)); + + WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.DebuggerWasDisabled, this._reset, this); + this._presentationModel.addEventListener(WebInspector.DebuggerPresentationModel.Events.DebuggerReset, this._reset, this); +} + +WebInspector.ScriptsNavigator.ScriptsTab = "scripts"; +WebInspector.ScriptsNavigator.ContentScriptsTab = "contentScripts"; + +WebInspector.ScriptsNavigator.prototype = { + /** + * @type {Element} + */ + get defaultFocusedElement() + { + return this._navigatorScriptsTreeElement; + }, + + /** + * @param {Element} element + */ + show: function(element) + { + this._tabbedPane.show(element); + element.appendChild(this._treeSearchBox); + }, + + /** + * @param {WebInspector.UISourceCode} uiSourceCode + */ + addUISourceCode: function(uiSourceCode) + { + if (this._scriptTreeElementsByUISourceCode.get(uiSourceCode)) + return; + + var scriptTitle = uiSourceCode.fileName || WebInspector.UIString("(program)"); + var scriptTreeElement = new WebInspector.NavigatorScriptTreeElement(this, uiSourceCode, scriptTitle); + this._scriptTreeElementsByUISourceCode.put(uiSourceCode, scriptTreeElement); + + var folderTreeElement = this._getOrCreateFolderTreeElement(uiSourceCode.isContentScript, uiSourceCode.domain, uiSourceCode.folderName); + folderTreeElement.appendChild(scriptTreeElement); + }, + + /** + * @param {WebInspector.UISourceCode} uiSourceCode + * @return {boolean} + */ + isScriptSourceAdded: function(uiSourceCode) + { + var scriptTreeElement = this._scriptTreeElementsByUISourceCode.get(uiSourceCode); + return !!scriptTreeElement; + }, + + /** + * @param {WebInspector.UISourceCode} uiSourceCode + */ + revealUISourceCode: function(uiSourceCode) + { + this._lastSelectedUISourceCode = uiSourceCode; + this._tabbedPane.selectTab(uiSourceCode.isContentScript ? WebInspector.ScriptsNavigator.ContentScriptsTab : WebInspector.ScriptsNavigator.ScriptsTab); + + var scriptTreeElement = this._scriptTreeElementsByUISourceCode.get(uiSourceCode); + scriptTreeElement.revealAndSelect(true); + }, + + /** + * @param {WebInspector.UISourceCode} uiSourceCode + * @param {boolean} isDirty + */ + setScriptSourceIsDirty: function(uiSourceCode, isDirty) + { + // Do nothing. + }, + + /** + * @param {Array.} oldUISourceCodeList + * @param {Array.} uiSourceCodeList + */ + replaceUISourceCodes: function(oldUISourceCodeList, uiSourceCodeList) + { + var selected = false; + for (var i = 0; i < oldUISourceCodeList.length; ++i) { + var uiSourceCode = oldUISourceCodeList[i]; + var treeElement = this._scriptTreeElementsByUISourceCode.get(uiSourceCode); + if (treeElement) { + if (this._lastSelectedUISourceCode && this._lastSelectedUISourceCode === uiSourceCode) + selected = true; + this.removeUISourceCode(uiSourceCode); + } + } + + for (var i = 0; i < uiSourceCodeList.length; ++i) + this.addUISourceCode(uiSourceCodeList[i]); + + if (selected) + this.revealUISourceCode(uiSourceCodeList[0]); + }, + + /** + * @param {WebInspector.UISourceCode} uiSourceCode + */ + scriptSelected: function(uiSourceCode) + { + this._lastSelectedUISourceCode = uiSourceCode; + this.dispatchEventToListeners(WebInspector.ScriptsPanel.FileSelector.Events.FileSelected, uiSourceCode); + }, + + /** + * @param {WebInspector.UISourceCode} uiSourceCode + */ + removeUISourceCode: function(uiSourceCode) + { + var treeElement = this._scriptTreeElementsByUISourceCode.get(uiSourceCode); + while (treeElement) { + var parent = treeElement.parent; + if (parent) { + if (treeElement instanceof WebInspector.NavigatorFolderTreeElement) + delete this._folderTreeElements[treeElement.folderIdentifier]; + parent.removeChild(treeElement); + if (parent.children.length) + break; + } + treeElement = parent; + } + }, + + _showScriptFoldersSettingChanged: function() + { + var uiSourceCodes = this._navigatorScriptsTree.scriptTreeElements(); + uiSourceCodes = uiSourceCodes.concat(this._navigatorContentScriptsTree.scriptTreeElements()); + this._reset(); + for (var i = 0; i < uiSourceCodes.length; ++i) + this.addUISourceCode(uiSourceCodes[i]); + + this.revealUISourceCode(this._lastSelectedUISourceCode); + }, + + _reset: function() + { + this._navigatorScriptsTree.stopSearch(); + this._navigatorScriptsTree.removeChildren(); + this._navigatorContentScriptsTree.stopSearch(); + this._navigatorContentScriptsTree.removeChildren(); + this._folderTreeElements = {}; + }, + + /** + * @param {boolean} isContentScript + * @param {string} domain + * @param {string} folderName + */ + _folderIdentifier: function(isContentScript, domain, folderName) + { + var contentScriptPrefix = isContentScript ? "0" : "1"; + return contentScriptPrefix + ":" + domain + folderName; + }, + + /** + * @param {boolean} isContentScript + * @param {string} domain + * @param {string} folderName + */ + _getOrCreateFolderTreeElement: function(isContentScript, domain, folderName) + { + var folderIdentifier = this._folderIdentifier(isContentScript, domain, folderName); + + if (this._folderTreeElements[folderIdentifier]) + return this._folderTreeElements[folderIdentifier]; + + var showScriptFolders = WebInspector.settings.showScriptFolders.get(); + + if ((domain === "" && folderName === "") || !showScriptFolders) + return isContentScript ? this._navigatorContentScriptsTree : this._navigatorScriptsTree; + + var folderTreeElement = new WebInspector.NavigatorFolderTreeElement(folderIdentifier, domain, folderName); + + var parentFolderElement; + if (folderName === "") + parentFolderElement = isContentScript ? this._navigatorContentScriptsTree : this._navigatorScriptsTree; + else + parentFolderElement = this._getOrCreateFolderTreeElement(isContentScript, domain, ""); + + parentFolderElement.appendChild(folderTreeElement); + + this._folderTreeElements[folderIdentifier] = folderTreeElement; + return folderTreeElement; + } +} + +WebInspector.ScriptsNavigator.prototype.__proto__ = WebInspector.Object.prototype; + +/** + * @constructor + * @extends {TreeOutline} + * @param {Element} element + */ +WebInspector.NavigatorTreeOutline = function(navigator, element) +{ + TreeOutline.call(this, element); + + this._navigator = navigator; + + this.comparator = WebInspector.NavigatorTreeOutline._treeElementsCompare; + + this.searchable = true; + this.searchInputElement = document.createElement("input"); +} + +WebInspector.NavigatorTreeOutline._treeElementsCompare = function compare(treeElement1, treeElement2) +{ + // Insert in the alphabetical order, first domains, then folders, then scripts. + function typeWeight(treeElement) + { + if (treeElement instanceof WebInspector.NavigatorFolderTreeElement) { + if (treeElement.isDomain) + return 1; + return 2; + } + return 3; + } + + var typeWeight1 = typeWeight(treeElement1); + var typeWeight2 = typeWeight(treeElement2); + + var result; + if (typeWeight1 > typeWeight2) + result = 1; + else if (typeWeight1 < typeWeight2) + result = -1; + else { + var title1 = treeElement1.titleText; + var title2 = treeElement2.titleText; + result = title1.localeCompare(title2); + } + return result; +} + +WebInspector.NavigatorTreeOutline.prototype = { + /** + * @return {Array.} + */ + scriptTreeElements: function() + { + var result = []; + if (this.children.length) { + for (var treeElement = this.children[0]; treeElement; treeElement = treeElement.traverseNextTreeElement(false, this, true)) { + if (treeElement instanceof WebInspector.NavigatorScriptTreeElement) + result.push(treeElement.uiSourceCode); + } + } + return result; + }, + + searchStarted: function() + { + this._navigator._treeSearchBox.appendChild(this.searchInputElement); + this._navigator._treeSearchBox.addStyleClass("visible"); + }, + + searchFinished: function() + { + this._navigator._treeSearchBox.removeChild(this.searchInputElement); + this._navigator._treeSearchBox.removeStyleClass("visible"); + }, +} + +WebInspector.NavigatorTreeOutline.prototype.__proto__ = TreeOutline.prototype; + +/** + * @constructor + * @extends {TreeElement} + * @param {string} title + * @param {Array.} iconClasses + * @param {boolean} hasChildren + * @param {boolean=} noIcon + */ +WebInspector.BaseNavigatorTreeElement = function(title, iconClasses, hasChildren, noIcon) +{ + TreeElement.call(this, "", null, hasChildren); + this._titleText = title; + this._iconClasses = iconClasses; + this._noIcon = noIcon; +} + +WebInspector.BaseNavigatorTreeElement.prototype = { + onattach: function() + { + this.listItemElement.removeChildren(); + if (this._iconClasses) { + for (var i = 0; i < this._iconClasses.length; ++i) + this.listItemElement.addStyleClass(this._iconClasses[i]); + } + + var selectionElement = document.createElement("div"); + selectionElement.className = "selection"; + this.listItemElement.appendChild(selectionElement); + + if (!this._noIcon) { + this.imageElement = document.createElement("img"); + this.imageElement.className = "icon"; + this.listItemElement.appendChild(this.imageElement); + } + + this.titleElement = document.createElement("div"); + this.titleElement.className = "base-navigator-tree-element-title"; + this._titleTextNode = document.createTextNode(""); + this._titleTextNode.textContent = this._titleText; + this.titleElement.appendChild(this._titleTextNode); + this.listItemElement.appendChild(this.titleElement); + + this.expand(); + }, + + onreveal: function() + { + if (this.listItemElement) + this.listItemElement.scrollIntoViewIfNeeded(true); + }, + + /** + * @type {string} + */ + get titleText() + { + return this._titleText; + }, + + set titleText(titleText) + { + this._titleText = titleText || ""; + this._titleTextNode.textContent = this._titleText; + }, + + /** + * @param {string} searchText + */ + matchesSearchText: function(searchText) + { + return this.titleText.match(new RegExp("^" + searchText.escapeForRegExp(), "i")); + } +} + +WebInspector.BaseNavigatorTreeElement.prototype.__proto__ = TreeElement.prototype; + +/** + * @constructor + * @extends {WebInspector.BaseNavigatorTreeElement} + * @param {string} folderIdentifier + * @param {string} domain + * @param {string} folderName + */ +WebInspector.NavigatorFolderTreeElement = function(folderIdentifier, domain, folderName) +{ + this._folderIdentifier = folderIdentifier; + this._folderName = folderName; + + var iconClass = this.isDomain ? "scripts-navigator-domain-tree-item" : "scripts-navigator-folder-tree-item"; + var title = this.isDomain ? domain : folderName.substring(1); + WebInspector.BaseNavigatorTreeElement.call(this, title, [iconClass], true); +} + +WebInspector.NavigatorFolderTreeElement.prototype = { + /** + * @type {string} + */ + get folderIdentifier() + { + return this._folderIdentifier; + }, + + /** + * @type {boolean} + */ + get isDomain() + { + return this._folderName === ""; + }, + + onattach: function() + { + WebInspector.BaseNavigatorTreeElement.prototype.onattach.call(this); + if (this._isDomain) + this.collapse(); + else + this.expand(); + } +} + +WebInspector.NavigatorFolderTreeElement.prototype.__proto__ = WebInspector.BaseNavigatorTreeElement.prototype; + +/** + * @constructor + * @extends {WebInspector.BaseNavigatorTreeElement} + * @param {WebInspector.ScriptsNavigator} navigator + * @param {WebInspector.UISourceCode} uiSourceCode + * @param {string} title + */ +WebInspector.NavigatorScriptTreeElement = function(navigator, uiSourceCode, title) +{ + WebInspector.BaseNavigatorTreeElement.call(this, title, ["scripts-navigator-script-tree-item"], false); + this._navigator = navigator; + this._uiSourceCode = uiSourceCode; + this.tooltip = uiSourceCode.url; +} + +WebInspector.NavigatorScriptTreeElement.prototype = { + /** + * @type {WebInspector.UISourceCode} + */ + get uiSourceCode() + { + return this._uiSourceCode; + }, + + /** + * @param {Event} event + */ + ondblclick: function(event) + { + this._navigator.scriptSelected(this.uiSourceCode); + }, + + onenter: function() + { + this._navigator.scriptSelected(this.uiSourceCode); + } +} + +WebInspector.NavigatorScriptTreeElement.prototype.__proto__ = WebInspector.BaseNavigatorTreeElement.prototype; +/* ResourcesPanel.js */ + +/* + * Copyright (C) 2007, 2008, 2010 Apple Inc. All rights reserved. + * Copyright (C) 2009 Joseph Pecoraro + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.Panel} + */ +WebInspector.ResourcesPanel = function(database) +{ + WebInspector.Panel.call(this, "resources"); + this.registerRequiredCSS("resourcesPanel.css"); + + WebInspector.settings.resourcesLastSelectedItem = WebInspector.settings.createSetting("resourcesLastSelectedItem", {}); + + this.createSplitViewWithSidebarTree(); + this.sidebarElement.addStyleClass("outline-disclosure"); + this.sidebarElement.addStyleClass("filter-all"); + this.sidebarElement.addStyleClass("children"); + this.sidebarElement.addStyleClass("small"); + + this.sidebarTreeElement.removeStyleClass("sidebar-tree"); + + this.resourcesListTreeElement = new WebInspector.StorageCategoryTreeElement(this, WebInspector.UIString("Frames"), "Frames", ["frame-storage-tree-item"]); + this.sidebarTree.appendChild(this.resourcesListTreeElement); + + this.databasesListTreeElement = new WebInspector.StorageCategoryTreeElement(this, WebInspector.UIString("Databases"), "Databases", ["database-storage-tree-item"]); + this.sidebarTree.appendChild(this.databasesListTreeElement); + + this.localStorageListTreeElement = new WebInspector.StorageCategoryTreeElement(this, WebInspector.UIString("Local Storage"), "LocalStorage", ["domstorage-storage-tree-item", "local-storage"]); + this.sidebarTree.appendChild(this.localStorageListTreeElement); + + this.sessionStorageListTreeElement = new WebInspector.StorageCategoryTreeElement(this, WebInspector.UIString("Session Storage"), "SessionStorage", ["domstorage-storage-tree-item", "session-storage"]); + this.sidebarTree.appendChild(this.sessionStorageListTreeElement); + + this.cookieListTreeElement = new WebInspector.StorageCategoryTreeElement(this, WebInspector.UIString("Cookies"), "Cookies", ["cookie-storage-tree-item"]); + this.sidebarTree.appendChild(this.cookieListTreeElement); + + this.applicationCacheListTreeElement = new WebInspector.StorageCategoryTreeElement(this, WebInspector.UIString("Application Cache"), "ApplicationCache", ["application-cache-storage-tree-item"]); + this.sidebarTree.appendChild(this.applicationCacheListTreeElement); + + this.storageViews = this.splitView.mainElement; + this.storageViews.addStyleClass("diff-container"); + + this.storageViewStatusBarItemsContainer = document.createElement("div"); + this.storageViewStatusBarItemsContainer.className = "status-bar-items"; + + this._databases = []; + this._domStorage = []; + this._cookieViews = {}; + this._origins = {}; + this._domains = {}; + + this.sidebarElement.addEventListener("mousemove", this._onmousemove.bind(this), false); + this.sidebarElement.addEventListener("mouseout", this._onmouseout.bind(this), false); + + function viewGetter() + { + return this.visibleView; + } + WebInspector.GoToLineDialog.install(this, viewGetter.bind(this)); + + WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.OnLoad, this._onLoadEventFired, this); + WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.CachedResourcesLoaded, this._cachedResourcesLoaded, this); + WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.WillLoadCachedResources, this._resetWithFrames, this); +} + +WebInspector.ResourcesPanel.prototype = { + get toolbarItemLabel() + { + return WebInspector.UIString("Resources"); + }, + + get statusBarItems() + { + return [this.storageViewStatusBarItemsContainer]; + }, + + wasShown: function() + { + WebInspector.Panel.prototype.wasShown.call(this); + this._initialize(); + }, + + _initialize: function() + { + if (!this._initialized && this.isShowing() && this._cachedResourcesWereLoaded) { + this._populateResourceTree(); + this._populateApplicationCacheTree(); + this._initDefaultSelection(); + this._initialized = true; + } + }, + + _onLoadEventFired: function() + { + this._initDefaultSelection(); + }, + + _initDefaultSelection: function() + { + if (!this._initialized) + return; + + var itemURL = WebInspector.settings.resourcesLastSelectedItem.get(); + if (itemURL) { + for (var treeElement = this.sidebarTree.children[0]; treeElement; treeElement = treeElement.traverseNextTreeElement(false, this.sidebarTree, true)) { + if (treeElement.itemURL === itemURL) { + treeElement.revealAndSelect(true); + return; + } + } + } + + var mainResource = WebInspector.inspectedPageURL && this.resourcesListTreeElement && this.resourcesListTreeElement.expanded && WebInspector.resourceTreeModel.resourceForURL(WebInspector.inspectedPageURL); + if (mainResource) + this.showResource(mainResource); + }, + + _resetWithFrames: function() + { + this.resourcesListTreeElement.removeChildren(); + this._treeElementForFrameId = {}; + this._reset(); + }, + + _reset: function() + { + this._origins = {}; + this._domains = {}; + for (var i = 0; i < this._databases.length; ++i) { + var database = this._databases[i]; + delete database._tableViews; + if (database._queryView) + database._queryView.removeEventListener(WebInspector.DatabaseQueryView.Events.SchemaUpdated, this._updateDatabaseTables, this); + delete database._queryView; + } + this._databases = []; + + var domStorageLength = this._domStorage.length; + for (var i = 0; i < this._domStorage.length; ++i) { + var domStorage = this._domStorage[i]; + delete domStorage._domStorageView; + } + this._domStorage = []; + + this._cookieViews = {}; + + this.databasesListTreeElement.removeChildren(); + this.localStorageListTreeElement.removeChildren(); + this.sessionStorageListTreeElement.removeChildren(); + this.cookieListTreeElement.removeChildren(); + + if (this.visibleView) + this.visibleView.detach(); + + this.storageViewStatusBarItemsContainer.removeChildren(); + + if (this.sidebarTree.selectedTreeElement) + this.sidebarTree.selectedTreeElement.deselect(); + }, + + _populateResourceTree: function() + { + this._treeElementForFrameId = {}; + WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameAdded, this._frameAdded, this); + WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameNavigated, this._frameNavigated, this); + WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameDetached, this._frameDetached, this); + WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.ResourceAdded, this._resourceAdded, this); + + function populateFrame(frame) + { + this._frameAdded({data:frame}); + for (var i = 0; i < frame.childFrames.length; ++i) + populateFrame.call(this, frame.childFrames[i]); + + var resources = frame.resources(); + for (var i = 0; i < resources.length; ++i) + this._resourceAdded({data:resources[i]}); + } + populateFrame.call(this, WebInspector.resourceTreeModel.mainFrame); + }, + + _frameAdded: function(event) + { + var frame = event.data; + var parentFrame = frame.parentFrame; + + var parentTreeElement = parentFrame ? this._treeElementForFrameId[parentFrame.id] : this.resourcesListTreeElement; + if (!parentTreeElement) { + console.warn("No frame to route " + frame.url + " to.") + return; + } + + var frameTreeElement = new WebInspector.FrameTreeElement(this, frame); + this._treeElementForFrameId[frame.id] = frameTreeElement; + parentTreeElement.appendChild(frameTreeElement); + }, + + _frameDetached: function(event) + { + var frame = event.data; + var frameTreeElement = this._treeElementForFrameId[frame.id]; + if (!frameTreeElement) + return; + + delete this._treeElementForFrameId[frame.id]; + if (frameTreeElement.parent) + frameTreeElement.parent.removeChild(frameTreeElement); + }, + + _resourceAdded: function(event) + { + var resource = event.data; + var frameId = resource.frameId; + + if (resource.statusCode >= 301 && resource.statusCode <= 303) + return; + + var frameTreeElement = this._treeElementForFrameId[frameId]; + if (!frameTreeElement) { + // This is a frame's main resource, it will be retained + // and re-added by the resource manager; + return; + } + + frameTreeElement.appendResource(resource); + }, + + _frameNavigated: function(event) + { + var frame = event.data; + + if (!frame.parentFrame) + this._reset(); + + var frameId = frame.id; + var frameTreeElement = this._treeElementForFrameId[frameId]; + if (frameTreeElement) + frameTreeElement.frameNavigated(frame); + + var applicationCacheFrameTreeElement = this._applicationCacheFrameElements[frameId]; + if (applicationCacheFrameTreeElement) + applicationCacheFrameTreeElement.frameNavigated(frame); + }, + + _cachedResourcesLoaded: function() + { + this._cachedResourcesWereLoaded = true; + this._initialize(); + }, + + addDatabase: function(database) + { + this._databases.push(database); + + var databaseTreeElement = new WebInspector.DatabaseTreeElement(this, database); + database._databasesTreeElement = databaseTreeElement; + this.databasesListTreeElement.appendChild(databaseTreeElement); + }, + + addDocumentURL: function(url) + { + var parsedURL = url.asParsedURL(); + if (!parsedURL) + return; + + var domain = parsedURL.host; + if (!this._domains[domain]) { + this._domains[domain] = true; + + var cookieDomainTreeElement = new WebInspector.CookieTreeElement(this, domain); + this.cookieListTreeElement.appendChild(cookieDomainTreeElement); + } + }, + + addDOMStorage: function(domStorage) + { + this._domStorage.push(domStorage); + var domStorageTreeElement = new WebInspector.DOMStorageTreeElement(this, domStorage, (domStorage.isLocalStorage ? "local-storage" : "session-storage")); + domStorage._domStorageTreeElement = domStorageTreeElement; + if (domStorage.isLocalStorage) + this.localStorageListTreeElement.appendChild(domStorageTreeElement); + else + this.sessionStorageListTreeElement.appendChild(domStorageTreeElement); + }, + + selectDatabase: function(databaseId) + { + var database; + for (var i = 0, len = this._databases.length; i < len; ++i) { + database = this._databases[i]; + if (database.id === databaseId) { + this.showDatabase(database); + database._databasesTreeElement.select(); + return; + } + } + }, + + selectDOMStorage: function(storageId) + { + var domStorage = this._domStorageForId(storageId); + if (domStorage) { + this.showDOMStorage(domStorage); + domStorage._domStorageTreeElement.select(); + } + }, + + canShowAnchorLocation: function(anchor) + { + return !!WebInspector.resourceForURL(anchor.href); + }, + + showAnchorLocation: function(anchor) + { + var resource = WebInspector.resourceForURL(anchor.href); + this.showResource(resource, anchor.lineNumber); + }, + + /** + * @param {number=} line + */ + showResource: function(resource, line) + { + var resourceTreeElement = this._findTreeElementForResource(resource); + if (resourceTreeElement) + resourceTreeElement.revealAndSelect(); + + if (typeof line === "number") { + var view = this._resourceViewForResource(resource); + if (view.canHighlightLine()) + view.highlightLine(line); + } + return true; + }, + + _showResourceView: function(resource) + { + var view = this._resourceViewForResource(resource); + if (!view) { + this.visibleView.detach(); + return; + } + if (view.searchCanceled) + view.searchCanceled(); + this._fetchAndApplyDiffMarkup(view, resource); + this._innerShowView(view); + }, + + _resourceViewForResource: function(resource) + { + if (WebInspector.ResourceView.hasTextContent(resource)) { + var treeElement = this._findTreeElementForResource(resource); + if (!treeElement) + return null; + return treeElement.sourceView(); + } + return WebInspector.ResourceView.nonSourceViewForResource(resource); + }, + + _showRevisionView: function(revision) + { + var view = this._sourceViewForRevision(revision); + this._fetchAndApplyDiffMarkup(view, revision.resource, revision); + this._innerShowView(view); + }, + + _sourceViewForRevision: function(revision) + { + var treeElement = this._findTreeElementForRevision(revision); + return treeElement.sourceView(); + }, + + /** + * @param {WebInspector.ResourceRevision=} revision + */ + _fetchAndApplyDiffMarkup: function(view, resource, revision) + { + var baseRevision = resource.history[0]; + if (!baseRevision) + return; + if (!(view instanceof WebInspector.SourceFrame)) + return; + + baseRevision.requestContent(step1.bind(this)); + + function step1(baseContent) + { + (revision ? revision : resource).requestContent(step2.bind(this, baseContent)); + } + + function step2(baseContent, revisionContent) + { + this._applyDiffMarkup(view, baseContent, revisionContent); + } + }, + + _applyDiffMarkup: function(view, baseContent, newContent) + { + var diffData = TextDiff.compute(baseContent, newContent); + view.markDiff(diffData); + }, + + /** + * @param {string=} tableName + */ + showDatabase: function(database, tableName) + { + if (!database) + return; + + var view; + if (tableName) { + if (!("_tableViews" in database)) + database._tableViews = {}; + view = database._tableViews[tableName]; + if (!view) { + view = new WebInspector.DatabaseTableView(database, tableName); + database._tableViews[tableName] = view; + } + } else { + view = database._queryView; + if (!view) { + view = new WebInspector.DatabaseQueryView(database); + database._queryView = view; + view.addEventListener(WebInspector.DatabaseQueryView.Events.SchemaUpdated, this._updateDatabaseTables, this); + } + } + + this._innerShowView(view); + }, + + showDOMStorage: function(domStorage) + { + if (!domStorage) + return; + + var view; + view = domStorage._domStorageView; + if (!view) { + view = new WebInspector.DOMStorageItemsView(domStorage); + domStorage._domStorageView = view; + } + + this._innerShowView(view); + }, + + showCookies: function(treeElement, cookieDomain) + { + var view = this._cookieViews[cookieDomain]; + if (!view) { + view = new WebInspector.CookieItemsView(treeElement, cookieDomain); + this._cookieViews[cookieDomain] = view; + } + + this._innerShowView(view); + }, + + showApplicationCache: function(frameId) + { + if (!this._applicationCacheViews[frameId]) + this._applicationCacheViews[frameId] = new WebInspector.ApplicationCacheItemsView(this._applicationCacheModel, frameId); + + this._innerShowView(this._applicationCacheViews[frameId]); + }, + + showCategoryView: function(categoryName) + { + if (!this._categoryView) + this._categoryView = new WebInspector.StorageCategoryView(); + this._categoryView.setText(categoryName); + this._innerShowView(this._categoryView); + }, + + _innerShowView: function(view) + { + if (this.visibleView) + this.visibleView.detach(); + + view.show(this.storageViews); + this.visibleView = view; + + this.storageViewStatusBarItemsContainer.removeChildren(); + var statusBarItems = view.statusBarItems || []; + for (var i = 0; i < statusBarItems.length; ++i) + this.storageViewStatusBarItemsContainer.appendChild(statusBarItems[i]); + }, + + closeVisibleView: function() + { + if (!this.visibleView) + return; + this.visibleView.detach(); + delete this.visibleView; + }, + + _updateDatabaseTables: function(event) + { + var database = event.data; + + if (!database || !database._databasesTreeElement) + return; + + database._databasesTreeElement.shouldRefreshChildren = true; + + if (!("_tableViews" in database)) + return; + + var tableNamesHash = {}; + var self = this; + function tableNamesCallback(tableNames) + { + var tableNamesLength = tableNames.length; + for (var i = 0; i < tableNamesLength; ++i) + tableNamesHash[tableNames[i]] = true; + + for (var tableName in database._tableViews) { + if (!(tableName in tableNamesHash)) { + if (self.visibleView === database._tableViews[tableName]) + self.closeVisibleView(); + delete database._tableViews[tableName]; + } + } + } + database.getTableNames(tableNamesCallback); + }, + + updateDOMStorage: function(storageId) + { + var domStorage = this._domStorageForId(storageId); + if (!domStorage) + return; + + var view = domStorage._domStorageView; + if (this.visibleView && view === this.visibleView) + domStorage._domStorageView.update(); + }, + + _populateApplicationCacheTree: function() + { + this._applicationCacheModel = new WebInspector.ApplicationCacheModel(); + + this._applicationCacheViews = {}; + this._applicationCacheFrameElements = {}; + this._applicationCacheManifestElements = {}; + + this._applicationCacheModel.addEventListener(WebInspector.ApplicationCacheModel.EventTypes.FrameManifestAdded, this._applicationCacheFrameManifestAdded, this); + this._applicationCacheModel.addEventListener(WebInspector.ApplicationCacheModel.EventTypes.FrameManifestRemoved, this._applicationCacheFrameManifestRemoved, this); + + this._applicationCacheModel.addEventListener(WebInspector.ApplicationCacheModel.EventTypes.FrameManifestStatusUpdated, this._applicationCacheFrameManifestStatusChanged, this); + this._applicationCacheModel.addEventListener(WebInspector.ApplicationCacheModel.EventTypes.NetworkStateChanged, this._applicationCacheNetworkStateChanged, this); + }, + + _applicationCacheFrameManifestAdded: function(event) + { + var frameId = event.data; + var manifestURL = this._applicationCacheModel.frameManifestURL(frameId); + var status = this._applicationCacheModel.frameManifestStatus(frameId) + + var manifestTreeElement = this._applicationCacheManifestElements[manifestURL] + if (!manifestTreeElement) { + manifestTreeElement = new WebInspector.ApplicationCacheManifestTreeElement(this, manifestURL); + this.applicationCacheListTreeElement.appendChild(manifestTreeElement); + this._applicationCacheManifestElements[manifestURL] = manifestTreeElement; + } + + var frameTreeElement = new WebInspector.ApplicationCacheFrameTreeElement(this, frameId, manifestURL); + manifestTreeElement.appendChild(frameTreeElement); + manifestTreeElement.expand(); + this._applicationCacheFrameElements[frameId] = frameTreeElement; + }, + + _applicationCacheFrameManifestRemoved: function(event) + { + var frameId = event.data; + var frameTreeElement = this._applicationCacheFrameElements[frameId]; + if (!frameTreeElement) + return; + + var manifestURL = frameTreeElement.manifestURL; + delete this._applicationCacheFrameElements[frameId]; + delete this._applicationCacheViews[frameId]; + frameTreeElement.parent.removeChild(frameTreeElement); + + var manifestTreeElement = this._applicationCacheManifestElements[manifestURL]; + if (manifestTreeElement.children.length !== 0) + return; + + delete this._applicationCacheManifestElements[manifestURL]; + manifestTreeElement.parent.removeChild(manifestTreeElement); + }, + + _applicationCacheFrameManifestStatusChanged: function(event) + { + var frameId = event.data; + var status = this._applicationCacheModel.frameManifestStatus(frameId) + + if (this._applicationCacheViews[frameId]) + this._applicationCacheViews[frameId].updateStatus(status); + }, + + _applicationCacheNetworkStateChanged: function(event) + { + var isNowOnline = event.data; + + for (var manifestURL in this._applicationCacheViews) + this._applicationCacheViews[manifestURL].updateNetworkState(isNowOnline); + }, + + _domStorageForId: function(storageId) + { + if (!this._domStorage) + return null; + var domStorageLength = this._domStorage.length; + for (var i = 0; i < domStorageLength; ++i) { + var domStorage = this._domStorage[i]; + if (domStorage.id == storageId) + return domStorage; + } + return null; + }, + + sidebarResized: function(event) + { + var width = event.data; + this.storageViewStatusBarItemsContainer.style.left = width + "px"; + }, + + performSearch: function(query) + { + this._resetSearchResults(); + var regex = WebInspector.SourceFrame.createSearchRegex(query); + var totalMatchesCount = 0; + + function searchInEditedResource(treeElement) + { + var resource = treeElement.representedObject; + if (resource.history.length == 0) + return; + var matchesCount = countRegexMatches(regex, resource.content) + treeElement.searchMatchesFound(matchesCount); + totalMatchesCount += matchesCount; + } + + function callback(error, result) + { + if (!error) { + for (var i = 0; i < result.length; i++) { + var searchResult = result[i]; + var frameTreeElement = this._treeElementForFrameId[searchResult.frameId]; + if (!frameTreeElement) + continue; + var resource = frameTreeElement.resourceByURL(searchResult.url); + + // FIXME: When the same script is used in several frames and this script contains at least + // one search result then some search results can not be matched with a resource on panel. + // https://bugs.webkit.org/show_bug.cgi?id=66005 + if (!resource) + continue; + + if (resource.history.length > 0) + continue; // Skip edited resources. + this._findTreeElementForResource(resource).searchMatchesFound(searchResult.matchesCount); + totalMatchesCount += searchResult.matchesCount; + } + } + + WebInspector.searchController.updateSearchMatchesCount(totalMatchesCount, this); + this._searchController = new WebInspector.ResourcesSearchController(this.resourcesListTreeElement, totalMatchesCount); + + if (this.sidebarTree.selectedTreeElement && this.sidebarTree.selectedTreeElement.searchMatchesCount) + this.jumpToNextSearchResult(); + } + + this._forAllResourceTreeElements(searchInEditedResource.bind(this)); + PageAgent.searchInResources(regex.source, !regex.ignoreCase, true, callback.bind(this)); + }, + + _ensureViewSearchPerformed: function(callback) + { + function viewSearchPerformedCallback(searchId) + { + if (searchId !== this._lastViewSearchId) + return; // Search is obsolete. + this._viewSearchInProgress = false; + callback(); + } + + if (!this._viewSearchInProgress) { + if (!this.visibleView.hasSearchResults()) { + // We give id to each search, so that we can skip callbacks for obsolete searches. + this._lastViewSearchId = this._lastViewSearchId ? this._lastViewSearchId + 1 : 0; + this._viewSearchInProgress = true; + this.visibleView.performSearch(this.currentQuery, viewSearchPerformedCallback.bind(this, this._lastViewSearchId)); + } else + callback(); + } + }, + + _showSearchResult: function(searchResult) + { + this._lastSearchResultIndex = searchResult.index; + this._lastSearchResultTreeElement = searchResult.treeElement; + + // At first show view for treeElement. + if (searchResult.treeElement !== this.sidebarTree.selectedTreeElement) { + this.showResource(searchResult.treeElement.representedObject); + WebInspector.searchController.focusSearchField(); + } + + function callback(searchId) + { + if (this.sidebarTree.selectedTreeElement !== this._lastSearchResultTreeElement) + return; // User has selected another view while we were searching. + if (this._lastSearchResultIndex != -1) + this.visibleView.jumpToSearchResult(this._lastSearchResultIndex); + WebInspector.searchController.updateCurrentMatchIndex(searchResult.currentMatchIndex - 1, this); + } + + // Then run SourceFrame search if needed and jump to search result index when done. + this._ensureViewSearchPerformed(callback.bind(this)); + }, + + _resetSearchResults: function() + { + function callback(resourceTreeElement) + { + resourceTreeElement._resetSearchResults(); + } + + this._forAllResourceTreeElements(callback); + if (this.visibleView && this.visibleView.searchCanceled) + this.visibleView.searchCanceled(); + + this._lastSearchResultTreeElement = null; + this._lastSearchResultIndex = -1; + this._viewSearchInProgress = false; + }, + + searchCanceled: function() + { + function callback(resourceTreeElement) + { + resourceTreeElement._updateErrorsAndWarningsBubbles(); + } + + WebInspector.searchController.updateSearchMatchesCount(0, this); + this._resetSearchResults(); + this._forAllResourceTreeElements(callback); + }, + + jumpToNextSearchResult: function() + { + if (!this.currentSearchMatches) + return; + var currentTreeElement = this.sidebarTree.selectedTreeElement; + var nextSearchResult = this._searchController.nextSearchResult(currentTreeElement); + this._showSearchResult(nextSearchResult); + }, + + jumpToPreviousSearchResult: function() + { + if (!this.currentSearchMatches) + return; + var currentTreeElement = this.sidebarTree.selectedTreeElement; + var previousSearchResult = this._searchController.previousSearchResult(currentTreeElement); + this._showSearchResult(previousSearchResult); + }, + + _forAllResourceTreeElements: function(callback) + { + var stop = false; + for (var treeElement = this.resourcesListTreeElement; !stop && treeElement; treeElement = treeElement.traverseNextTreeElement(false, this.resourcesListTreeElement, true)) { + if (treeElement instanceof WebInspector.FrameResourceTreeElement) + stop = callback(treeElement); + } + }, + + _findTreeElementForResource: function(resource) + { + function isAncestor(ancestor, object) + { + // Redirects, XHRs do not belong to the tree, it is fine to silently return false here. + return false; + } + + function getParent(object) + { + // Redirects, XHRs do not belong to the tree, it is fine to silently return false here. + return null; + } + + return this.sidebarTree.findTreeElement(resource, isAncestor, getParent); + }, + + _findTreeElementForRevision: function(revision) + { + function isAncestor(ancestor, object) + { + return false; + } + + function getParent(object) + { + return null; + } + + return this.sidebarTree.findTreeElement(revision, isAncestor, getParent); + }, + + showView: function(view) + { + if (view) + this.showResource(view.resource); + }, + + _onmousemove: function(event) + { + var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY); + if (!nodeUnderMouse) + return; + + var listNode = nodeUnderMouse.enclosingNodeOrSelfWithNodeName("li"); + if (!listNode) + return; + + var element = listNode.treeElement; + if (this._previousHoveredElement === element) + return; + + if (this._previousHoveredElement) { + this._previousHoveredElement.hovered = false; + delete this._previousHoveredElement; + } + + if (element instanceof WebInspector.FrameTreeElement) { + this._previousHoveredElement = element; + element.hovered = true; + } + }, + + _onmouseout: function(event) + { + if (this._previousHoveredElement) { + this._previousHoveredElement.hovered = false; + delete this._previousHoveredElement; + } + } +} + +WebInspector.ResourcesPanel.prototype.__proto__ = WebInspector.Panel.prototype; + +/** + * @constructor + * @extends {TreeElement} + * @param {boolean=} hasChildren + * @param {boolean=} noIcon + */ +WebInspector.BaseStorageTreeElement = function(storagePanel, representedObject, title, iconClasses, hasChildren, noIcon) +{ + TreeElement.call(this, "", representedObject, hasChildren); + this._storagePanel = storagePanel; + this._titleText = title; + this._iconClasses = iconClasses; + this._noIcon = noIcon; +} + +WebInspector.BaseStorageTreeElement.prototype = { + onattach: function() + { + this.listItemElement.removeChildren(); + if (this._iconClasses) { + for (var i = 0; i < this._iconClasses.length; ++i) + this.listItemElement.addStyleClass(this._iconClasses[i]); + } + + var selectionElement = document.createElement("div"); + selectionElement.className = "selection"; + this.listItemElement.appendChild(selectionElement); + + if (!this._noIcon) { + this.imageElement = document.createElement("img"); + this.imageElement.className = "icon"; + this.listItemElement.appendChild(this.imageElement); + } + + this.titleElement = document.createElement("div"); + this.titleElement.className = "base-storage-tree-element-title"; + this._titleTextNode = document.createTextNode(""); + this.titleElement.appendChild(this._titleTextNode); + this._updateTitle(); + this._updateSubtitle(); + this.listItemElement.appendChild(this.titleElement); + }, + + get displayName() + { + return this._displayName; + }, + + _updateDisplayName: function() + { + this._displayName = this._titleText || ""; + if (this._subtitleText) + this._displayName += " (" + this._subtitleText + ")"; + }, + + _updateTitle: function() + { + this._updateDisplayName(); + + if (!this.titleElement) + return; + + this._titleTextNode.textContent = this._titleText || ""; + }, + + _updateSubtitle: function() + { + this._updateDisplayName(); + + if (!this.titleElement) + return; + + if (this._subtitleText) { + if (!this._subtitleElement) { + this._subtitleElement = document.createElement("span"); + this._subtitleElement.className = "base-storage-tree-element-subtitle"; + this.titleElement.appendChild(this._subtitleElement); + } + this._subtitleElement.textContent = "(" + this._subtitleText + ")"; + } else if (this._subtitleElement) { + this.titleElement.removeChild(this._subtitleElement); + delete this._subtitleElement; + } + }, + + onselect: function() + { + var itemURL = this.itemURL; + if (itemURL) + WebInspector.settings.resourcesLastSelectedItem.set(itemURL); + }, + + onreveal: function() + { + if (this.listItemElement) + this.listItemElement.scrollIntoViewIfNeeded(false); + }, + + get titleText() + { + return this._titleText; + }, + + set titleText(titleText) + { + this._titleText = titleText; + this._updateTitle(); + }, + + get subtitleText() + { + return this._subtitleText; + }, + + set subtitleText(subtitleText) + { + this._subtitleText = subtitleText; + this._updateSubtitle(); + }, + + get searchMatchesCount() + { + return 0; + } +} + +WebInspector.BaseStorageTreeElement.prototype.__proto__ = TreeElement.prototype; + +/** + * @constructor + * @extends {WebInspector.BaseStorageTreeElement} + * @param {boolean=} noIcon + */ +WebInspector.StorageCategoryTreeElement = function(storagePanel, categoryName, settingsKey, iconClasses, noIcon) +{ + WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, categoryName, iconClasses, true, noIcon); + this._expandedSettingKey = "resources" + settingsKey + "Expanded"; + WebInspector.settings[this._expandedSettingKey] = WebInspector.settings.createSetting(this._expandedSettingKey, settingsKey === "Frames"); + this._categoryName = categoryName; +} + +WebInspector.StorageCategoryTreeElement.prototype = { + get itemURL() + { + return "category://" + this._categoryName; + }, + + onselect: function() + { + WebInspector.BaseStorageTreeElement.prototype.onselect.call(this); + this._storagePanel.showCategoryView(this._categoryName); + }, + + onattach: function() + { + WebInspector.BaseStorageTreeElement.prototype.onattach.call(this); + if (WebInspector.settings[this._expandedSettingKey].get()) + this.expand(); + }, + + onexpand: function() + { + WebInspector.settings[this._expandedSettingKey].set(true); + }, + + oncollapse: function() + { + WebInspector.settings[this._expandedSettingKey].set(false); + } +} + +WebInspector.StorageCategoryTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype; + +/** + * @constructor + * @extends {WebInspector.BaseStorageTreeElement} + */ +WebInspector.FrameTreeElement = function(storagePanel, frame) +{ + WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, "", ["frame-storage-tree-item"]); + this._frame = frame; + this.frameNavigated(frame); +} + +WebInspector.FrameTreeElement.prototype = { + frameNavigated: function(frame) + { + this.removeChildren(); + this._frameId = frame.id; + + this.titleText = frame.name; + this.subtitleText = WebInspector.Resource.displayName(frame.url); + + this._categoryElements = {}; + this._treeElementForResource = {}; + + this._storagePanel.addDocumentURL(frame.url); + }, + + get itemURL() + { + return "frame://" + encodeURI(this.displayName); + }, + + onselect: function() + { + WebInspector.BaseStorageTreeElement.prototype.onselect.call(this); + this._storagePanel.showCategoryView(this.displayName); + + this.listItemElement.removeStyleClass("hovered"); + DOMAgent.hideHighlight(); + }, + + set hovered(hovered) + { + if (hovered) { + this.listItemElement.addStyleClass("hovered"); + DOMAgent.highlightFrame(this._frameId, WebInspector.Color.PageHighlight.Content.toProtocolRGBA(), WebInspector.Color.PageHighlight.ContentOutline.toProtocolRGBA()); + } else { + this.listItemElement.removeStyleClass("hovered"); + DOMAgent.hideHighlight(); + } + }, + + appendResource: function(resource) + { + var categoryName = resource.category.name; + var categoryElement = resource.category === WebInspector.resourceCategories.documents ? this : this._categoryElements[categoryName]; + if (!categoryElement) { + categoryElement = new WebInspector.StorageCategoryTreeElement(this._storagePanel, resource.category.title, categoryName, null, true); + this._categoryElements[resource.category.name] = categoryElement; + this._insertInPresentationOrder(this, categoryElement); + } + var resourceTreeElement = new WebInspector.FrameResourceTreeElement(this._storagePanel, resource); + this._insertInPresentationOrder(categoryElement, resourceTreeElement); + resourceTreeElement._populateRevisions(); + + this._treeElementForResource[resource.url] = resourceTreeElement; + }, + + resourceByURL: function(url) + { + var treeElement = this._treeElementForResource[url]; + return treeElement ? treeElement.representedObject : null; + }, + + appendChild: function(treeElement) + { + this._insertInPresentationOrder(this, treeElement); + }, + + _insertInPresentationOrder: function(parentTreeElement, childTreeElement) + { + // Insert in the alphabetical order, first frames, then resources. Document resource goes last. + function typeWeight(treeElement) + { + if (treeElement instanceof WebInspector.StorageCategoryTreeElement) + return 2; + if (treeElement instanceof WebInspector.FrameTreeElement) + return 1; + return 3; + } + + function compare(treeElement1, treeElement2) + { + var typeWeight1 = typeWeight(treeElement1); + var typeWeight2 = typeWeight(treeElement2); + + var result; + if (typeWeight1 > typeWeight2) + result = 1; + else if (typeWeight1 < typeWeight2) + result = -1; + else { + var title1 = treeElement1.displayName || treeElement1.titleText; + var title2 = treeElement2.displayName || treeElement2.titleText; + result = title1.localeCompare(title2); + } + return result; + } + + var children = parentTreeElement.children; + var i; + for (i = 0; i < children.length; ++i) { + if (compare(childTreeElement, children[i]) < 0) + break; + } + parentTreeElement.insertChild(childTreeElement, i); + } +} + +WebInspector.FrameTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype; + +/** + * @constructor + * @extends {WebInspector.BaseStorageTreeElement} + */ +WebInspector.FrameResourceTreeElement = function(storagePanel, resource) +{ + WebInspector.BaseStorageTreeElement.call(this, storagePanel, resource, resource.displayName, ["resource-sidebar-tree-item", "resources-category-" + resource.category.name]); + this._resource = resource; + this._resource.addEventListener(WebInspector.Resource.Events.MessageAdded, this._consoleMessageAdded, this); + this._resource.addEventListener(WebInspector.Resource.Events.MessagesCleared, this._consoleMessagesCleared, this); + this._resource.addEventListener(WebInspector.Resource.Events.RevisionAdded, this._revisionAdded, this); + this.tooltip = resource.url; +} + +WebInspector.FrameResourceTreeElement.prototype = { + get itemURL() + { + return this._resource.url; + }, + + onselect: function() + { + WebInspector.BaseStorageTreeElement.prototype.onselect.call(this); + this._storagePanel._showResourceView(this._resource); + }, + + ondblclick: function(event) + { + InspectorFrontendHost.openInNewTab(this._resource.url); + }, + + onattach: function() + { + WebInspector.BaseStorageTreeElement.prototype.onattach.call(this); + + if (this._resource.category === WebInspector.resourceCategories.images) { + var previewImage = document.createElement("img"); + previewImage.className = "image-resource-icon-preview"; + this._resource.populateImageSource(previewImage); + + var iconElement = document.createElement("div"); + iconElement.className = "icon"; + iconElement.appendChild(previewImage); + this.listItemElement.replaceChild(iconElement, this.imageElement); + } + + this._statusElement = document.createElement("div"); + this._statusElement.className = "status"; + this.listItemElement.insertBefore(this._statusElement, this.titleElement); + + this.listItemElement.draggable = true; + this.listItemElement.addEventListener("dragstart", this._ondragstart.bind(this), false); + this.listItemElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), true); + + this._updateErrorsAndWarningsBubbles(); + }, + + _ondragstart: function(event) + { + event.dataTransfer.setData("text/plain", this._resource.content); + event.dataTransfer.effectAllowed = "copy"; + return true; + }, + + _handleContextMenuEvent: function(event) + { + var contextMenu = new WebInspector.ContextMenu(); + contextMenu.appendItem(WebInspector.openLinkExternallyLabel(), WebInspector.openResource.bind(WebInspector, this._resource.url, false)); + this._appendOpenInNetworkPanelAction(contextMenu, event); + WebInspector.populateResourceContextMenu(contextMenu, this._resource.url, null); + this._appendSaveAsAction(contextMenu, event); + contextMenu.show(event); + }, + + _appendOpenInNetworkPanelAction: function(contextMenu, event) + { + if (!this._resource.requestId) + return; + + contextMenu.appendItem(WebInspector.openInNetworkPanelLabel(), WebInspector.openRequestInNetworkPanel.bind(WebInspector, this._resource)); + }, + + _appendSaveAsAction: function(contextMenu, event) + { + if (!InspectorFrontendHost.canSaveAs()) + return; + + if (this._resource.type !== WebInspector.Resource.Type.Document && + this._resource.type !== WebInspector.Resource.Type.Stylesheet && + this._resource.type !== WebInspector.Resource.Type.Script) + return; + + function save() + { + var fileName = this._resource.displayName; + this._resource.requestContent(InspectorFrontendHost.saveAs.bind(InspectorFrontendHost, fileName)); + } + + contextMenu.appendSeparator(); + contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Save as..." : "Save As..."), save.bind(this)); + }, + + _setBubbleText: function(x) + { + if (!this._bubbleElement) { + this._bubbleElement = document.createElement("div"); + this._bubbleElement.className = "bubble"; + this._statusElement.appendChild(this._bubbleElement); + } + + this._bubbleElement.textContent = x; + }, + + _resetBubble: function() + { + if (this._bubbleElement) { + this._bubbleElement.textContent = ""; + this._bubbleElement.removeStyleClass("search-matches"); + this._bubbleElement.removeStyleClass("warning"); + this._bubbleElement.removeStyleClass("error"); + } + }, + + _resetSearchResults: function() + { + this._resetBubble(); + this._searchMatchesCount = 0; + }, + + get searchMatchesCount() + { + return this._searchMatchesCount; + }, + + searchMatchesFound: function(matchesCount) + { + this._resetSearchResults(); + + this._searchMatchesCount = matchesCount; + this._setBubbleText(matchesCount); + this._bubbleElement.addStyleClass("search-matches"); + + // Expand, do not scroll into view. + var currentAncestor = this.parent; + while (currentAncestor && !currentAncestor.root) { + if (!currentAncestor.expanded) + currentAncestor.expand(); + currentAncestor = currentAncestor.parent; + } + }, + + _updateErrorsAndWarningsBubbles: function() + { + if (this._storagePanel.currentQuery) + return; + + this._resetBubble(); + + if (this._resource.warnings || this._resource.errors) + this._setBubbleText(this._resource.warnings + this._resource.errors); + + if (this._resource.warnings) + this._bubbleElement.addStyleClass("warning"); + + if (this._resource.errors) + this._bubbleElement.addStyleClass("error"); + }, + + _consoleMessagesCleared: function() + { + // FIXME: move to the SourceFrame. + if (this._sourceView) + this._sourceView.clearMessages(); + + this._updateErrorsAndWarningsBubbles(); + }, + + _consoleMessageAdded: function(event) + { + var msg = event.data; + if (this._sourceView) + this._sourceView.addMessage(msg); + this._updateErrorsAndWarningsBubbles(); + }, + + _populateRevisions: function() + { + for (var i = 0; i < this._resource.history.length; ++i) + this._appendRevision(this._resource.history[i]); + }, + + _revisionAdded: function(event) + { + this._appendRevision(event.data); + }, + + _appendRevision: function(revision) + { + this.insertChild(new WebInspector.ResourceRevisionTreeElement(this._storagePanel, revision), 0); + var oldView = this._sourceView; + if (oldView) { + // This is needed when resource content was changed from scripts panel. + var newView = this._recreateSourceView(); + if (oldView === this._storagePanel.visibleView) + this._storagePanel._showResourceView(this._resource); + } + }, + + sourceView: function() + { + if (!this._sourceView) { + this._sourceView = this._createSourceView(); + if (this._resource.messages) { + for (var i = 0; i < this._resource.messages.length; i++) + this._sourceView.addMessage(this._resource.messages[i]); + } + } + return this._sourceView; + }, + + _createSourceView: function() + { + return new WebInspector.EditableResourceSourceFrame(this._resource); + }, + + _recreateSourceView: function() + { + var oldView = this._sourceView; + var newView = this._createSourceView(); + + var oldViewParentNode = oldView.isShowing() ? oldView.element.parentNode : null; + newView.inheritScrollPositions(oldView); + + this._sourceView.detach(); + this._sourceView = newView; + + if (oldViewParentNode) + newView.show(oldViewParentNode); + + return newView; + } +} + +WebInspector.FrameResourceTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype; + +/** + * @constructor + * @extends {WebInspector.BaseStorageTreeElement} + */ +WebInspector.DatabaseTreeElement = function(storagePanel, database) +{ + WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, database.name, ["database-storage-tree-item"], true); + this._database = database; +} + +WebInspector.DatabaseTreeElement.prototype = { + get itemURL() + { + return "database://" + encodeURI(this._database.name); + }, + + onselect: function() + { + WebInspector.BaseStorageTreeElement.prototype.onselect.call(this); + this._storagePanel.showDatabase(this._database); + }, + + onexpand: function() + { + this._updateChildren(); + }, + + _updateChildren: function() + { + this.removeChildren(); + + function tableNamesCallback(tableNames) + { + var tableNamesLength = tableNames.length; + for (var i = 0; i < tableNamesLength; ++i) + this.appendChild(new WebInspector.DatabaseTableTreeElement(this._storagePanel, this._database, tableNames[i])); + } + this._database.getTableNames(tableNamesCallback.bind(this)); + } +} + +WebInspector.DatabaseTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype; + +/** + * @constructor + * @extends {WebInspector.BaseStorageTreeElement} + */ +WebInspector.DatabaseTableTreeElement = function(storagePanel, database, tableName) +{ + WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, tableName, ["database-storage-tree-item"]); + this._database = database; + this._tableName = tableName; +} + +WebInspector.DatabaseTableTreeElement.prototype = { + get itemURL() + { + return "database://" + encodeURI(this._database.name) + "/" + encodeURI(this._tableName); + }, + + onselect: function() + { + WebInspector.BaseStorageTreeElement.prototype.onselect.call(this); + this._storagePanel.showDatabase(this._database, this._tableName); + } +} +WebInspector.DatabaseTableTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype; + +/** + * @constructor + * @extends {WebInspector.BaseStorageTreeElement} + */ +WebInspector.DOMStorageTreeElement = function(storagePanel, domStorage, className) +{ + WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, domStorage.domain ? domStorage.domain : WebInspector.UIString("Local Files"), ["domstorage-storage-tree-item", className]); + this._domStorage = domStorage; +} + +WebInspector.DOMStorageTreeElement.prototype = { + get itemURL() + { + return "storage://" + this._domStorage.domain + "/" + (this._domStorage.isLocalStorage ? "local" : "session"); + }, + + onselect: function() + { + WebInspector.BaseStorageTreeElement.prototype.onselect.call(this); + this._storagePanel.showDOMStorage(this._domStorage); + } +} +WebInspector.DOMStorageTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype; + +/** + * @constructor + * @extends {WebInspector.BaseStorageTreeElement} + */ +WebInspector.CookieTreeElement = function(storagePanel, cookieDomain) +{ + WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, cookieDomain ? cookieDomain : WebInspector.UIString("Local Files"), ["cookie-storage-tree-item"]); + this._cookieDomain = cookieDomain; +} + +WebInspector.CookieTreeElement.prototype = { + get itemURL() + { + return "cookies://" + this._cookieDomain; + }, + + onselect: function() + { + WebInspector.BaseStorageTreeElement.prototype.onselect.call(this); + this._storagePanel.showCookies(this, this._cookieDomain); + } +} +WebInspector.CookieTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype; + +/** + * @constructor + * @extends {WebInspector.BaseStorageTreeElement} + */ +WebInspector.ApplicationCacheManifestTreeElement = function(storagePanel, manifestURL) +{ + var title = WebInspector.Resource.displayName(manifestURL); + WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, title, ["application-cache-storage-tree-item"]); + this.tooltip = manifestURL; + this._manifestURL = manifestURL; +} + +WebInspector.ApplicationCacheManifestTreeElement.prototype = { + get itemURL() + { + return "appcache://" + this._manifestURL; + }, + + get manifestURL() + { + return this._manifestURL; + }, + + onselect: function() + { + WebInspector.BaseStorageTreeElement.prototype.onselect.call(this); + this._storagePanel.showCategoryView(this._manifestURL); + } +} +WebInspector.ApplicationCacheManifestTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype; + +/** + * @constructor + * @extends {WebInspector.BaseStorageTreeElement} + */ +WebInspector.ApplicationCacheFrameTreeElement = function(storagePanel, frameId, manifestURL) +{ + WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, "", ["frame-storage-tree-item"]); + this._frameId = frameId; + this._manifestURL = manifestURL; + this._refreshTitles(); +} + +WebInspector.ApplicationCacheFrameTreeElement.prototype = { + get itemURL() + { + return "appcache://" + this._manifestURL + "/" + encodeURI(this.displayName); + }, + + get frameId() + { + return this._frameId; + }, + + get manifestURL() + { + return this._manifestURL; + }, + + _refreshTitles: function() + { + var frame = WebInspector.resourceTreeModel.frameForId(this._frameId); + if (!frame) { + this.subtitleText = WebInspector.UIString("new frame"); + return; + } + this.titleText = frame.name; + this.subtitleText = WebInspector.Resource.displayName(frame.url); + }, + + frameNavigated: function() + { + this._refreshTitles(); + }, + + onselect: function() + { + WebInspector.BaseStorageTreeElement.prototype.onselect.call(this); + this._storagePanel.showApplicationCache(this._frameId); + } +} +WebInspector.ApplicationCacheFrameTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype; + +/** + * @constructor + * @extends {WebInspector.BaseStorageTreeElement} + */ +WebInspector.ResourceRevisionTreeElement = function(storagePanel, revision) +{ + var title = revision.timestamp ? revision.timestamp.toLocaleTimeString() : WebInspector.UIString("(original)"); + WebInspector.BaseStorageTreeElement.call(this, storagePanel, revision, title, ["resource-sidebar-tree-item", "resources-category-" + revision.resource.category.name]); + if (revision.timestamp) + this.tooltip = revision.timestamp.toLocaleString(); + this._revision = revision; +} + +WebInspector.ResourceRevisionTreeElement.prototype = { + get itemURL() + { + return this._revision.resource.url; + }, + + onattach: function() + { + WebInspector.BaseStorageTreeElement.prototype.onattach.call(this); + this.listItemElement.draggable = true; + this.listItemElement.addEventListener("dragstart", this._ondragstart.bind(this), false); + this.listItemElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), true); + }, + + onselect: function() + { + WebInspector.BaseStorageTreeElement.prototype.onselect.call(this); + this._storagePanel._showRevisionView(this._revision); + }, + + _ondragstart: function(event) + { + if (this._revision.content) { + event.dataTransfer.setData("text/plain", this._revision.content); + event.dataTransfer.effectAllowed = "copy"; + return true; + } + }, + + _handleContextMenuEvent: function(event) + { + var contextMenu = new WebInspector.ContextMenu(); + contextMenu.appendItem(WebInspector.UIString("Revert to this revision"), this._revision.revertToThis.bind(this._revision)); + + if (InspectorFrontendHost.canSaveAs()) { + function save() + { + var fileName = this._revision.resource.displayName; + this._revision.requestContent(InspectorFrontendHost.saveAs.bind(InspectorFrontendHost, fileName)); + } + contextMenu.appendSeparator(); + contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Save as..." : "Save As..."), save.bind(this)); + } + + contextMenu.show(event); + }, + + sourceView: function() + { + if (!this._sourceView) + this._sourceView = new WebInspector.ResourceRevisionSourceFrame(this._revision); + return this._sourceView; + } +} + +WebInspector.ResourceRevisionTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype; + +/** + * @constructor + * @extends {WebInspector.View} + */ +WebInspector.StorageCategoryView = function() +{ + WebInspector.View.call(this); + + this.element.addStyleClass("storage-view"); + this._emptyView = new WebInspector.EmptyView(""); + this._emptyView.show(this.element); +} + +WebInspector.StorageCategoryView.prototype = { + setText: function(text) + { + this._emptyView.text = text; + } +} + +WebInspector.StorageCategoryView.prototype.__proto__ = WebInspector.View.prototype; + +/** + * @constructor + * @param {WebInspector.BaseStorageTreeElement} rootElement + * @param {number} matchesCount + */ +WebInspector.ResourcesSearchController = function(rootElement, matchesCount) +{ + this._root = rootElement; + this._matchesCount = matchesCount; + this._traverser = new WebInspector.SearchResultsTreeElementsTraverser(rootElement); + this._lastTreeElement = null; + this._lastIndex = -1; +} + +WebInspector.ResourcesSearchController.prototype = { + /** + * @param {WebInspector.BaseStorageTreeElement} currentTreeElement + */ + nextSearchResult: function(currentTreeElement) + { + if (!currentTreeElement) + return this._searchResult(this._traverser.first(), 0, 1); + + if (!currentTreeElement.searchMatchesCount) + return this._searchResult(this._traverser.next(currentTreeElement), 0); + + if (this._lastTreeElement !== currentTreeElement || this._lastIndex === -1) + return this._searchResult(currentTreeElement, 0); + + if (this._lastIndex == currentTreeElement.searchMatchesCount - 1) + return this._searchResult(this._traverser.next(currentTreeElement), 0, this._currentMatchIndex % this._matchesCount + 1); + + return this._searchResult(currentTreeElement, this._lastIndex + 1, this._currentMatchIndex + 1); + }, + + /** + * @param {WebInspector.BaseStorageTreeElement} currentTreeElement + */ + previousSearchResult: function(currentTreeElement) + { + if (!currentTreeElement) { + var treeElement = this._traverser.last(); + return this._searchResult(treeElement, treeElement.searchMatchesCount - 1, this._matchesCount); + } + + if (currentTreeElement.searchMatchesCount && this._lastTreeElement === currentTreeElement) { + if (this._lastIndex > 0) + return this._searchResult(currentTreeElement, this._lastIndex - 1, this._currentMatchIndex - 1); + else { + var treeElement = this._traverser.previous(currentTreeElement); + var currentMatchIndex = this._currentMatchIndex - 1 ? this._currentMatchIndex - 1 : this._matchesCount; + return this._searchResult(treeElement, treeElement.searchMatchesCount - 1, currentMatchIndex); + } + } + + var treeElement = this._traverser.previous(currentTreeElement) + return this._searchResult(treeElement, treeElement.searchMatchesCount - 1); + }, + + /** + * @param {WebInspector.BaseStorageTreeElement} treeElement + * @param {number} index + * @param {number=} currentMatchIndex + * @return {Object} + */ + _searchResult: function(treeElement, index, currentMatchIndex) + { + this._lastTreeElement = treeElement; + this._lastIndex = index; + if (!currentMatchIndex) + currentMatchIndex = this._traverser.matchIndex(treeElement, index); + this._currentMatchIndex = currentMatchIndex; + return {treeElement: treeElement, index: index, currentMatchIndex: currentMatchIndex}; + } +} + +/** + * @constructor + * @param {WebInspector.BaseStorageTreeElement} rootElement + */ +WebInspector.SearchResultsTreeElementsTraverser = function(rootElement) +{ + this._root = rootElement; +} + +WebInspector.SearchResultsTreeElementsTraverser.prototype = { + /** + * @return {WebInspector.BaseStorageTreeElement} + */ + first: function() + { + return this.next(this._root); + }, + + /** + * @return {WebInspector.BaseStorageTreeElement} + */ + last: function() + { + return this.previous(this._root); + }, + + /** + * @param {WebInspector.BaseStorageTreeElement} startTreeElement + * @return {WebInspector.BaseStorageTreeElement} + */ + next: function(startTreeElement) + { + var treeElement = startTreeElement; + do { + treeElement = this._traverseNext(treeElement) || this._root; + } while (treeElement != startTreeElement && !this._elementSearchMatchesCount(treeElement)); + return treeElement; + }, + + /** + * @param {WebInspector.BaseStorageTreeElement} startTreeElement + * @return {WebInspector.BaseStorageTreeElement} + */ + previous: function(startTreeElement) + { + var treeElement = startTreeElement; + do { + treeElement = this._traversePrevious(treeElement) || this._lastTreeElement(); + } while (treeElement != startTreeElement && !this._elementSearchMatchesCount(treeElement)); + return treeElement; + }, + + /** + * @param {WebInspector.BaseStorageTreeElement} startTreeElement + * @param {number} index + * @return {number} + */ + matchIndex: function(startTreeElement, index) + { + var matchIndex = 1; + var treeElement = this._root; + while (treeElement != startTreeElement) { + matchIndex += this._elementSearchMatchesCount(treeElement); + treeElement = this._traverseNext(treeElement) || this._root; + if (treeElement === this._root) + return 0; + } + return matchIndex + index; + }, + + /** + * @param {WebInspector.BaseStorageTreeElement} treeElement + * @return {number} + */ + _elementSearchMatchesCount: function(treeElement) + { + return treeElement.searchMatchesCount; + }, + + /** + * @param {WebInspector.BaseStorageTreeElement} treeElement + * @return {WebInspector.BaseStorageTreeElement} + */ + _traverseNext: function(treeElement) + { + return /** @type {WebInspector.BaseStorageTreeElement} */ treeElement.traverseNextTreeElement(false, this._root, true); + }, + + /** + * @param {WebInspector.BaseStorageTreeElement} treeElement + * @return {WebInspector.BaseStorageTreeElement} + */ + _traversePrevious: function(treeElement) + { + return /** @type {WebInspector.BaseStorageTreeElement} */ treeElement.traversePreviousTreeElement(false, true); + }, + + /** + * @return {WebInspector.BaseStorageTreeElement} + */ + _lastTreeElement: function() + { + var treeElement = this._root; + var nextTreeElement; + while (nextTreeElement = this._traverseNext(treeElement)) + treeElement = nextTreeElement; + return treeElement; + } +} +/* ProfilesPanel.js */ + +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +const UserInitiatedProfileName = "org.webkit.profiles.user-initiated"; + +WebInspector.ProfileType = function(id, name) +{ + this._id = id; + this._name = name; +} + +WebInspector.ProfileType.URLRegExp = /webkit-profile:\/\/(.+)\/(.+)#([0-9]+)/; + +WebInspector.ProfileType.prototype = { + get buttonTooltip() + { + return ""; + }, + + get id() + { + return this._id; + }, + + get treeItemTitle() + { + return this._name; + }, + + get name() + { + return this._name; + }, + + buttonClicked: function() + { + }, + + viewForProfile: function(profile) + { + if (!profile._profileView) + profile._profileView = this.createView(profile); + return profile._profileView; + }, + + reset: function() + { + }, + + get description() + { + return ""; + }, + + // Must be implemented by subclasses. + createView: function(profile) + { + throw new Error("Needs implemented."); + }, + + // Must be implemented by subclasses. + createSidebarTreeElementForProfile: function(profile) + { + throw new Error("Needs implemented."); + } +} + +WebInspector.registerLinkifierPlugin(function(title) +{ + var profileStringMatches = WebInspector.ProfileType.URLRegExp.exec(title); + if (profileStringMatches) + title = WebInspector.panels.profiles.displayTitleForProfileLink(profileStringMatches[2], profileStringMatches[1]); + return title; +}); + +WebInspector.ProfilesPanel = function() +{ + WebInspector.Panel.call(this, "profiles"); + this.registerRequiredCSS("panelEnablerView.css"); + this.registerRequiredCSS("heapProfiler.css"); + this.registerRequiredCSS("profilesPanel.css"); + + this.createSplitViewWithSidebarTree(); + + this.profilesItemTreeElement = new WebInspector.ProfilesSidebarTreeElement(this); + this.sidebarTree.appendChild(this.profilesItemTreeElement); + + this._profileTypesByIdMap = {}; + + var panelEnablerHeading = WebInspector.UIString("You need to enable profiling before you can use the Profiles panel."); + var panelEnablerDisclaimer = WebInspector.UIString("Enabling profiling will make scripts run slower."); + var panelEnablerButton = WebInspector.UIString("Enable Profiling"); + this.panelEnablerView = new WebInspector.PanelEnablerView("profiles", panelEnablerHeading, panelEnablerDisclaimer, panelEnablerButton); + this.panelEnablerView.addEventListener("enable clicked", this.enableProfiler, this); + + this.profileViews = document.createElement("div"); + this.profileViews.id = "profile-views"; + this.splitView.mainElement.appendChild(this.profileViews); + + this.enableToggleButton = new WebInspector.StatusBarButton("", "enable-toggle-status-bar-item"); + this.enableToggleButton.addEventListener("click", this._toggleProfiling.bind(this), false); + if (!Capabilities.profilerCausesRecompilation) + this.enableToggleButton.element.addStyleClass("hidden"); + + this.recordButton = new WebInspector.StatusBarButton("", "record-profile-status-bar-item"); + this.recordButton.addEventListener("click", this.toggleRecordButton, this); + + this.clearResultsButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear all profiles."), "clear-status-bar-item"); + this.clearResultsButton.addEventListener("click", this._clearProfiles.bind(this), false); + + this.profileViewStatusBarItemsContainer = document.createElement("div"); + this.profileViewStatusBarItemsContainer.className = "status-bar-items"; + + this._profiles = []; + this._profilerEnabled = !Capabilities.profilerCausesRecompilation; + + this._launcherView = new WebInspector.ProfileLauncherView(this); + this._launcherView.addEventListener(WebInspector.ProfileLauncherView.EventTypes.ProfileTypeSelected, this._onProfileTypeSelected, this); + this._reset(); + this._launcherView.setUpEventListeners(); + + this._registerProfileType(new WebInspector.CPUProfileType()); + this._registerProfileType(new WebInspector.CSSSelectorProfileType()); + if (Capabilities.heapProfilerPresent) + this._registerProfileType(new WebInspector.DetailedHeapshotProfileType()); + + InspectorBackend.registerProfilerDispatcher(new WebInspector.ProfilerDispatcher(this)); + + if (!Capabilities.profilerCausesRecompilation || WebInspector.settings.profilerEnabled.get()) + ProfilerAgent.enable(this._profilerWasEnabled.bind(this)); +} + +WebInspector.ProfilesPanel.EventTypes = { + ProfileStarted: "profile-started", + ProfileFinished: "profile-finished" +} + +WebInspector.ProfilesPanel.prototype = { + get toolbarItemLabel() + { + return WebInspector.UIString("Profiles"); + }, + + get statusBarItems() + { + return [this.enableToggleButton.element, this.recordButton.element, this.clearResultsButton.element, this.profileViewStatusBarItemsContainer]; + }, + + toggleRecordButton: function() + { + this._selectedProfileType.buttonClicked(); + }, + + wasShown: function() + { + WebInspector.Panel.prototype.wasShown.call(this); + this._populateProfiles(); + }, + + _profilerWasEnabled: function() + { + if (this._profilerEnabled) + return; + + this._profilerEnabled = true; + + this._reset(); + if (this.isShowing()) + this._populateProfiles(); + }, + + _profilerWasDisabled: function() + { + if (!this._profilerEnabled) + return; + + this._profilerEnabled = false; + this._reset(); + }, + + _onProfileTypeSelected: function(event) + { + this._selectedProfileType = event.data; + this.recordButton.title = this._selectedProfileType.buttonTooltip; + }, + + _reset: function() + { + WebInspector.Panel.prototype.reset.call(this); + + for (var i = 0; i < this._profiles.length; ++i) { + var view = this._profiles[i]._profileView; + if (view) { + view.detach(); + if ("dispose" in view) + view.dispose(); + } + + delete this._profiles[i]._profileView; + } + delete this.visibleView; + + delete this.currentQuery; + this.searchCanceled(); + + for (var id in this._profileTypesByIdMap) { + var profileType = this._profileTypesByIdMap[id]; + var treeElement = profileType.treeElement; + treeElement.removeChildren(); + treeElement.hidden = true; + profileType.reset(); + } + + this._profiles = []; + this._profilesIdMap = {}; + this._profileGroups = {}; + this._profileGroupsForLinks = {}; + this._profilesWereRequested = false; + + this.sidebarTreeElement.removeStyleClass("some-expandable"); + + this.profileViews.removeChildren(); + this.profileViewStatusBarItemsContainer.removeChildren(); + + this.removeAllListeners(); + this._launcherView.setUpEventListeners(); + + this._updateInterface(); + this.profilesItemTreeElement.select(); + this._showLauncherView(); + }, + + _showLauncherView: function() + { + this.closeVisibleView(); + this.profileViewStatusBarItemsContainer.removeChildren(); + this._launcherView.show(this.splitView.mainElement); + this.visibleView = this._launcherView; + }, + + _clearProfiles: function() + { + ProfilerAgent.clearProfiles(); + this._reset(); + }, + + _registerProfileType: function(profileType) + { + this._profileTypesByIdMap[profileType.id] = profileType; + this._launcherView.addProfileType(profileType); + profileType.treeElement = new WebInspector.SidebarSectionTreeElement(profileType.treeItemTitle, null, true); + profileType.treeElement.hidden = true; + this.sidebarTree.appendChild(profileType.treeElement); + }, + + _makeKey: function(text, profileTypeId) + { + return escape(text) + '/' + escape(profileTypeId); + }, + + addProfileHeader: function(profile) + { + if (this.hasTemporaryProfile(profile.typeId)) + this._removeTemporaryProfile(); + + var typeId = profile.typeId; + var profileType = this.getProfileType(typeId); + var sidebarParent = profileType.treeElement; + sidebarParent.hidden = false; + var small = false; + var alternateTitle; + + profile.__profilesPanelProfileType = profileType; + this._profiles.push(profile); + this._profilesIdMap[this._makeKey(profile.uid, typeId)] = profile; + + if (profile.title.indexOf(UserInitiatedProfileName) !== 0) { + var profileTitleKey = this._makeKey(profile.title, typeId); + if (!(profileTitleKey in this._profileGroups)) + this._profileGroups[profileTitleKey] = []; + + var group = this._profileGroups[profileTitleKey]; + group.push(profile); + + if (group.length === 2) { + // Make a group TreeElement now that there are 2 profiles. + group._profilesTreeElement = new WebInspector.ProfileGroupSidebarTreeElement(profile.title); + + // Insert at the same index for the first profile of the group. + var index = sidebarParent.children.indexOf(group[0]._profilesTreeElement); + sidebarParent.insertChild(group._profilesTreeElement, index); + + // Move the first profile to the group. + var selected = group[0]._profilesTreeElement.selected; + sidebarParent.removeChild(group[0]._profilesTreeElement); + group._profilesTreeElement.appendChild(group[0]._profilesTreeElement); + if (selected) + group[0]._profilesTreeElement.revealAndSelect(); + + group[0]._profilesTreeElement.small = true; + group[0]._profilesTreeElement.mainTitle = WebInspector.UIString("Run %d", 1); + + this.sidebarTreeElement.addStyleClass("some-expandable"); + } + + if (group.length >= 2) { + sidebarParent = group._profilesTreeElement; + alternateTitle = WebInspector.UIString("Run %d", group.length); + small = true; + } + } + + var profileTreeElement = profileType.createSidebarTreeElementForProfile(profile); + profile.sidebarElement = profileTreeElement; + profileTreeElement.small = small; + if (alternateTitle) + profileTreeElement.mainTitle = alternateTitle; + profile._profilesTreeElement = profileTreeElement; + + sidebarParent.appendChild(profileTreeElement); + if (!profile.isTemporary) { + if (!this.visibleView) + this.showProfile(profile); + this.dispatchEventToListeners("profile added"); + delete this._temporaryRecordingProfile; + this.dispatchEventToListeners(WebInspector.ProfilesPanel.EventTypes.ProfileFinished); + this.recordButton.toggled = false; + } else { + this.dispatchEventToListeners(WebInspector.ProfilesPanel.EventTypes.ProfileStarted); + this.recordButton.toggled = true; + } + + this.recordButton.title = this._selectedProfileType.buttonTooltip; + }, + + _removeTemporaryProfile: function() + { + this._removeProfileHeader(this._temporaryRecordingProfile); + delete this._temporaryRecordingProfile; + }, + + _removeProfileHeader: function(profile) + { + var typeId = profile.typeId; + var profileType = this.getProfileType(typeId); + var sidebarParent = profileType.treeElement; + + for (var i = 0; i < this._profiles.length; ++i) { + if (this._profiles[i].uid === profile.uid) { + profile = this._profiles[i]; + this._profiles.splice(i, 1); + break; + } + } + delete this._profilesIdMap[this._makeKey(profile.uid, typeId)]; + + var profileTitleKey = this._makeKey(profile.title, typeId); + delete this._profileGroups[profileTitleKey]; + + sidebarParent.removeChild(profile._profilesTreeElement); + + if (!profile.isTemporary) + ProfilerAgent.removeProfile(profile.typeId, profile.uid); + + // No other item will be selected if there aren't any other profiles, so + // make sure that view gets cleared when the last profile is removed. + if (!this._profiles.length) + this.closeVisibleView(); + }, + + showProfile: function(profile) + { + if (!profile || profile.isTemporary) + return; + + this.closeVisibleView(); + + var view = profile.__profilesPanelProfileType.viewForProfile(profile); + + view.show(this.profileViews); + + profile._profilesTreeElement._suppressOnSelect = true; + profile._profilesTreeElement.revealAndSelect(); + delete profile._profilesTreeElement._suppressOnSelect; + + this.visibleView = view; + + this.profileViewStatusBarItemsContainer.removeChildren(); + + var statusBarItems = view.statusBarItems; + if (statusBarItems) + for (var i = 0; i < statusBarItems.length; ++i) + this.profileViewStatusBarItemsContainer.appendChild(statusBarItems[i]); + }, + + getProfiles: function(typeId) + { + var result = []; + var profilesCount = this._profiles.length; + for (var i = 0; i < profilesCount; ++i) { + var profile = this._profiles[i]; + if (!profile.isTemporary && profile.typeId === typeId) + result.push(profile); + } + return result; + }, + + hasTemporaryProfile: function(typeId) + { + var profilesCount = this._profiles.length; + for (var i = 0; i < profilesCount; ++i) + if (this._profiles[i].typeId === typeId && this._profiles[i].isTemporary) + return true; + return false; + }, + + hasProfile: function(profile) + { + return !!this._profilesIdMap[this._makeKey(profile.uid, profile.typeId)]; + }, + + getProfile: function(typeId, uid) + { + return this._profilesIdMap[this._makeKey(uid, typeId)]; + }, + + loadHeapSnapshot: function(uid, callback) + { + var profile = this._profilesIdMap[this._makeKey(uid, WebInspector.DetailedHeapshotProfileType.TypeId)]; + if (!profile) + return; + + if (!profile.proxy) { + function setProfileWait(event) { + profile.sidebarElement.wait = event.data; + } + var worker = new WebInspector.HeapSnapshotWorker(); + worker.addEventListener("wait", setProfileWait, this); + profile.proxy = worker.createObject("WebInspector.HeapSnapshotLoader"); + } + var proxy = profile.proxy; + if (proxy.startLoading(callback)) { + profile.sidebarElement.subtitle = WebInspector.UIString("Loading\u2026"); + profile.sidebarElement.wait = true; + ProfilerAgent.getProfile(profile.typeId, profile.uid); + } + }, + + _addHeapSnapshotChunk: function(uid, chunk) + { + var profile = this._profilesIdMap[this._makeKey(uid, WebInspector.DetailedHeapshotProfileType.TypeId)]; + if (!profile || !profile.proxy) + return; + profile.proxy.pushJSONChunk(chunk); + }, + + _finishHeapSnapshot: function(uid) + { + var profile = this._profilesIdMap[this._makeKey(uid, WebInspector.DetailedHeapshotProfileType.TypeId)]; + if (!profile || !profile.proxy) + return; + var proxy = profile.proxy; + function parsed(snapshotProxy) + { + profile.proxy = snapshotProxy; + profile.sidebarElement.subtitle = Number.bytesToString(snapshotProxy.totalSize); + profile.sidebarElement.wait = false; + snapshotProxy.worker.startCheckingForLongRunningCalls(); + } + if (proxy.finishLoading(parsed)) + profile.sidebarElement.subtitle = WebInspector.UIString("Parsing\u2026"); + }, + + showView: function(view) + { + this.showProfile(view.profile); + }, + + getProfileType: function(typeId) + { + return this._profileTypesByIdMap[typeId]; + }, + + showProfileForURL: function(url) + { + var match = url.match(WebInspector.ProfileType.URLRegExp); + if (!match) + return; + this.showProfile(this._profilesIdMap[this._makeKey(match[3], match[1])]); + }, + + closeVisibleView: function() + { + if (this.visibleView) + this.visibleView.detach(); + delete this.visibleView; + }, + + displayTitleForProfileLink: function(title, typeId) + { + title = unescape(title); + if (title.indexOf(UserInitiatedProfileName) === 0) { + title = WebInspector.UIString("Profile %d", title.substring(UserInitiatedProfileName.length + 1)); + } else { + var titleKey = this._makeKey(title, typeId); + if (!(titleKey in this._profileGroupsForLinks)) + this._profileGroupsForLinks[titleKey] = 0; + + var groupNumber = ++this._profileGroupsForLinks[titleKey]; + + if (groupNumber > 2) + // The title is used in the console message announcing that a profile has started so it gets + // incremented twice as often as it's displayed + title += " " + WebInspector.UIString("Run %d", (groupNumber + 1) / 2); + } + + return title; + }, + + performSearch: function(query) + { + this.searchCanceled(); + + var searchableViews = this._searchableViews(); + if (!searchableViews || !searchableViews.length) + return; + + var parentElement = this.viewsContainerElement; + var visibleView = this.visibleView; + var sortFuction = this.searchResultsSortFunction; + + var matchesCountUpdateTimeout = null; + + function updateMatchesCount() + { + WebInspector.searchController.updateSearchMatchesCount(this._totalSearchMatches, this); + matchesCountUpdateTimeout = null; + } + + function updateMatchesCountSoon() + { + if (matchesCountUpdateTimeout) + return; + // Update the matches count every half-second so it doesn't feel twitchy. + matchesCountUpdateTimeout = setTimeout(updateMatchesCount.bind(this), 500); + } + + function finishedCallback(view, searchMatches) + { + if (!searchMatches) + return; + + this._totalSearchMatches += searchMatches; + this._searchResults.push(view); + + if (sortFuction) + this._searchResults.sort(sortFuction); + + if (this.searchMatchFound) + this.searchMatchFound(view, searchMatches); + + updateMatchesCountSoon.call(this); + + if (view === visibleView) + view.jumpToFirstSearchResult(); + } + + var i = 0; + var panel = this; + var boundFinishedCallback = finishedCallback.bind(this); + var chunkIntervalIdentifier = null; + + // Split up the work into chunks so we don't block the + // UI thread while processing. + + function processChunk() + { + var view = searchableViews[i]; + + if (++i >= searchableViews.length) { + if (panel._currentSearchChunkIntervalIdentifier === chunkIntervalIdentifier) + delete panel._currentSearchChunkIntervalIdentifier; + clearInterval(chunkIntervalIdentifier); + } + + if (!view) + return; + + view.currentQuery = query; + view.performSearch(query, boundFinishedCallback); + } + + processChunk(); + + chunkIntervalIdentifier = setInterval(processChunk, 25); + this._currentSearchChunkIntervalIdentifier = chunkIntervalIdentifier; + }, + + jumpToNextSearchResult: function() + { + if (!this.showView || !this._searchResults || !this._searchResults.length) + return; + + var showFirstResult = false; + + this._currentSearchResultIndex = this._searchResults.indexOf(this.visibleView); + if (this._currentSearchResultIndex === -1) { + this._currentSearchResultIndex = 0; + showFirstResult = true; + } + + var currentView = this._searchResults[this._currentSearchResultIndex]; + + if (currentView.showingLastSearchResult()) { + if (++this._currentSearchResultIndex >= this._searchResults.length) + this._currentSearchResultIndex = 0; + currentView = this._searchResults[this._currentSearchResultIndex]; + showFirstResult = true; + } + + if (currentView !== this.visibleView) { + this.showView(currentView); + WebInspector.searchController.focusSearchField(); + } + + if (showFirstResult) + currentView.jumpToFirstSearchResult(); + else + currentView.jumpToNextSearchResult(); + }, + + jumpToPreviousSearchResult: function() + { + if (!this.showView || !this._searchResults || !this._searchResults.length) + return; + + var showLastResult = false; + + this._currentSearchResultIndex = this._searchResults.indexOf(this.visibleView); + if (this._currentSearchResultIndex === -1) { + this._currentSearchResultIndex = 0; + showLastResult = true; + } + + var currentView = this._searchResults[this._currentSearchResultIndex]; + + if (currentView.showingFirstSearchResult()) { + if (--this._currentSearchResultIndex < 0) + this._currentSearchResultIndex = (this._searchResults.length - 1); + currentView = this._searchResults[this._currentSearchResultIndex]; + showLastResult = true; + } + + if (currentView !== this.visibleView) { + this.showView(currentView); + WebInspector.searchController.focusSearchField(); + } + + if (showLastResult) + currentView.jumpToLastSearchResult(); + else + currentView.jumpToPreviousSearchResult(); + }, + + _searchableViews: function() + { + var views = []; + + const visibleView = this.visibleView; + if (visibleView && visibleView.performSearch) + views.push(visibleView); + + var profilesLength = this._profiles.length; + for (var i = 0; i < profilesLength; ++i) { + var profile = this._profiles[i]; + var view = profile.__profilesPanelProfileType.viewForProfile(profile); + if (!view.performSearch || view === visibleView) + continue; + views.push(view); + } + + return views; + }, + + searchMatchFound: function(view, matches) + { + view.profile._profilesTreeElement.searchMatches = matches; + }, + + searchCanceled: function() + { + if (this._searchResults) { + for (var i = 0; i < this._searchResults.length; ++i) { + var view = this._searchResults[i]; + if (view.searchCanceled) + view.searchCanceled(); + delete view.currentQuery; + } + } + + WebInspector.Panel.prototype.searchCanceled.call(this); + + if (this._currentSearchChunkIntervalIdentifier) { + clearInterval(this._currentSearchChunkIntervalIdentifier); + delete this._currentSearchChunkIntervalIdentifier; + } + + this._totalSearchMatches = 0; + this._currentSearchResultIndex = 0; + this._searchResults = []; + + if (!this._profiles) + return; + + for (var i = 0; i < this._profiles.length; ++i) { + var profile = this._profiles[i]; + profile._profilesTreeElement.searchMatches = 0; + } + }, + + _updateInterface: function() + { + // FIXME: Replace ProfileType-specific button visibility changes by a single ProfileType-agnostic "combo-button" visibility change. + if (this._profilerEnabled) { + this.enableToggleButton.title = WebInspector.UIString("Profiling enabled. Click to disable."); + this.enableToggleButton.toggled = true; + this.recordButton.visible = true; + this.profileViewStatusBarItemsContainer.removeStyleClass("hidden"); + this.clearResultsButton.element.removeStyleClass("hidden"); + this.panelEnablerView.detach(); + } else { + this.enableToggleButton.title = WebInspector.UIString("Profiling disabled. Click to enable."); + this.enableToggleButton.toggled = false; + this.recordButton.visible = false; + this.profileViewStatusBarItemsContainer.addStyleClass("hidden"); + this.clearResultsButton.element.addStyleClass("hidden"); + this.panelEnablerView.show(this.element); + } + }, + + get profilerEnabled() + { + return this._profilerEnabled; + }, + + enableProfiler: function() + { + if (this._profilerEnabled) + return; + this._toggleProfiling(this.panelEnablerView.alwaysEnabled); + }, + + disableProfiler: function() + { + if (!this._profilerEnabled) + return; + this._toggleProfiling(this.panelEnablerView.alwaysEnabled); + }, + + _toggleProfiling: function(optionalAlways) + { + if (this._profilerEnabled) { + WebInspector.settings.profilerEnabled.set(false); + ProfilerAgent.disable(this._profilerWasDisabled.bind(this)); + } else { + WebInspector.settings.profilerEnabled.set(!!optionalAlways); + ProfilerAgent.enable(this._profilerWasEnabled.bind(this)); + } + }, + + _populateProfiles: function() + { + if (!this._profilerEnabled || this._profilesWereRequested) + return; + + function populateCallback(error, profileHeaders) { + if (error) + return; + profileHeaders.sort(function(a, b) { return a.uid - b.uid; }); + var profileHeadersLength = profileHeaders.length; + for (var i = 0; i < profileHeadersLength; ++i) + if (!this.hasProfile(profileHeaders[i])) + this.addProfileHeader(profileHeaders[i]); + } + + ProfilerAgent.getProfileHeaders(populateCallback.bind(this)); + + this._profilesWereRequested = true; + }, + + sidebarResized: function(event) + { + var width = event.data; + // Min width = * 31 + this.profileViewStatusBarItemsContainer.style.left = Math.max(5 * 31, width) + "px"; + }, + + setRecordingProfile: function(profileType, isProfiling) + { + this.getProfileType(profileType).setRecordingProfile(isProfiling); + if (this.hasTemporaryProfile(profileType) !== isProfiling) { + if (!this._temporaryRecordingProfile) { + this._temporaryRecordingProfile = { + typeId: profileType, + title: WebInspector.UIString("Recording\u2026"), + uid: -1, + isTemporary: true + }; + } + if (isProfiling) { + this.addProfileHeader(this._temporaryRecordingProfile); + if (profileType === WebInspector.CPUProfileType.TypeId) + WebInspector.userMetrics.ProfilesCPUProfileTaken.record(); + } else + this._removeTemporaryProfile() + } + }, + + takeHeapSnapshot: function() + { + if (!this.hasTemporaryProfile(WebInspector.DetailedHeapshotProfileType.TypeId)) { + if (!this._temporaryRecordingProfile) { + this._temporaryRecordingProfile = { + typeId: WebInspector.DetailedHeapshotProfileType.TypeId, + title: WebInspector.UIString("Snapshotting\u2026"), + uid: -1, + isTemporary: true + }; + } + this.addProfileHeader(this._temporaryRecordingProfile); + } + ProfilerAgent.takeHeapSnapshot(); + WebInspector.userMetrics.ProfilesHeapProfileTaken.record(); + }, + + _reportHeapSnapshotProgress: function(done, total) + { + if (this.hasTemporaryProfile(WebInspector.DetailedHeapshotProfileType.TypeId)) { + this._temporaryRecordingProfile.sidebarElement.subtitle = WebInspector.UIString("%.2f%%", (done / total) * 100); + this._temporaryRecordingProfile.sidebarElement.wait = true; + if (done >= total) + this._removeTemporaryProfile(); + } + } +} + +WebInspector.ProfilesPanel.prototype.__proto__ = WebInspector.Panel.prototype; + + +WebInspector.ProfilerDispatcher = function(profiler) +{ + this._profiler = profiler; +} + +WebInspector.ProfilerDispatcher.prototype = { + resetProfiles: function() + { + this._profiler._reset(); + }, + + addProfileHeader: function(profile) + { + this._profiler.addProfileHeader(profile); + }, + + addHeapSnapshotChunk: function(uid, chunk) + { + this._profiler._addHeapSnapshotChunk(uid, chunk); + }, + + finishHeapSnapshot: function(uid) + { + this._profiler._finishHeapSnapshot(uid); + }, + + setRecordingProfile: function(isProfiling) + { + this._profiler.setRecordingProfile(WebInspector.CPUProfileType.TypeId, isProfiling); + }, + + reportHeapSnapshotProgress: function(done, total) + { + this._profiler._reportHeapSnapshotProgress(done, total); + } +} + +WebInspector.ProfileSidebarTreeElement = function(profile, titleFormat, className) +{ + this.profile = profile; + this._titleFormat = titleFormat; + + if (this.profile.title.indexOf(UserInitiatedProfileName) === 0) + this._profileNumber = this.profile.title.substring(UserInitiatedProfileName.length + 1); + + WebInspector.SidebarTreeElement.call(this, className, "", "", profile, false); + + this.refreshTitles(); +} + +WebInspector.ProfileSidebarTreeElement.prototype = { + onselect: function() + { + if (!this._suppressOnSelect) + this.treeOutline.panel.showProfile(this.profile); + }, + + ondelete: function() + { + this.treeOutline.panel._removeProfileHeader(this.profile); + return true; + }, + + get mainTitle() + { + if (this._mainTitle) + return this._mainTitle; + if (this.profile.title.indexOf(UserInitiatedProfileName) === 0) + return WebInspector.UIString(this._titleFormat, this._profileNumber); + return this.profile.title; + }, + + set mainTitle(x) + { + this._mainTitle = x; + this.refreshTitles(); + }, + + set searchMatches(matches) + { + if (!matches) { + if (!this.bubbleElement) + return; + this.bubbleElement.removeStyleClass("search-matches"); + this.bubbleText = ""; + return; + } + + this.bubbleText = matches; + this.bubbleElement.addStyleClass("search-matches"); + } +} + +WebInspector.ProfileSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype; + +WebInspector.ProfileGroupSidebarTreeElement = function(title, subtitle) +{ + WebInspector.SidebarTreeElement.call(this, "profile-group-sidebar-tree-item", title, subtitle, null, true); +} + +WebInspector.ProfileGroupSidebarTreeElement.prototype = { + onselect: function() + { + if (this.children.length > 0) + WebInspector.panels.profiles.showProfile(this.children[this.children.length - 1].profile); + } +} + +WebInspector.ProfileGroupSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype; + +/** + * @constructor + * @extends {WebInspector.SidebarTreeElement} + */ +WebInspector.ProfilesSidebarTreeElement = function(panel) +{ + this._panel = panel; + this.small = false; + + WebInspector.SidebarTreeElement.call(this, "profile-launcher-view-tree-item", WebInspector.UIString("Profiles"), "", null, false); +} + +WebInspector.ProfilesSidebarTreeElement.prototype = { + onselect: function() + { + this._panel._showLauncherView(); + }, + + get selectable() + { + return true; + } +} + +WebInspector.ProfilesSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype; +/* ConsolePanel.js */ + +/* + * Copyright (C) 2009 Joseph Pecoraro + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.Panel} + */ +WebInspector.ConsolePanel = function() +{ + WebInspector.Panel.call(this, "console"); + + WebInspector.consoleView.addEventListener(WebInspector.ConsoleView.Events.EntryAdded, this._consoleMessageAdded, this); + WebInspector.consoleView.addEventListener(WebInspector.ConsoleView.Events.ConsoleCleared, this._consoleCleared, this); + this._view = WebInspector.consoleView; +} + +WebInspector.ConsolePanel.prototype = { + get toolbarItemLabel() + { + return WebInspector.UIString("Console"); + }, + + get statusBarItems() + { + return this._view.statusBarItems; + }, + + wasShown: function() + { + WebInspector.Panel.prototype.wasShown.call(this); + if (WebInspector.drawer.visible) { + WebInspector.drawer.hide(WebInspector.Drawer.AnimationType.Immediately); + this._drawerWasVisible = true; + } + this._view.show(this.element); + }, + + willHide: function() + { + if (this._drawerWasVisible) { + WebInspector.drawer.show(this._view, WebInspector.Drawer.AnimationType.Immediately); + delete this._drawerWasVisible; + } + WebInspector.Panel.prototype.willHide.call(this); + }, + + searchCanceled: function() + { + this._clearCurrentSearchResultHighlight(); + delete this._searchResults; + delete this._searchRegex; + }, + + performSearch: function(query) + { + WebInspector.searchController.updateSearchMatchesCount(0, this); + this.searchCanceled(); + this._searchRegex = createPlainTextSearchRegex(query, "gi"); + + this._searchResults = []; + var messages = WebInspector.consoleView.messages; + for (var i = 0; i < messages.length; i++) { + if (messages[i].matchesRegex(this._searchRegex)) { + this._searchResults.push(messages[i]); + this._searchRegex.lastIndex = 0; + } + } + WebInspector.searchController.updateSearchMatchesCount(this._searchResults.length, this); + this._currentSearchResultIndex = -1; + if (this._searchResults.length) + this._jumpToSearchResult(0); + }, + + jumpToNextSearchResult: function() + { + if (!this._searchResults || !this._searchResults.length) + return; + this._jumpToSearchResult((this._currentSearchResultIndex + 1) % this._searchResults.length); + }, + + jumpToPreviousSearchResult: function() + { + if (!this._searchResults || !this._searchResults.length) + return; + var index = this._currentSearchResultIndex - 1; + if (index === -1) + index = this._searchResults.length - 1; + this._jumpToSearchResult(index); + }, + + _clearCurrentSearchResultHighlight: function() + { + if (!this._searchResults) + return; + var highlightedMessage = this._searchResults[this._currentSearchResultIndex]; + if (highlightedMessage) + highlightedMessage.clearHighlight(); + this._currentSearchResultIndex = -1; + }, + + _jumpToSearchResult: function(index) + { + this._clearCurrentSearchResultHighlight(); + this._currentSearchResultIndex = index; + this._searchResults[index].highlightSearchResults(this._searchRegex); + }, + + _consoleMessageAdded: function(event) + { + if (!this._searchRegex || !this.isShowing()) + return; + var message = event.data; + this._searchRegex.lastIndex = 0; + if (message.matchesRegex(this._searchRegex)) { + this._searchResults.push(message); + WebInspector.searchController.updateSearchMatchesCount(this._searchResults.length, this); + } + }, + + _consoleCleared: function() + { + if (!this._searchResults) + return; + this._clearCurrentSearchResultHighlight(); + this._searchResults.length = 0; + if (this.isShowing()) + WebInspector.searchController.updateSearchMatchesCount(0, this); + } +} + +WebInspector.ConsolePanel.prototype.__proto__ = WebInspector.Panel.prototype; +/* ExtensionAPI.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +function defineCommonExtensionSymbols(apiPrivate) +{ + if (!apiPrivate.audits) + apiPrivate.audits = {}; + + apiPrivate.audits.Severity = { + Info: "info", + Warning: "warning", + Severe: "severe" + }; + + if (!apiPrivate.console) + apiPrivate.console = {}; + apiPrivate.console.Severity = { + Tip: "tip", + Debug: "debug", + Log: "log", + Warning: "warning", + Error: "error" + }; + apiPrivate.Events = { + AuditStarted: "audit-started-", + ButtonClicked: "button-clicked-", + ConsoleMessageAdded: "console-message-added", + ElementsPanelObjectSelected: "panel-objectSelected-elements", + NetworkRequestFinished: "network-request-finished", + Reset: "reset", + OpenResource: "open-resource", + PanelSearch: "panel-search-", + Reload: "Reload", + ResourceAdded: "resource-added", + ResourceContentCommitted: "resource-content-committed", + TimelineEventRecorded: "timeline-event-recorded", + ViewShown: "view-shown-", + ViewHidden: "view-hidden-" + }; + apiPrivate.Commands = { + AddAuditCategory: "addAuditCategory", + AddAuditResult: "addAuditResult", + AddConsoleMessage: "addConsoleMessage", + AddRequestHeaders: "addRequestHeaders", + CreatePanel: "createPanel", + CreateSidebarPane: "createSidebarPane", + CreateStatusBarButton: "createStatusBarButton", + EvaluateOnInspectedPage: "evaluateOnInspectedPage", + GetConsoleMessages: "getConsoleMessages", + GetHAR: "getHAR", + GetPageResources: "getPageResources", + GetRequestContent: "getRequestContent", + GetResourceContent: "getResourceContent", + Subscribe: "subscribe", + SetOpenResourceHandler: "setOpenResourceHandler", + SetResourceContent: "setResourceContent", + SetSidebarContent: "setSidebarContent", + SetSidebarHeight: "setSidebarHeight", + SetSidebarPage: "setSidebarPage", + StopAuditCategoryRun: "stopAuditCategoryRun", + Unsubscribe: "unsubscribe", + UpdateButton: "updateButton", + InspectedURLChanged: "inspectedURLChanged" + }; +} + +function injectedExtensionAPI(injectedScriptId) +{ + +var apiPrivate = {}; + +defineCommonExtensionSymbols(apiPrivate); + +var commands = apiPrivate.Commands; +var events = apiPrivate.Events; + +// Here and below, all constructors are private to API implementation. +// For a public type Foo, if internal fields are present, these are on +// a private FooImpl type, an instance of FooImpl is used in a closure +// by Foo consutrctor to re-bind publicly exported members to an instance +// of Foo. + +/** + * @constructor + */ +function EventSinkImpl(type, customDispatch) +{ + this._type = type; + this._listeners = []; + this._customDispatch = customDispatch; +} + +EventSinkImpl.prototype = { + addListener: function(callback) + { + if (typeof callback !== "function") + throw "addListener: callback is not a function"; + if (this._listeners.length === 0) + extensionServer.sendRequest({ command: commands.Subscribe, type: this._type }); + this._listeners.push(callback); + extensionServer.registerHandler("notify-" + this._type, bind(this._dispatch, this)); + }, + + removeListener: function(callback) + { + var listeners = this._listeners; + + for (var i = 0; i < listeners.length; ++i) { + if (listeners[i] === callback) { + listeners.splice(i, 1); + break; + } + } + if (this._listeners.length === 0) + extensionServer.sendRequest({ command: commands.Unsubscribe, type: this._type }); + }, + + _fire: function() + { + var listeners = this._listeners.slice(); + for (var i = 0; i < listeners.length; ++i) + listeners[i].apply(null, arguments); + }, + + _dispatch: function(request) + { + if (this._customDispatch) + this._customDispatch.call(this, request); + else + this._fire.apply(this, request.arguments); + } +} + +/** + * @constructor + */ +function InspectorExtensionAPI() +{ + this.audits = new Audits(); + this.inspectedWindow = new InspectedWindow(); + this.panels = new Panels(); + this.network = new Network(); + defineDeprecatedProperty(this, "webInspector", "resources", "network"); + this.timeline = new Timeline(); + this.console = new ConsoleAPI(); + + this.onReset = new EventSink(events.Reset); +} + +/** + * @constructor + */ +InspectorExtensionAPI.prototype = { + log: function(message) + { + extensionServer.sendRequest({ command: commands.Log, message: message }); + } +} + +/** + * @constructor + */ +function ConsoleAPI() +{ + this.onMessageAdded = new EventSink(events.ConsoleMessageAdded); +} + +ConsoleAPI.prototype = { + getMessages: function(callback) + { + extensionServer.sendRequest({ command: commands.GetConsoleMessages }, callback); + }, + + addMessage: function(severity, text, url, line) + { + extensionServer.sendRequest({ command: commands.AddConsoleMessage, severity: severity, text: text, url: url, line: line }); + }, + + get Severity() + { + return apiPrivate.console.Severity; + } +} + +/** + * @constructor + */ +function Network() +{ + function dispatchRequestEvent(message) + { + var request = message.arguments[1]; + request.__proto__ = new Request(message.arguments[0]); + this._fire(request); + } + this.onRequestFinished = new EventSink(events.NetworkRequestFinished, dispatchRequestEvent); + defineDeprecatedProperty(this, "network", "onFinished", "onRequestFinished"); + this.onNavigated = new EventSink(events.InspectedURLChanged); +} + +Network.prototype = { + getHAR: function(callback) + { + function callbackWrapper(result) + { + var entries = (result && result.entries) || []; + for (var i = 0; i < entries.length; ++i) { + entries[i].__proto__ = new Request(entries[i]._requestId); + delete entries[i]._requestId; + } + callback(result); + } + return extensionServer.sendRequest({ command: commands.GetHAR }, callback && callbackWrapper); + }, + + addRequestHeaders: function(headers) + { + return extensionServer.sendRequest({ command: commands.AddRequestHeaders, headers: headers, extensionId: window.location.hostname }); + } +} + +/** + * @constructor + */ +function RequestImpl(id) +{ + this._id = id; +} + +RequestImpl.prototype = { + getContent: function(callback) + { + function callbackWrapper(response) + { + callback(response.content, response.encoding); + } + extensionServer.sendRequest({ command: commands.GetRequestContent, id: this._id }, callback && callbackWrapper); + } +} + +/** + * @constructor + */ +function Panels() +{ + var panels = { + elements: new ElementsPanel() + }; + + function panelGetter(name) + { + return panels[name]; + } + for (var panel in panels) + this.__defineGetter__(panel, bind(panelGetter, null, panel)); +} + +Panels.prototype = { + create: function(title, icon, page, callback) + { + var id = "extension-panel-" + extensionServer.nextObjectId(); + var request = { + command: commands.CreatePanel, + id: id, + title: title, + icon: icon, + page: page + }; + extensionServer.sendRequest(request, callback && bind(callback, this, new ExtensionPanel(id))); + }, + + setOpenResourceHandler: function(callback) + { + var hadHandler = extensionServer.hasHandler(events.OpenResource); + + if (!callback) + extensionServer.unregisterHandler(events.OpenResource); + else { + function callbackWrapper(message) + { + callback.call(null, message.resource, message.lineNumber); + } + extensionServer.registerHandler(events.OpenResource, callbackWrapper); + } + // Only send command if we either removed an existing handler or added handler and had none before. + if (hadHandler === !callback) + extensionServer.sendRequest({ command: commands.SetOpenResourceHandler, "handlerPresent": !!callback }); + } +} + +/** + * @constructor + */ +function ExtensionViewImpl(id) +{ + this._id = id; + + function dispatchShowEvent(message) + { + var frameIndex = message.arguments[0]; + this._fire(window.top.frames[frameIndex]); + } + this.onShown = new EventSink(events.ViewShown + id, dispatchShowEvent); + this.onHidden = new EventSink(events.ViewHidden + id); +} + +/** + * @constructor + */ +function PanelWithSidebarImpl(id) +{ + this._id = id; +} + +PanelWithSidebarImpl.prototype = { + createSidebarPane: function(title, callback) + { + var id = "extension-sidebar-" + extensionServer.nextObjectId(); + var request = { + command: commands.CreateSidebarPane, + panel: this._id, + id: id, + title: title + }; + function callbackWrapper() + { + callback(new ExtensionSidebarPane(id)); + } + extensionServer.sendRequest(request, callback && callbackWrapper); + } +} + +PanelWithSidebarImpl.prototype.__proto__ = ExtensionViewImpl.prototype; + +/** + * @constructor + * @extends {PanelWithSidebar} + */ +function ElementsPanel() +{ + var id = "elements"; + PanelWithSidebar.call(this, id); + this.onSelectionChanged = new EventSink(events.ElementsPanelObjectSelected); +} + +/** + * @constructor + * @extends {ExtensionViewImpl} + */ +function ExtensionPanelImpl(id) +{ + ExtensionViewImpl.call(this, id); + this.onSearch = new EventSink(events.PanelSearch + id); +} + +ExtensionPanelImpl.prototype = { + createStatusBarButton: function(iconPath, tooltipText, disabled) + { + var id = "button-" + extensionServer.nextObjectId(); + var request = { + command: commands.CreateStatusBarButton, + panel: this._id, + id: id, + icon: iconPath, + tooltip: tooltipText, + disabled: !!disabled + }; + extensionServer.sendRequest(request); + return new Button(id); + } +}; + +ExtensionPanelImpl.prototype.__proto__ = ExtensionViewImpl.prototype; + +/** + * @constructor + * @extends {ExtensionViewImpl} + */ +function ExtensionSidebarPaneImpl(id) +{ + ExtensionViewImpl.call(this, id); +} + +ExtensionSidebarPaneImpl.prototype = { + setHeight: function(height) + { + extensionServer.sendRequest({ command: commands.SetSidebarHeight, id: this._id, height: height }); + }, + + setExpression: function(expression, rootTitle, callback) + { + extensionServer.sendRequest({ command: commands.SetSidebarContent, id: this._id, expression: expression, rootTitle: rootTitle, evaluateOnPage: true }, callback); + }, + + setObject: function(jsonObject, rootTitle, callback) + { + extensionServer.sendRequest({ command: commands.SetSidebarContent, id: this._id, expression: jsonObject, rootTitle: rootTitle }, callback); + }, + + setPage: function(page) + { + extensionServer.sendRequest({ command: commands.SetSidebarPage, id: this._id, page: page }); + } +} + +/** + * @constructor + */ +function ButtonImpl(id) +{ + this._id = id; + this.onClicked = new EventSink(events.ButtonClicked + id); +} + +ButtonImpl.prototype = { + update: function(iconPath, tooltipText, disabled) + { + var request = { + command: commands.UpdateButton, + id: this._id, + icon: iconPath, + tooltip: tooltipText, + disabled: !!disabled + }; + extensionServer.sendRequest(request); + } +}; + +/** + * @constructor + */ +function Audits() +{ +} + +Audits.prototype = { + addCategory: function(displayName, resultCount) + { + var id = "extension-audit-category-" + extensionServer.nextObjectId(); + extensionServer.sendRequest({ command: commands.AddAuditCategory, id: id, displayName: displayName, resultCount: resultCount }); + return new AuditCategory(id); + } +} + +/** + * @constructor + */ +function AuditCategoryImpl(id) +{ + function dispatchAuditEvent(request) + { + var auditResult = new AuditResult(request.arguments[0]); + try { + this._fire(auditResult); + } catch (e) { + console.error("Uncaught exception in extension audit event handler: " + e); + auditResult.done(); + } + } + this._id = id; + this.onAuditStarted = new EventSink(events.AuditStarted + id, dispatchAuditEvent); +} + +/** + * @constructor + */ +function AuditResultImpl(id) +{ + this._id = id; + + this.createURL = bind(this._nodeFactory, null, "url"); + this.createSnippet = bind(this._nodeFactory, null, "snippet"); + this.createText = bind(this._nodeFactory, null, "text"); +} + +AuditResultImpl.prototype = { + addResult: function(displayName, description, severity, details) + { + // shorthand for specifying details directly in addResult(). + if (details && !(details instanceof AuditResultNode)) + details = new AuditResultNode(details instanceof Array ? details : [details]); + + var request = { + command: commands.AddAuditResult, + resultId: this._id, + displayName: displayName, + description: description, + severity: severity, + details: details + }; + extensionServer.sendRequest(request); + }, + + createResult: function() + { + return new AuditResultNode(Array.prototype.slice.call(arguments)); + }, + + done: function() + { + extensionServer.sendRequest({ command: commands.StopAuditCategoryRun, resultId: this._id }); + }, + + get Severity() + { + return apiPrivate.audits.Severity; + }, + + createResourceLink: function(url, lineNumber) + { + return { + type: "resourceLink", + arguments: [url, lineNumber && lineNumber - 1] + }; + }, + + _nodeFactory: function(type) + { + return { + type: type, + arguments: Array.prototype.slice.call(arguments, 1) + }; + } +} + +/** + * @constructor + */ +function AuditResultNode(contents) +{ + this.contents = contents; + this.children = []; + this.expanded = false; +} + +AuditResultNode.prototype = { + addChild: function() + { + var node = new AuditResultNode(Array.prototype.slice.call(arguments)); + this.children.push(node); + return node; + } +}; + +/** + * @constructor + */ +function InspectedWindow() +{ + function dispatchResourceEvent(message) + { + this._fire(new Resource(message.arguments[0])); + } + function dispatchResourceContentEvent(message) + { + this._fire(new Resource(message.arguments[0]), message.arguments[1]); + } + this.onResourceAdded = new EventSink(events.ResourceAdded, dispatchResourceEvent); + this.onResourceContentCommitted = new EventSink(events.ResourceContentCommitted, dispatchResourceContentEvent); +} + +InspectedWindow.prototype = { + reload: function(optionsOrUserAgent) + { + var options = null; + if (typeof optionsOrUserAgent === "object") + options = optionsOrUserAgent; + else if (typeof optionsOrUserAgent === "string") { + options = { userAgent: optionsOrUserAgent }; + console.warn("Passing userAgent as string parameter to inspectedWindow.reload() is deprecated. " + + "Use inspectedWindow.reload({ userAgent: value}) instead."); + } + return extensionServer.sendRequest({ command: commands.Reload, options: options }); + }, + + eval: function(expression, callback) + { + function callbackWrapper(result) + { + callback(result.value, result.isException); + } + return extensionServer.sendRequest({ command: commands.EvaluateOnInspectedPage, expression: expression }, callback && callbackWrapper); + }, + + getResources: function(callback) + { + function wrapResource(resourceData) + { + return new Resource(resourceData); + } + function callbackWrapper(resources) + { + callback(resources.map(wrapResource)); + } + return extensionServer.sendRequest({ command: commands.GetPageResources }, callback && callbackWrapper); + } +} + +/** + * @constructor + */ +function ResourceImpl(resourceData) +{ + this._url = resourceData.url + this._type = resourceData.type; +} + +ResourceImpl.prototype = { + get url() + { + return this._url; + }, + + get type() + { + return this._type; + }, + + getContent: function(callback) + { + function callbackWrapper(response) + { + callback(response.content, response.encoding); + } + + return extensionServer.sendRequest({ command: commands.GetResourceContent, url: this._url }, callback && callbackWrapper); + }, + + setContent: function(content, commit, callback) + { + return extensionServer.sendRequest({ command: commands.SetResourceContent, url: this._url, content: content, commit: commit }, callback); + } +} + +/** + * @constructor + */ +function TimelineImpl() +{ + this.onEventRecorded = new EventSink(events.TimelineEventRecorded); +} + +/** + * @constructor + */ +function ExtensionServerClient() +{ + this._callbacks = {}; + this._handlers = {}; + this._lastRequestId = 0; + this._lastObjectId = 0; + + this.registerHandler("callback", bind(this._onCallback, this)); + + var channel = new MessageChannel(); + this._port = channel.port1; + this._port.addEventListener("message", bind(this._onMessage, this), false); + this._port.start(); + + top.postMessage("registerExtension", [ channel.port2 ], "*"); +} + +ExtensionServerClient.prototype = { + sendRequest: function(message, callback) + { + if (typeof callback === "function") + message.requestId = this._registerCallback(callback); + return this._port.postMessage(message); + }, + + hasHandler: function(command) + { + return !!this._handlers[command]; + }, + + registerHandler: function(command, handler) + { + this._handlers[command] = handler; + }, + + unregisterHandler: function(command) + { + delete this._handlers[command]; + }, + + nextObjectId: function() + { + return injectedScriptId + "_" + ++this._lastObjectId; + }, + + _registerCallback: function(callback) + { + var id = ++this._lastRequestId; + this._callbacks[id] = callback; + return id; + }, + + _onCallback: function(request) + { + if (request.requestId in this._callbacks) { + var callback = this._callbacks[request.requestId]; + delete this._callbacks[request.requestId]; + callback(request.result); + } + }, + + _onMessage: function(event) + { + var request = event.data; + var handler = this._handlers[request.command]; + if (handler) + handler.call(this, request); + } +} + +/** + * @param {...*} vararg + */ +function bind(func, thisObject, vararg) +{ + var args = Array.prototype.slice.call(arguments, 2); + return function() { return func.apply(thisObject, args.concat(Array.prototype.slice.call(arguments, 0))); }; +} + +function populateInterfaceClass(interface, implementation) +{ + for (var member in implementation) { + if (member.charAt(0) === "_") + continue; + var descriptor = null; + // Traverse prototype chain until we find the owner. + for (var owner = implementation; owner && !descriptor; owner = owner.__proto__) + descriptor = Object.getOwnPropertyDescriptor(owner, member); + if (!descriptor) + continue; + if (typeof descriptor.value === "function") + interface[member] = bind(descriptor.value, implementation); + else if (typeof descriptor.get === "function") + interface.__defineGetter__(member, bind(descriptor.get, implementation)); + else + Object.defineProperty(interface, member, descriptor); + } +} + +function declareInterfaceClass(implConstructor) +{ + return function() + { + var impl = { __proto__: implConstructor.prototype }; + implConstructor.apply(impl, arguments); + populateInterfaceClass(this, impl); + } +} + +function defineDeprecatedProperty(object, className, oldName, newName) +{ + var warningGiven = false; + function getter() + { + if (!warningGiven) { + console.warn(className + "." + oldName + " is deprecated. Use " + className + "." + newName + " instead"); + warningGiven = true; + } + return object[newName]; + } + object.__defineGetter__(oldName, getter); +} + +var AuditCategory = declareInterfaceClass(AuditCategoryImpl); +var AuditResult = declareInterfaceClass(AuditResultImpl); +var Button = declareInterfaceClass(ButtonImpl); +var EventSink = declareInterfaceClass(EventSinkImpl); +var ExtensionPanel = declareInterfaceClass(ExtensionPanelImpl); +var ExtensionSidebarPane = declareInterfaceClass(ExtensionSidebarPaneImpl); +var PanelWithSidebar = declareInterfaceClass(PanelWithSidebarImpl); +var Request = declareInterfaceClass(RequestImpl); +var Resource = declareInterfaceClass(ResourceImpl); +var Timeline = declareInterfaceClass(TimelineImpl); + +var extensionServer = new ExtensionServerClient(); + +return new InspectorExtensionAPI(); +} + +// Default implementation; platforms will override. +function buildPlatformExtensionAPI(extensionInfo) +{ + function platformExtensionAPI(coreAPI) + { + window.webInspector = coreAPI; + } + return platformExtensionAPI.toString(); +} + + +function buildExtensionAPIInjectedScript(extensionInfo) +{ + return "(function(injectedScriptHost, inspectedWindow, injectedScriptId){ " + + defineCommonExtensionSymbols.toString() + ";" + + injectedExtensionAPI.toString() + ";" + + buildPlatformExtensionAPI(extensionInfo) + ";" + + "platformExtensionAPI(injectedExtensionAPI(injectedScriptId));" + + "return {};" + + "})"; +} +/* ExtensionAuditCategory.js */ + +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + */ +WebInspector.ExtensionAuditCategory = function(id, displayName, ruleCount) +{ + this._id = id; + this._displayName = displayName; + this._ruleCount = ruleCount; +} + +WebInspector.ExtensionAuditCategory.prototype = { + // AuditCategory interface + get id() + { + return this._id; + }, + + get displayName() + { + return this._displayName; + }, + + get ruleCount() + { + return this._ruleCount; + }, + + run: function(resources, callback) + { + new WebInspector.ExtensionAuditCategoryResults(this, callback); + } +} + +/** + * @constructor + */ +WebInspector.ExtensionAuditCategoryResults = function(category, callback) +{ + this._category = category; + this._pendingRules = category.ruleCount; + this._ruleCompletionCallback = callback; + + this.id = category.id + "-" + ++WebInspector.ExtensionAuditCategoryResults._lastId; + WebInspector.extensionServer.startAuditRun(category, this); +} + +WebInspector.ExtensionAuditCategoryResults.prototype = { + get complete() + { + return !this._pendingRules; + }, + + cancel: function() + { + while (!this.complete) + this._addResult(null); + }, + + addResult: function(displayName, description, severity, details) + { + var result = new WebInspector.AuditRuleResult(displayName); + result.addChild(description); + result.severity = severity; + if (details) + this._addNode(result, details); + this._addResult(result); + }, + + _addNode: function(parent, node) + { + var addedNode = parent.addChild(node.contents, node.expanded); + if (node.children) { + for (var i = 0; i < node.children.length; ++i) + this._addNode(addedNode, node.children[i]); + } + }, + + _addResult: function(result) + { + this._ruleCompletionCallback(result); + this._pendingRules--; + if (!this._pendingRules) + WebInspector.extensionServer.stopAuditRun(this); + } +} + +WebInspector.ExtensionAuditCategoryResults._lastId = 0; +/* ExtensionServer.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + */ +WebInspector.ExtensionServer = function() +{ + this._clientObjects = {}; + this._handlers = {}; + this._subscribers = {}; + this._subscriptionStartHandlers = {}; + this._subscriptionStopHandlers = {}; + this._extraHeaders = {}; + this._requests = {}; + this._lastRequestId = 0; + this._registeredExtensions = {}; + this._status = new WebInspector.ExtensionStatus(); + + var commands = WebInspector.extensionAPI.Commands; + + this._registerHandler(commands.AddAuditCategory, this._onAddAuditCategory.bind(this)); + this._registerHandler(commands.AddAuditResult, this._onAddAuditResult.bind(this)); + this._registerHandler(commands.AddConsoleMessage, this._onAddConsoleMessage.bind(this)); + this._registerHandler(commands.AddRequestHeaders, this._onAddRequestHeaders.bind(this)); + this._registerHandler(commands.CreatePanel, this._onCreatePanel.bind(this)); + this._registerHandler(commands.CreateSidebarPane, this._onCreateSidebarPane.bind(this)); + this._registerHandler(commands.CreateStatusBarButton, this._onCreateStatusBarButton.bind(this)); + this._registerHandler(commands.EvaluateOnInspectedPage, this._onEvaluateOnInspectedPage.bind(this)); + this._registerHandler(commands.GetHAR, this._onGetHAR.bind(this)); + this._registerHandler(commands.GetConsoleMessages, this._onGetConsoleMessages.bind(this)); + this._registerHandler(commands.GetPageResources, this._onGetPageResources.bind(this)); + this._registerHandler(commands.GetRequestContent, this._onGetRequestContent.bind(this)); + this._registerHandler(commands.GetResourceContent, this._onGetResourceContent.bind(this)); + this._registerHandler(commands.Log, this._onLog.bind(this)); + this._registerHandler(commands.Reload, this._onReload.bind(this)); + this._registerHandler(commands.SetOpenResourceHandler, this._onSetOpenResourceHandler.bind(this)); + this._registerHandler(commands.SetResourceContent, this._onSetResourceContent.bind(this)); + this._registerHandler(commands.SetSidebarHeight, this._onSetSidebarHeight.bind(this)); + this._registerHandler(commands.SetSidebarContent, this._onSetSidebarContent.bind(this)); + this._registerHandler(commands.SetSidebarPage, this._onSetSidebarPage.bind(this)); + this._registerHandler(commands.StopAuditCategoryRun, this._onStopAuditCategoryRun.bind(this)); + this._registerHandler(commands.Subscribe, this._onSubscribe.bind(this)); + this._registerHandler(commands.Unsubscribe, this._onUnsubscribe.bind(this)); + this._registerHandler(commands.UpdateButton, this._onUpdateButton.bind(this)); + + window.addEventListener("message", this._onWindowMessage.bind(this), false); +} + +WebInspector.ExtensionServer.prototype = { + hasExtensions: function() + { + return !!Object.keys(this._registeredExtensions).length; + }, + + notifySearchAction: function(panelId, action, searchString) + { + this._postNotification(WebInspector.extensionAPI.Events.PanelSearch + panelId, action, searchString); + }, + + notifyViewShown: function(identifier, frameIndex) + { + this._postNotification(WebInspector.extensionAPI.Events.ViewShown + identifier, frameIndex); + }, + + notifyViewHidden: function(identifier) + { + this._postNotification(WebInspector.extensionAPI.Events.ViewHidden + identifier); + }, + + notifyButtonClicked: function(identifier) + { + this._postNotification(WebInspector.extensionAPI.Events.ButtonClicked + identifier); + }, + + _inspectedURLChanged: function(event) + { + this._requests = {}; + var url = event.data; + this._postNotification(WebInspector.extensionAPI.Events.InspectedURLChanged, url); + }, + + _mainFrameNavigated: function(event) + { + this._postNotification(WebInspector.extensionAPI.Events.Reset); + }, + + startAuditRun: function(category, auditRun) + { + this._clientObjects[auditRun.id] = auditRun; + this._postNotification("audit-started-" + category.id, auditRun.id); + }, + + stopAuditRun: function(auditRun) + { + delete this._clientObjects[auditRun.id]; + }, + + /** + * @param {...*} vararg + */ + _postNotification: function(type, vararg) + { + var subscribers = this._subscribers[type]; + if (!subscribers) + return; + var message = { + command: "notify-" + type, + arguments: Array.prototype.slice.call(arguments, 1) + }; + for (var i = 0; i < subscribers.length; ++i) + subscribers[i].postMessage(message); + }, + + _onSubscribe: function(message, port) + { + var subscribers = this._subscribers[message.type]; + if (subscribers) + subscribers.push(port); + else { + this._subscribers[message.type] = [ port ]; + if (this._subscriptionStartHandlers[message.type]) + this._subscriptionStartHandlers[message.type](); + } + }, + + _onUnsubscribe: function(message, port) + { + var subscribers = this._subscribers[message.type]; + if (!subscribers) + return; + subscribers.remove(port); + if (!subscribers.length) { + delete this._subscribers[message.type]; + if (this._subscriptionStopHandlers[message.type]) + this._subscriptionStopHandlers[message.type](); + } + }, + + _onAddRequestHeaders: function(message) + { + var id = message.extensionId; + if (typeof id !== "string") + return this._status.E_BADARGTYPE("extensionId", typeof id, "string"); + var extensionHeaders = this._extraHeaders[id]; + if (!extensionHeaders) { + extensionHeaders = {}; + this._extraHeaders[id] = extensionHeaders; + } + for (var name in message.headers) + extensionHeaders[name] = message.headers[name]; + var allHeaders = /** @type NetworkAgent.Headers */ {}; + for (var extension in this._extraHeaders) { + var headers = this._extraHeaders[extension]; + for (name in headers) { + if (typeof headers[name] === "string") + allHeaders[name] = headers[name]; + } + } + NetworkAgent.setExtraHTTPHeaders(allHeaders); + }, + + _onCreatePanel: function(message, port) + { + var id = message.id; + // The ids are generated on the client API side and must be unique, so the check below + // shouldn't be hit unless someone is bypassing the API. + if (id in this._clientObjects || id in WebInspector.panels) + return this._status.E_EXISTS(id); + + var page = this._expandResourcePath(port._extensionOrigin, message.page); + var icon = this._expandResourcePath(port._extensionOrigin, message.icon) + var panel = new WebInspector.ExtensionPanel(id, message.title, page, icon); + this._clientObjects[id] = panel; + WebInspector.panels[id] = panel; + WebInspector.addPanel(panel); + return this._status.OK(); + }, + + _onCreateStatusBarButton: function(message, port) + { + var panel = this._clientObjects[message.panel]; + if (!panel || !(panel instanceof WebInspector.ExtensionPanel)) + return this._status.E_NOTFOUND(message.panel); + var button = new WebInspector.ExtensionButton(message.id, this._expandResourcePath(port._extensionOrigin, message.icon), message.tooltip, message.disabled); + this._clientObjects[message.id] = button; + panel.addStatusBarItem(button.element); + return this._status.OK(); + }, + + _onUpdateButton: function(message, port) + { + var button = this._clientObjects[message.id]; + if (!button || !(button instanceof WebInspector.ExtensionButton)) + return this._status.E_NOTFOUND(message.id); + button.update(this._expandResourcePath(port._extensionOrigin, message.icon), message.tooltip, message.disabled); + return this._status.OK(); + }, + + _onCreateSidebarPane: function(message) + { + var panel = WebInspector.panels[message.panel]; + if (!panel) + return this._status.E_NOTFOUND(message.panel); + if (!panel.sidebarElement || !panel.sidebarPanes) + return this._status.E_NOTSUPPORTED(); + var id = message.id; + var sidebar = new WebInspector.ExtensionSidebarPane(message.title, message.id); + this._clientObjects[id] = sidebar; + panel.sidebarPanes[id] = sidebar; + panel.sidebarElement.appendChild(sidebar.element); + + return this._status.OK(); + }, + + _onSetSidebarHeight: function(message) + { + var sidebar = this._clientObjects[message.id]; + if (!sidebar) + return this._status.E_NOTFOUND(message.id); + sidebar.setHeight(message.height); + return this._status.OK(); + }, + + _onSetSidebarContent: function(message, port) + { + var sidebar = this._clientObjects[message.id]; + if (!sidebar) + return this._status.E_NOTFOUND(message.id); + function callback(error) + { + var result = error ? this._status.E_FAILED(error) : this._status.OK(); + this._dispatchCallback(message.requestId, port, result); + } + if (message.evaluateOnPage) + sidebar.setExpression(message.expression, message.rootTitle, callback.bind(this)); + else + sidebar.setObject(message.expression, message.rootTitle, callback.bind(this)); + }, + + _onSetSidebarPage: function(message, port) + { + var sidebar = this._clientObjects[message.id]; + if (!sidebar) + return this._status.E_NOTFOUND(message.id); + sidebar.setPage(this._expandResourcePath(port._extensionOrigin, message.page)); + }, + + _onSetOpenResourceHandler: function(message, port) + { + var name = this._registeredExtensions[port._extensionOrigin].name || ("Extension " + port._extensionOrigin); + if (message.handlerPresent) + WebInspector.openAnchorLocationRegistry.registerHandler(name, this._handleOpenURL.bind(this, port)); + else + WebInspector.openAnchorLocationRegistry.unregisterHandler(name); + }, + + _handleOpenURL: function(port, details) + { + var resource = WebInspector.resourceForURL(details.url); + if (!resource) + return false; + var lineNumber = details.lineNumber; + if (typeof lineNumber === "number") + lineNumber += 1; + port.postMessage({ + command: "open-resource", + resource: this._makeResource(resource), + lineNumber: lineNumber + }); + return true; + }, + + _onLog: function(message) + { + WebInspector.log(message.message); + }, + + _onReload: function(message) + { + var options = /** @type ExtensionReloadOptions */ (message.options || {}); + NetworkAgent.setUserAgentOverride(typeof options.userAgent === "string" ? options.userAgent : ""); + var injectedScript; + if (options.injectedScript) { + // Wrap client script into anonymous function, return another anonymous function that + // returns empty object for compatibility with InjectedScriptManager on the backend. + injectedScript = "((function(){" + options.injectedScript + "})(),function(){return {}})"; + } + PageAgent.reload(!!options.ignoreCache, injectedScript); + return this._status.OK(); + }, + + _onEvaluateOnInspectedPage: function(message, port) + { + function callback(error, resultPayload, wasThrown) + { + var result = {}; + if (error) { + result.isException = true; + result.value = error.message; + } else + result.value = resultPayload.value; + + if (wasThrown) + result.isException = true; + + this._dispatchCallback(message.requestId, port, result); + } + RuntimeAgent.evaluate(message.expression, "", true, undefined, undefined, true, callback.bind(this)); + }, + + _onGetConsoleMessages: function() + { + return WebInspector.console.messages.map(this._makeConsoleMessage); + }, + + _onAddConsoleMessage: function(message) + { + function convertSeverity(level) + { + switch (level) { + case WebInspector.extensionAPI.console.Severity.Tip: + return WebInspector.ConsoleMessage.MessageLevel.Tip; + case WebInspector.extensionAPI.console.Severity.Log: + return WebInspector.ConsoleMessage.MessageLevel.Log; + case WebInspector.extensionAPI.console.Severity.Warning: + return WebInspector.ConsoleMessage.MessageLevel.Warning; + case WebInspector.extensionAPI.console.Severity.Error: + return WebInspector.ConsoleMessage.MessageLevel.Error; + case WebInspector.extensionAPI.console.Severity.Debug: + return WebInspector.ConsoleMessage.MessageLevel.Debug; + } + } + var level = convertSeverity(message.severity); + if (!level) + return this._status.E_BADARG("message.severity", message.severity); + + var consoleMessage = WebInspector.ConsoleMessage.create( + WebInspector.ConsoleMessage.MessageSource.JS, + level, + message.text, + WebInspector.ConsoleMessage.MessageType.Log, + message.url, + message.line); + WebInspector.console.addMessage(consoleMessage); + }, + + _makeConsoleMessage: function(message) + { + function convertLevel(level) + { + if (!level) + return; + switch (level) { + case WebInspector.ConsoleMessage.MessageLevel.Tip: + return WebInspector.extensionAPI.console.Severity.Tip; + case WebInspector.ConsoleMessage.MessageLevel.Log: + return WebInspector.extensionAPI.console.Severity.Log; + case WebInspector.ConsoleMessage.MessageLevel.Warning: + return WebInspector.extensionAPI.console.Severity.Warning; + case WebInspector.ConsoleMessage.MessageLevel.Error: + return WebInspector.extensionAPI.console.Severity.Error; + case WebInspector.ConsoleMessage.MessageLevel.Debug: + return WebInspector.extensionAPI.console.Severity.Debug; + default: + return WebInspector.extensionAPI.console.Severity.Log; + } + } + var result = { + severity: convertLevel(message.level), + text: message.text, + }; + if (message.url) + result.url = message.url; + if (message.line) + result.line = message.line; + return result; + }, + + _onGetHAR: function() + { + var requests = WebInspector.networkLog.resources; + var harLog = (new WebInspector.HARLog(requests)).build(); + for (var i = 0; i < harLog.entries.length; ++i) + harLog.entries[i]._requestId = this._requestId(requests[i]); + return harLog; + }, + + _makeResource: function(resource) + { + return { + url: resource.url, + type: WebInspector.Resource.Type.toString(resource.type) + }; + }, + + _onGetPageResources: function() + { + var resources = []; + function pushResourceData(resource) + { + resources.push(this._makeResource(resource)); + } + WebInspector.resourceTreeModel.forAllResources(pushResourceData.bind(this)); + return resources; + }, + + _getResourceContent: function(resource, message, port) + { + function onContentAvailable(content, encoded) + { + var response = { + encoding: encoded ? "base64" : "", + content: content + }; + this._dispatchCallback(message.requestId, port, response); + } + resource.requestContent(onContentAvailable.bind(this)); + }, + + _onGetRequestContent: function(message, port) + { + var request = this._requestById(message.id); + if (!request) + return this._status.E_NOTFOUND(message.id); + this._getResourceContent(request, message, port); + }, + + _onGetResourceContent: function(message, port) + { + var resource = WebInspector.resourceTreeModel.resourceForURL(message.url); + if (!resource) + return this._status.E_NOTFOUND(message.url); + this._getResourceContent(resource, message, port); + }, + + _onSetResourceContent: function(message, port) + { + function callbackWrapper(error) + { + var response = error ? this._status.E_FAILED(error) : this._status.OK(); + this._dispatchCallback(message.requestId, port, response); + } + var resource = WebInspector.resourceTreeModel.resourceForURL(message.url); + if (!resource) + return this._status.E_NOTFOUND(message.url); + resource.setContent(message.content, message.commit, callbackWrapper.bind(this)); + }, + + _requestId: function(request) + { + if (!request._extensionRequestId) { + request._extensionRequestId = ++this._lastRequestId; + this._requests[request._extensionRequestId] = request; + } + return request._extensionRequestId; + }, + + _requestById: function(id) + { + return this._requests[id]; + }, + + _onAddAuditCategory: function(message) + { + var category = new WebInspector.ExtensionAuditCategory(message.id, message.displayName, message.resultCount); + if (WebInspector.panels.audits.getCategory(category.id)) + return this._status.E_EXISTS(category.id); + this._clientObjects[message.id] = category; + WebInspector.panels.audits.addCategory(category); + }, + + _onAddAuditResult: function(message) + { + var auditResult = this._clientObjects[message.resultId]; + if (!auditResult) + return this._status.E_NOTFOUND(message.resultId); + try { + auditResult.addResult(message.displayName, message.description, message.severity, message.details); + } catch (e) { + return e; + } + return this._status.OK(); + }, + + _onStopAuditCategoryRun: function(message) + { + var auditRun = this._clientObjects[message.resultId]; + if (!auditRun) + return this._status.E_NOTFOUND(message.resultId); + auditRun.cancel(); + }, + + _dispatchCallback: function(requestId, port, result) + { + if (requestId) + port.postMessage({ command: "callback", requestId: requestId, result: result }); + }, + + initExtensions: function() + { + this._registerAutosubscriptionHandler(WebInspector.extensionAPI.Events.ConsoleMessageAdded, + WebInspector.console, WebInspector.ConsoleModel.Events.MessageAdded, this._notifyConsoleMessageAdded); + this._registerAutosubscriptionHandler(WebInspector.extensionAPI.Events.NetworkRequestFinished, + WebInspector.networkManager, WebInspector.NetworkManager.EventTypes.ResourceFinished, this._notifyRequestFinished); + this._registerAutosubscriptionHandler(WebInspector.extensionAPI.Events.ResourceAdded, + WebInspector.resourceTreeModel, + WebInspector.ResourceTreeModel.EventTypes.ResourceAdded, + this._notifyResourceAdded); + if (WebInspector.panels.elements) { + this._registerAutosubscriptionHandler(WebInspector.extensionAPI.Events.ElementsPanelObjectSelected, + WebInspector.panels.elements.treeOutline, + WebInspector.ElementsTreeOutline.Events.SelectedNodeChanged, + this._notifyElementsSelectionChanged); + } + this._registerAutosubscriptionHandler(WebInspector.extensionAPI.Events.ResourceContentCommitted, + WebInspector.resourceTreeModel, + WebInspector.ResourceTreeModel.EventTypes.ResourceContentCommitted, + this._notifyResourceContentCommitted); + + function onTimelineSubscriptionStarted() + { + WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.EventTypes.TimelineEventRecorded, + this._notifyTimelineEventRecorded, this); + WebInspector.timelineManager.start(); + } + function onTimelineSubscriptionStopped() + { + WebInspector.timelineManager.stop(); + WebInspector.timelineManager.removeEventListener(WebInspector.TimelineManager.EventTypes.TimelineEventRecorded, + this._notifyTimelineEventRecorded, this); + } + this._registerSubscriptionHandler(WebInspector.extensionAPI.Events.TimelineEventRecorded, + onTimelineSubscriptionStarted.bind(this), onTimelineSubscriptionStopped.bind(this)); + + WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.InspectedURLChanged, + this._inspectedURLChanged, this); + WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.MainFrameNavigated, this._mainFrameNavigated, this); + InspectorExtensionRegistry.getExtensionsAsync(); + }, + + _notifyConsoleMessageAdded: function(event) + { + this._postNotification(WebInspector.extensionAPI.Events.ConsoleMessageAdded, this._makeConsoleMessage(event.data)); + }, + + _notifyResourceAdded: function(event) + { + var resource = event.data; + this._postNotification(WebInspector.extensionAPI.Events.ResourceAdded, this._makeResource(resource)); + }, + + _notifyResourceContentCommitted: function(event) + { + this._postNotification(WebInspector.extensionAPI.Events.ResourceContentCommitted, this._makeResource(event.data.resource), event.data.content); + }, + + _notifyRequestFinished: function(event) + { + var request = event.data; + this._postNotification(WebInspector.extensionAPI.Events.NetworkRequestFinished, this._requestId(request), (new WebInspector.HAREntry(request)).build()); + }, + + _notifyElementsSelectionChanged: function() + { + this._postNotification(WebInspector.extensionAPI.Events.ElementsPanelObjectSelected); + }, + + _notifyTimelineEventRecorded: function(event) + { + this._postNotification(WebInspector.extensionAPI.Events.TimelineEventRecorded, event.data); + }, + + /** + * @param {Array.} extensions + */ + _addExtensions: function(extensions) + { + for (var i = 0; i < extensions.length; ++i) + this._addExtension(extensions[i]); + }, + + _addExtension: function(extensionInfo) + { + const urlOriginRegExp = new RegExp("([^:]+:\/\/[^/]*)\/"); // Can't use regexp literal here, MinJS chokes on it. + var startPage = extensionInfo.startPage; + var name = extensionInfo.name; + + try { + var originMatch = urlOriginRegExp.exec(startPage); + if (!originMatch) { + console.error("Skipping extension with invalid URL: " + startPage); + return false; + } + var extensionOrigin = originMatch[1]; + if (!this._registeredExtensions[extensionOrigin]) { + // See ExtensionAPI.js and ExtensionCommon.js for details. + InspectorFrontendHost.setInjectedScriptForOrigin(extensionOrigin, buildExtensionAPIInjectedScript(extensionInfo)); + this._registeredExtensions[extensionOrigin] = { name: name }; + } + var iframe = document.createElement("iframe"); + iframe.src = startPage; + iframe.style.display = "none"; + document.body.appendChild(iframe); + } catch (e) { + console.error("Failed to initialize extension " + startPage + ":" + e); + return false; + } + return true; + }, + + _onWindowMessage: function(event) + { + if (event.data === "registerExtension") + this._registerExtension(event.origin, event.ports[0]); + }, + + _registerExtension: function(origin, port) + { + if (!this._registeredExtensions.hasOwnProperty(origin)) { + if (origin !== window.location.origin) // Just ignore inspector frames. + console.error("Ignoring unauthorized client request from " + origin); + return; + } + port._extensionOrigin = origin; + port.addEventListener("message", this._onmessage.bind(this), false); + port.start(); + }, + + _onmessage: function(event) + { + var message = event.data; + var result; + + if (message.command in this._handlers) + result = this._handlers[message.command](message, event.target); + else + result = this._status.E_NOTSUPPORTED(message.command); + + if (result && message.requestId) + this._dispatchCallback(message.requestId, event.target, result); + }, + + _registerHandler: function(command, callback) + { + this._handlers[command] = callback; + }, + + _registerSubscriptionHandler: function(eventTopic, onSubscribeFirst, onUnsubscribeLast) + { + this._subscriptionStartHandlers[eventTopic] = onSubscribeFirst; + this._subscriptionStopHandlers[eventTopic] = onUnsubscribeLast; + }, + + _registerAutosubscriptionHandler: function(eventTopic, eventTarget, frontendEventType, handler) + { + this._registerSubscriptionHandler(eventTopic, + eventTarget.addEventListener.bind(eventTarget, frontendEventType, handler, this), + eventTarget.removeEventListener.bind(eventTarget, frontendEventType, handler, this)); + }, + + _expandResourcePath: function(extensionPath, resourcePath) + { + if (!resourcePath) + return; + return extensionPath + this._normalizePath(resourcePath); + }, + + _normalizePath: function(path) + { + var source = path.split("/"); + var result = []; + + for (var i = 0; i < source.length; ++i) { + if (source[i] === ".") + continue; + // Ignore empty path components resulting from //, as well as a leading and traling slashes. + if (source[i] === "") + continue; + if (source[i] === "..") + result.pop(); + else + result.push(source[i]); + } + return "/" + result.join("/"); + } +} + +/** + * @constructor + */ +WebInspector.ExtensionStatus = function() +{ + function makeStatus(code, description) + { + var details = Array.prototype.slice.call(arguments, 2); + var status = { code: code, description: description, details: details }; + if (code !== "OK") { + status.isError = true; + console.log("Extension server error: " + String.vsprintf(description, details)); + } + return status; + } + + this.OK = makeStatus.bind(null, "OK", "OK"); + this.E_EXISTS = makeStatus.bind(null, "E_EXISTS", "Object already exists: %s"); + this.E_BADARG = makeStatus.bind(null, "E_BADARG", "Invalid argument %s: %s"); + this.E_BADARGTYPE = makeStatus.bind(null, "E_BADARGTYPE", "Invalid type for argument %s: got %s, expected %s"); + this.E_NOTFOUND = makeStatus.bind(null, "E_NOTFOUND", "Object not found: %s"); + this.E_NOTSUPPORTED = makeStatus.bind(null, "E_NOTSUPPORTED", "Object does not support requested operation: %s"); + this.E_FAILED = makeStatus.bind(null, "E_FAILED", "Operation failed: %s"); +} + +WebInspector.addExtensions = function(extensions) +{ + WebInspector.extensionServer._addExtensions(extensions); +} + +WebInspector.extensionAPI = {}; +defineCommonExtensionSymbols(WebInspector.extensionAPI); + +WebInspector.extensionServer = new WebInspector.ExtensionServer(); + +window.addExtension = function(page, name) +{ + WebInspector.extensionServer._addExtension({ + startPage: page, + name: name, + }); +} +/* ExtensionPanel.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.View} + * @param {string} id + * @param {Element} parent + * @param {string} src + * @param {string} className + */ +WebInspector.ExtensionView = function(id, parent, src, className) +{ + WebInspector.View.call(this); + + this._id = id; + this._iframe = document.createElement("iframe"); + this._iframe.addEventListener("load", this._onLoad.bind(this), false); + this._iframe.src = src; + this._iframe.className = className; + + this.element.appendChild(this._iframe); + this.show(parent); +} + +WebInspector.ExtensionView.prototype = { + wasShown: function() + { + if (typeof this._frameIndex === "number") + WebInspector.extensionServer.notifyViewShown(this._id, this._frameIndex); + }, + + willHide: function() + { + if (typeof this._frameIndex === "number") + WebInspector.extensionServer.notifyViewHidden(this._id); + }, + + _onLoad: function() + { + this._frameIndex = Array.prototype.indexOf.call(window.frames, this._iframe.contentWindow); + if (this.isShowing()) + WebInspector.extensionServer.notifyViewShown(this._id, this._frameIndex); + } +} + +WebInspector.ExtensionView.prototype.__proto__ = WebInspector.View.prototype; + +/** + * @constructor + * @extends {WebInspector.View} + * @param {string} id + */ +WebInspector.ExtensionNotifierView = function(id) +{ + WebInspector.View.call(this); + + this._id = id; +} + +WebInspector.ExtensionNotifierView.prototype = { + wasShown: function() + { + WebInspector.extensionServer.notifyViewShown(this._id); + }, + + willHide: function() + { + WebInspector.extensionServer.notifyViewHidden(this._id); + } +} + +WebInspector.ExtensionNotifierView.prototype.__proto__ = WebInspector.View.prototype; + +/** + * @constructor + * @extends {WebInspector.Panel} + * @param {string} id + * @param {string} label + * @param {string} iconURL + */ +WebInspector.ExtensionPanel = function(id, label, pageURL, iconURL) +{ + WebInspector.Panel.call(this, id); + this.setHideOnDetach(); + this._toolbarItemLabel = label; + this._statusBarItems = []; + + if (iconURL) { + this._addStyleRule(".toolbar-item." + id + " .toolbar-icon", "background-image: url(" + iconURL + ");"); + this._addStyleRule(".toolbar-small .toolbar-item." + id + " .toolbar-icon", "background-position-x: -32px;"); + } + new WebInspector.ExtensionView(id, this.element, pageURL, "extension panel"); +} + +WebInspector.ExtensionPanel.prototype = { + get toolbarItemLabel() + { + return this._toolbarItemLabel; + }, + + get defaultFocusedElement() + { + return this.sidebarTreeElement || this.element; + }, + + get statusBarItems() + { + return this._statusBarItems; + }, + + /** + * @param {Element} element + */ + addStatusBarItem: function(element) + { + this._statusBarItems.push(element); + }, + + searchCanceled: function(startingNewSearch) + { + WebInspector.extensionServer.notifySearchAction(this._id, "cancelSearch"); + WebInspector.Panel.prototype.searchCanceled.apply(this, arguments); + }, + + performSearch: function(query) + { + WebInspector.extensionServer.notifySearchAction(this._id, "performSearch", query); + WebInspector.Panel.prototype.performSearch.apply(this, arguments); + }, + + jumpToNextSearchResult: function() + { + WebInspector.extensionServer.notifySearchAction(this._id, "nextSearchResult"); + WebInspector.Panel.prototype.jumpToNextSearchResult.call(this); + }, + + jumpToPreviousSearchResult: function() + { + WebInspector.extensionServer.notifySearchAction(this._id, "previousSearchResult"); + WebInspector.Panel.prototype.jumpToPreviousSearchResult.call(this); + }, + + _addStyleRule: function(selector, body) + { + var style = document.createElement("style"); + style.textContent = selector + " { " + body + " }"; + document.head.appendChild(style); + } +} + +WebInspector.ExtensionPanel.prototype.__proto__ = WebInspector.Panel.prototype; + +/** + * @constructor + * @param {string} id + * @param {string} iconURL + * @param {string=} tooltip + * @param {boolean=} disabled + */ +WebInspector.ExtensionButton = function(id, iconURL, tooltip, disabled) +{ + this._id = id; + this.element = document.createElement("button"); + this.element.className = "status-bar-item extension"; + this.element.addEventListener("click", this._onClicked.bind(this), false); + this.update(iconURL, tooltip, disabled); +} + +WebInspector.ExtensionButton.prototype = { + /** + * @param {string} iconURL + * @param {string=} tooltip + * @param {boolean=} disabled + */ + update: function(iconURL, tooltip, disabled) + { + if (typeof iconURL === "string") + this.element.style.backgroundImage = "url(" + iconURL + ")"; + if (typeof tooltip === "string") + this.element.title = tooltip; + if (typeof disabled === "boolean") + this.element.disabled = disabled; + }, + + _onClicked: function() + { + WebInspector.extensionServer.notifyButtonClicked(this._id); + } +} + +/** + * @constructor + * @extends {WebInspector.SidebarPane} + * @param {string} title + * @param {string} id + */ +WebInspector.ExtensionSidebarPane = function(title, id) +{ + WebInspector.SidebarPane.call(this, title); + this._id = id; +} + +WebInspector.ExtensionSidebarPane.prototype = { + /** + * @param {Object} object + * @param {string} title + * @param {function(?string=)} callback + */ + setObject: function(object, title, callback) + { + this._createObjectPropertiesView(); + this._setObject(WebInspector.RemoteObject.fromLocalObject(object), title, callback); + }, + + /** + * @param {string} expression + * @param {string} title + * @param {function(?string=)} callback + */ + setExpression: function(expression, title, callback) + { + this._createObjectPropertiesView(); + RuntimeAgent.evaluate(expression, "extension-watch", true, undefined, undefined, undefined, this._onEvaluate.bind(this, title, callback)); + }, + + /** + * @param {string} url + */ + setPage: function(url) + { + if (this._objectPropertiesView) { + this._objectPropertiesView.detach(); + delete this._objectPropertiesView; + } + if (this._extensionView) + this._extensionView.detach(true); + + this._extensionView = new WebInspector.ExtensionView(this._id, this.bodyElement, url, "extension"); + }, + + /** + * @param {string} height + */ + setHeight: function(height) + { + this.bodyElement.style.height = height; + }, + + /** + * @param {string} title + * @param {function(?string=)} callback + * @param {?Protocol.Error} error + * @param {RuntimeAgent.RemoteObject} result + * @param {boolean=} wasThrown + */ + _onEvaluate: function(title, callback, error, result, wasThrown) + { + if (error) + callback(error.toString()); + else + this._setObject(WebInspector.RemoteObject.fromPayload(result), title, callback); + }, + + _createObjectPropertiesView: function() + { + if (this._objectPropertiesView) + return; + if (this._extensionView) { + this._extensionView.detach(true); + delete this._extensionView; + } + this._objectPropertiesView = new WebInspector.ExtensionNotifierView(this._id); + this._objectPropertiesView.show(this.bodyElement); + }, + + /** + * @param {Object} object + * @param {string} title + * @param {function(?string=)} callback + */ + _setObject: function(object, title, callback) + { + // This may only happen if setPage() was called while we were evaluating the expression. + if (!this._objectPropertiesView) { + callback("operation cancelled"); + return; + } + this._objectPropertiesView.element.removeChildren(); + var section = new WebInspector.ObjectPropertiesSection(object, title); + if (!title) + section.headerElement.addStyleClass("hidden"); + section.expanded = true; + section.editable = false; + this._objectPropertiesView.element.appendChild(section.element); + callback(); + } +} + +WebInspector.ExtensionSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype; +/* AuditsPanel.js */ + +/* + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.Panel} + */ +WebInspector.AuditsPanel = function() +{ + WebInspector.Panel.call(this, "audits"); + this.registerRequiredCSS("panelEnablerView.css"); + this.registerRequiredCSS("auditsPanel.css"); + + this.createSplitViewWithSidebarTree(); + this.auditsTreeElement = new WebInspector.SidebarSectionTreeElement("", {}, true); + this.sidebarTree.appendChild(this.auditsTreeElement); + this.auditsTreeElement.listItemElement.addStyleClass("hidden"); + + this.auditsItemTreeElement = new WebInspector.AuditsSidebarTreeElement(); + this.auditsTreeElement.appendChild(this.auditsItemTreeElement); + + this.auditResultsTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("RESULTS"), {}, true); + this.sidebarTree.appendChild(this.auditResultsTreeElement); + this.auditResultsTreeElement.expand(); + + this.clearResultsButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear audit results."), "clear-status-bar-item"); + this.clearResultsButton.addEventListener("click", this._clearButtonClicked, this); + + this.viewsContainerElement = this.splitView.mainElement; + + this._constructCategories(); + + this._launcherView = new WebInspector.AuditLauncherView(this.initiateAudit.bind(this), this.terminateAudit.bind(this)); + for (var id in this.categoriesById) + this._launcherView.addCategory(this.categoriesById[id]); + + WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.OnLoad, this._didMainResourceLoad, this); +} + +WebInspector.AuditsPanel.prototype = { + get toolbarItemLabel() + { + return WebInspector.UIString("Audits"); + }, + + get statusBarItems() + { + return [this.clearResultsButton.element]; + }, + + get categoriesById() + { + return this._auditCategoriesById; + }, + + addCategory: function(category) + { + this.categoriesById[category.id] = category; + this._launcherView.addCategory(category); + }, + + getCategory: function(id) + { + return this.categoriesById[id]; + }, + + _constructCategories: function() + { + this._auditCategoriesById = {}; + for (var categoryCtorID in WebInspector.AuditCategories) { + var auditCategory = new WebInspector.AuditCategories[categoryCtorID](); + auditCategory._id = categoryCtorID; + this.categoriesById[categoryCtorID] = auditCategory; + } + }, + + _executeAudit: function(categories, resultCallback) + { + var resources = WebInspector.networkLog.resources; + + var rulesRemaining = 0; + for (var i = 0; i < categories.length; ++i) + rulesRemaining += categories[i].ruleCount; + + this._progressMonitor.setTotalWork(rulesRemaining); + + var results = []; + var mainResourceURL = WebInspector.inspectedPageURL; + + function ruleResultReadyCallback(categoryResult, ruleResult) + { + if (this._progressMonitor.canceled) + return; + + if (ruleResult && ruleResult.children) + categoryResult.addRuleResult(ruleResult); + + --rulesRemaining; + this._progressMonitor.worked(1); + + if (this._progressMonitor.done() && resultCallback) + resultCallback(mainResourceURL, results); + } + + if (this._progressMonitor.done()) { + resultCallback(mainResourceURL, results); + return; + } + + for (var i = 0; i < categories.length; ++i) { + var category = categories[i]; + var result = new WebInspector.AuditCategoryResult(category); + results.push(result); + category.run(resources, ruleResultReadyCallback.bind(this, result), this._progressMonitor); + } + }, + + _auditFinishedCallback: function(launcherCallback, mainResourceURL, results) + { + var children = this.auditResultsTreeElement.children; + var ordinal = 1; + for (var i = 0; i < children.length; ++i) { + if (children[i].mainResourceURL === mainResourceURL) + ordinal++; + } + + var resultTreeElement = new WebInspector.AuditResultSidebarTreeElement(results, mainResourceURL, ordinal); + this.auditResultsTreeElement.appendChild(resultTreeElement); + resultTreeElement.revealAndSelect(); + if (!this._progressMonitor.canceled && launcherCallback) + launcherCallback(); + }, + + initiateAudit: function(categoryIds, progressElement, runImmediately, launcherCallback) + { + if (!categoryIds || !categoryIds.length) + return; + + this._progressMonitor = new WebInspector.AuditProgressMonitor(progressElement); + + var categories = []; + for (var i = 0; i < categoryIds.length; ++i) + categories.push(this.categoriesById[categoryIds[i]]); + + function initiateAuditCallback(categories, launcherCallback) + { + this._executeAudit(categories, this._auditFinishedCallback.bind(this, launcherCallback)); + } + + if (runImmediately) + initiateAuditCallback.call(this, categories, launcherCallback); + else + this._reloadResources(initiateAuditCallback.bind(this, categories, launcherCallback)); + + WebInspector.userMetrics.AuditsStarted.record(); + }, + + terminateAudit: function(launcherCallback) + { + this._progressMonitor.canceled = true; + launcherCallback(); + }, + + _reloadResources: function(callback) + { + this._pageReloadCallback = callback; + PageAgent.reload(false); + }, + + _didMainResourceLoad: function() + { + if (this._pageReloadCallback) { + var callback = this._pageReloadCallback; + delete this._pageReloadCallback; + callback(); + } + }, + + showResults: function(categoryResults) + { + if (!categoryResults._resultView) + categoryResults._resultView = new WebInspector.AuditResultView(categoryResults); + + this.visibleView = categoryResults._resultView; + }, + + showLauncherView: function() + { + this.visibleView = this._launcherView; + }, + + get visibleView() + { + return this._visibleView; + }, + + set visibleView(x) + { + if (this._visibleView === x) + return; + + if (this._visibleView) + this._visibleView.detach(); + + this._visibleView = x; + + if (x) + x.show(this.viewsContainerElement); + }, + + wasShown: function() + { + WebInspector.Panel.prototype.wasShown.call(this); + if (!this._visibleView) + this.auditsItemTreeElement.select(); + }, + + _clearButtonClicked: function() + { + this.auditsItemTreeElement.revealAndSelect(); + this.auditResultsTreeElement.removeChildren(); + } +} + +WebInspector.AuditsPanel.prototype.__proto__ = WebInspector.Panel.prototype; + +/** + * @constructor + */ +WebInspector.AuditCategory = function(displayName) +{ + this._displayName = displayName; + this._rules = []; +} + +WebInspector.AuditCategory.prototype = { + get id() + { + // this._id value is injected at construction time. + return this._id; + }, + + get displayName() + { + return this._displayName; + }, + + get ruleCount() + { + this._ensureInitialized(); + return this._rules.length; + }, + + addRule: function(rule, severity) + { + rule.severity = severity; + this._rules.push(rule); + }, + + run: function(resources, callback, progressMonitor) + { + this._ensureInitialized(); + for (var i = 0; i < this._rules.length; ++i) + this._rules[i].run(resources, callback, progressMonitor); + }, + + _ensureInitialized: function() + { + if (!this._initialized) { + if ("initialize" in this) + this.initialize(); + this._initialized = true; + } + } +} + +/** + * @constructor + */ +WebInspector.AuditRule = function(id, displayName) +{ + this._id = id; + this._displayName = displayName; +} + +WebInspector.AuditRule.Severity = { + Info: "info", + Warning: "warning", + Severe: "severe" +} + +WebInspector.AuditRule.SeverityOrder = { + "info": 3, + "warning": 2, + "severe": 1 +} + +WebInspector.AuditRule.prototype = { + get id() + { + return this._id; + }, + + get displayName() + { + return this._displayName; + }, + + set severity(severity) + { + this._severity = severity; + }, + + run: function(resources, callback, progressMonitor) + { + if (progressMonitor.canceled) + return; + + var result = new WebInspector.AuditRuleResult(this.displayName); + result.severity = this._severity; + this.doRun(resources, result, callback, progressMonitor); + }, + + doRun: function(resources, result, callback, progressMonitor) + { + throw new Error("doRun() not implemented"); + } +} + +/** + * @constructor + */ +WebInspector.AuditCategoryResult = function(category) +{ + this.title = category.displayName; + this.ruleResults = []; +} + +WebInspector.AuditCategoryResult.prototype = { + addRuleResult: function(ruleResult) + { + this.ruleResults.push(ruleResult); + } +} + +/** + * @constructor + * @param {boolean=} expanded + * @param {string=} className + */ +WebInspector.AuditRuleResult = function(value, expanded, className) +{ + this.value = value; + this.className = className; + this.expanded = expanded; + this.violationCount = 0; + this._formatters = { + r: WebInspector.AuditRuleResult.linkifyDisplayName + }; + var standardFormatters = Object.keys(String.standardFormatters); + for (var i = 0; i < standardFormatters.length; ++i) + this._formatters[standardFormatters[i]] = String.standardFormatters[standardFormatters[i]]; +} + +WebInspector.AuditRuleResult.linkifyDisplayName = function(url) +{ + return WebInspector.linkifyURLAsNode(url, WebInspector.displayNameForURL(url)); +} + +WebInspector.AuditRuleResult.resourceDomain = function(domain) +{ + return domain || WebInspector.UIString("[empty domain]"); +} + +WebInspector.AuditRuleResult.prototype = { + /** + * @param {boolean=} expanded + * @param {string=} className + */ + addChild: function(value, expanded, className) + { + if (!this.children) + this.children = []; + var entry = new WebInspector.AuditRuleResult(value, expanded, className); + this.children.push(entry); + return entry; + }, + + addURL: function(url) + { + return this.addChild(WebInspector.AuditRuleResult.linkifyDisplayName(url)); + }, + + addURLs: function(urls) + { + for (var i = 0; i < urls.length; ++i) + this.addURL(urls[i]); + }, + + addSnippet: function(snippet) + { + return this.addChild(snippet, false, "source-code"); + }, + + /** + * @param {string} format + * @param {...*} vararg + */ + addFormatted: function(format, vararg) + { + var substitutions = Array.prototype.slice.call(arguments, 1); + var fragment = document.createDocumentFragment(); + + var formattedResult = String.format(format, substitutions, this._formatters, fragment, this._append).formattedResult; + if (formattedResult instanceof Node) + formattedResult.normalize(); + return this.addChild(formattedResult); + }, + + _append: function(a, b) + { + if (!(b instanceof Node)) + b = document.createTextNode(b); + a.appendChild(b); + return a; + } +} + +/** + * @constructor + * @param {Element} progressElement + */ +WebInspector.AuditProgressMonitor = function(progressElement) +{ + this._element = progressElement; + this.setTotalWork(WebInspector.AuditProgressMonitor.INDETERMINATE); +} + +WebInspector.AuditProgressMonitor.INDETERMINATE = -1; + +WebInspector.AuditProgressMonitor.prototype = { + setTotalWork: function(total) + { + if (this.canceled || this._total === total) + return; + this._total = total; + this._value = 0; + this._element.max = total; + if (total === WebInspector.AuditProgressMonitor.INDETERMINATE) + this._element.removeAttribute("value"); + else + this._element.value = 0; + }, + + worked: function(items) + { + if (this.canceled || this.indeterminate || this.done()) + return; + this._value += items; + if (this._value > this._total) + this._value = this._total; + this._element.value = this._value; + }, + + get indeterminate() + { + return this._total === WebInspector.AuditProgressMonitor.INDETERMINATE; + }, + + done: function() + { + return !this.indeterminate && (this.canceled || this._value === this._total); + }, + + get canceled() + { + return !!this._canceled; + }, + + set canceled(x) + { + if (this._canceled === x) + return; + if (x) + this.setTotalWork(WebInspector.AuditProgressMonitor.INDETERMINATE); + this._canceled = x; + } +} + +/** + * @constructor + * @extends {WebInspector.SidebarTreeElement} + */ +WebInspector.AuditsSidebarTreeElement = function() +{ + this.small = false; + + WebInspector.SidebarTreeElement.call(this, "audits-sidebar-tree-item", WebInspector.UIString("Audits"), "", null, false); +} + +WebInspector.AuditsSidebarTreeElement.prototype = { + onattach: function() + { + WebInspector.SidebarTreeElement.prototype.onattach.call(this); + }, + + onselect: function() + { + WebInspector.panels.audits.showLauncherView(); + }, + + get selectable() + { + return true; + }, + + refresh: function() + { + this.refreshTitles(); + } +} + +WebInspector.AuditsSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype; + +/** + * @constructor + * @extends {WebInspector.SidebarTreeElement} + */ +WebInspector.AuditResultSidebarTreeElement = function(results, mainResourceURL, ordinal) +{ + this.results = results; + this.mainResourceURL = mainResourceURL; + + WebInspector.SidebarTreeElement.call(this, "audit-result-sidebar-tree-item", String.sprintf("%s (%d)", mainResourceURL, ordinal), "", {}, false); +} + +WebInspector.AuditResultSidebarTreeElement.prototype = { + onselect: function() + { + WebInspector.panels.audits.showResults(this.results); + }, + + get selectable() + { + return true; + } +} + +WebInspector.AuditResultSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype; + +// Contributed audit rules should go into this namespace. +WebInspector.AuditRules = {}; + +// Contributed audit categories should go into this namespace. +WebInspector.AuditCategories = {}; +/* AuditResultView.js */ + +/* + * Copyright (C) 2009 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.View} + */ +WebInspector.AuditResultView = function(categoryResults) +{ + WebInspector.View.call(this); + this.element.className = "audit-result-view"; + + function categorySorter(a, b) { + return (a.title || "").localeCompare(b.title || ""); + } + categoryResults.sort(categorySorter); + for (var i = 0; i < categoryResults.length; ++i) + this.element.appendChild(new WebInspector.AuditCategoryResultPane(categoryResults[i]).element); + + this.element.addEventListener("contextmenu", this._contextMenuEventFired.bind(this), true); +} + +WebInspector.AuditResultView.prototype = { + _contextMenuEventFired: function(event) + { + var contextMenu = new WebInspector.ContextMenu(); + if (WebInspector.populateHrefContextMenu(contextMenu, null, event)) + contextMenu.show(event); + } +} + +WebInspector.AuditResultView.prototype.__proto__ = WebInspector.View.prototype; + +/** + * @constructor + * @extends {WebInspector.SidebarPane} + */ +WebInspector.AuditCategoryResultPane = function(categoryResult) +{ + WebInspector.SidebarPane.call(this, categoryResult.title); + var treeOutlineElement = document.createElement("ol"); + this.bodyElement.addStyleClass("audit-result-tree"); + this.bodyElement.appendChild(treeOutlineElement); + + this._treeOutline = new TreeOutline(treeOutlineElement); + this._treeOutline.expandTreeElementsWhenArrowing = true; + + function ruleSorter(a, b) + { + var result = WebInspector.AuditRule.SeverityOrder[a.severity || 0] - WebInspector.AuditRule.SeverityOrder[b.severity || 0]; + if (!result) + result = (a.value || "").localeCompare(b.value || ""); + return result; + } + + categoryResult.ruleResults.sort(ruleSorter); + + for (var i = 0; i < categoryResult.ruleResults.length; ++i) { + var ruleResult = categoryResult.ruleResults[i]; + var treeElement = this._appendResult(this._treeOutline, ruleResult); + treeElement.listItemElement.addStyleClass("audit-result"); + + if (ruleResult.severity) { + var severityElement = document.createElement("img"); + severityElement.className = "severity-" + ruleResult.severity; + treeElement.listItemElement.appendChild(severityElement); + } + } + this.expand(); +} + +WebInspector.AuditCategoryResultPane.prototype = { + _appendResult: function(parentTreeElement, result) + { + var title = ""; + + if (typeof result.value === "string") { + title = result.value; + if (result.violationCount) + title = String.sprintf("%s (%d)", title, result.violationCount); + } + + var treeElement = new TreeElement(null, null, !!result.children); + treeElement.title = title; + parentTreeElement.appendChild(treeElement); + + if (result.className) + treeElement.listItemElement.addStyleClass(result.className); + if (typeof result.value !== "string") + treeElement.listItemElement.appendChild(WebInspector.applyFormatters(result.value)); + + if (result.children) { + for (var i = 0; i < result.children.length; ++i) + this._appendResult(treeElement, result.children[i]); + } + if (result.expanded) { + treeElement.listItemElement.removeStyleClass("parent"); + treeElement.listItemElement.addStyleClass("parent-expanded"); + treeElement.expand(); + } + return treeElement; + } +} + +WebInspector.AuditCategoryResultPane.prototype.__proto__ = WebInspector.SidebarPane.prototype; +/* AuditLauncherView.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.View} + */ +WebInspector.AuditLauncherView = function(runnerCallback, stopCallback) +{ + WebInspector.View.call(this); + + this._runnerCallback = runnerCallback; + this._stopCallback = stopCallback; + this._categoryIdPrefix = "audit-category-item-"; + this._auditRunning = false; + + this.element.addStyleClass("audit-launcher-view"); + this.element.addStyleClass("panel-enabler-view"); + + this._contentElement = document.createElement("div"); + this._contentElement.className = "audit-launcher-view-content"; + this.element.appendChild(this._contentElement); + this._boundCategoryClickListener = this._categoryClicked.bind(this); + + this._resetResourceCount(); + + this._sortedCategories = []; + + this._headerElement = document.createElement("h1"); + this._headerElement.className = "no-audits"; + this._headerElement.textContent = WebInspector.UIString("No audits to run"); + this._contentElement.appendChild(this._headerElement); + + WebInspector.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.ResourceStarted, this._onResourceStarted, this); + WebInspector.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.ResourceFinished, this._onResourceFinished, this); +} + +WebInspector.AuditLauncherView.prototype = { + _resetResourceCount: function() + { + this._loadedResources = 0; + this._totalResources = 0; + }, + + _onResourceStarted: function(event) + { + var resource = event.data; + // Ignore long-living WebSockets for the sake of progress indicator, as we won't be waiting them anyway. + if (resource.type === WebInspector.Resource.Type.WebSocket) + return; + ++this._totalResources; + this._updateResourceProgress(); + }, + + _onResourceFinished: function(event) + { + var resource = event.data; + // See resorceStarted for details. + if (resource.type === WebInspector.Resource.Type.WebSocket) + return; + ++this._loadedResources; + this._updateResourceProgress(); + }, + + addCategory: function(category) + { + if (!this._sortedCategories.length) + this._createLauncherUI(); + + var categoryElement = this._createCategoryElement(category.displayName, category.id); + category._checkboxElement = categoryElement.firstChild; + if (this._selectAllCheckboxElement.checked) { + category._checkboxElement.checked = true; + ++this._currentCategoriesCount; + } + + function compareCategories(a, b) + { + var aTitle = a.displayName || ""; + var bTitle = b.displayName || ""; + return aTitle.localeCompare(bTitle); + } + var insertBefore = insertionIndexForObjectInListSortedByFunction(category, this._sortedCategories, compareCategories); + this._categoriesElement.insertBefore(categoryElement, this._categoriesElement.children[insertBefore]); + this._sortedCategories.splice(insertBefore, 0, category); + this._updateButton(); + }, + + _setAuditRunning: function(auditRunning) + { + if (this._auditRunning === auditRunning) + return; + this._auditRunning = auditRunning; + delete this._stopRequested; + this._updateButton(); + this._updateResourceProgress(); + }, + + _launchButtonClicked: function(event) + { + if (!this._auditRunning) { + var catIds = []; + for (var category = 0; category < this._sortedCategories.length; ++category) { + if (this._sortedCategories[category]._checkboxElement.checked) + catIds.push(this._sortedCategories[category].id); + } + + this._setAuditRunning(true); + this._runnerCallback(catIds, this._resourceProgressElement, this._auditPresentStateElement.checked, this._setAuditRunning.bind(this, false)); + } else { + this._stopRequested = true; + this._stopCallback(this._setAuditRunning.bind(this, false)); + this._updateButton(); + } + }, + + _selectAllClicked: function(checkCategories) + { + var childNodes = this._categoriesElement.childNodes; + for (var i = 0, length = childNodes.length; i < length; ++i) + childNodes[i].firstChild.checked = checkCategories; + this._currentCategoriesCount = checkCategories ? this._sortedCategories.length : 0; + this._updateButton(); + }, + + _categoryClicked: function(event) + { + this._currentCategoriesCount += event.target.checked ? 1 : -1; + this._selectAllCheckboxElement.checked = this._currentCategoriesCount === this._sortedCategories.length; + this._updateButton(); + }, + + _createCategoryElement: function(title, id) + { + var labelElement = document.createElement("label"); + labelElement.id = this._categoryIdPrefix + id; + + var element = document.createElement("input"); + element.type = "checkbox"; + if (id !== "") + element.addEventListener("click", this._boundCategoryClickListener, false); + labelElement.appendChild(element); + labelElement.appendChild(document.createTextNode(title)); + + return labelElement; + }, + + _createLauncherUI: function() + { + this._headerElement = document.createElement("h1"); + this._headerElement.textContent = WebInspector.UIString("Select audits to run"); + + for (var child = 0; child < this._contentElement.children.length; ++child) + this._contentElement.removeChild(this._contentElement.children[child]); + + this._contentElement.appendChild(this._headerElement); + + function handleSelectAllClick(event) + { + this._selectAllClicked(event.target.checked); + } + var categoryElement = this._createCategoryElement(WebInspector.UIString("Select All"), ""); + categoryElement.id = "audit-launcher-selectall"; + this._selectAllCheckboxElement = categoryElement.firstChild; + this._selectAllCheckboxElement.checked = true; + this._selectAllCheckboxElement.addEventListener("click", handleSelectAllClick.bind(this), false); + this._contentElement.appendChild(categoryElement); + + this._categoriesElement = this._contentElement.createChild("div", "audit-categories-container"); + this._currentCategoriesCount = 0; + + this._contentElement.createChild("div", "flexible-space"); + + this._buttonContainerElement = this._contentElement.createChild("div", "button-container"); + + var labelElement = this._buttonContainerElement.createChild("label"); + this._auditPresentStateElement = labelElement.createChild("input"); + this._auditPresentStateElement.name = "audit-mode"; + this._auditPresentStateElement.type = "radio"; + this._auditPresentStateElement.checked = true; + this._auditPresentStateLabelElement = document.createTextNode(WebInspector.UIString("Audit Present State")); + labelElement.appendChild(this._auditPresentStateLabelElement); + + labelElement = this._buttonContainerElement.createChild("label"); + this.auditReloadedStateElement = labelElement.createChild("input"); + this.auditReloadedStateElement.name = "audit-mode"; + this.auditReloadedStateElement.type = "radio"; + labelElement.appendChild(document.createTextNode("Reload Page and Audit on Load")); + + this._launchButton = this._buttonContainerElement.createChild("button"); + this._launchButton.textContent = WebInspector.UIString("Run"); + this._launchButton.addEventListener("click", this._launchButtonClicked.bind(this), false); + + this._resourceProgressContainer = this._buttonContainerElement.createChild("span", "resource-progress"); + this._resourceProgressElement = this._resourceProgressContainer.createChild("progress"); + this._resourceProgressContainer.appendChild(document.createTextNode(" ")); + this._resourceProgressTextElement = this._resourceProgressContainer.createChild("span"); + + + this._selectAllClicked(this._selectAllCheckboxElement.checked); + this._updateButton(); + this._updateResourceProgress(); + }, + + _updateResourceProgress: function() + { + if (!this._resourceProgressContainer) + return; + + if (!this._auditRunning || this._stopRequested) { + this._resetResourceCount(); + this._resourceProgressContainer.addStyleClass("hidden"); + } else + this._resourceProgressContainer.removeStyleClass("hidden"); + if (this._loadedResources) + this._resourceProgressTextElement.textContent = WebInspector.UIString("Loading (%d of %d)", this._loadedResources, this._totalResources); + else + this._resourceProgressTextElement.textContent = ""; + }, + + _updateButton: function() + { + this._launchButton.textContent = this._auditRunning ? WebInspector.UIString("Stop") : WebInspector.UIString("Run"); + this._launchButton.disabled = !this._currentCategoriesCount || (this._auditRunning && this._stopRequested); + } +} + +WebInspector.AuditLauncherView.prototype.__proto__ = WebInspector.View.prototype; +/* AuditRules.js */ + +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.AuditRules.IPAddressRegexp = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/; + +WebInspector.AuditRules.CacheableResponseCodes = +{ + 200: true, + 203: true, + 206: true, + 300: true, + 301: true, + 410: true, + + 304: true // Underlying resource is cacheable +} + +WebInspector.AuditRules.getDomainToResourcesMap = function(resources, types, needFullResources) +{ + var domainToResourcesMap = {}; + for (var i = 0, size = resources.length; i < size; ++i) { + var resource = resources[i]; + if (types && types.indexOf(resource.type) === -1) + continue; + var parsedURL = resource.url.asParsedURL(); + if (!parsedURL) + continue; + var domain = parsedURL.host; + var domainResources = domainToResourcesMap[domain]; + if (domainResources === undefined) { + domainResources = []; + domainToResourcesMap[domain] = domainResources; + } + domainResources.push(needFullResources ? resource : resource.url); + } + return domainToResourcesMap; +} + +/** + * @constructor + * @extends {WebInspector.AuditRule} + */ +WebInspector.AuditRules.GzipRule = function() +{ + WebInspector.AuditRule.call(this, "network-gzip", "Enable gzip compression"); +} + +WebInspector.AuditRules.GzipRule.prototype = { + doRun: function(resources, result, callback, progressMonitor) + { + var totalSavings = 0; + var compressedSize = 0; + var candidateSize = 0; + var summary = result.addChild("", true); + for (var i = 0, length = resources.length; i < length; ++i) { + var resource = resources[i]; + if (resource.statusCode === 304) + continue; // Do not test 304 Not Modified resources as their contents are always empty. + if (this._shouldCompress(resource)) { + var size = resource.resourceSize; + candidateSize += size; + if (this._isCompressed(resource)) { + compressedSize += size; + continue; + } + var savings = 2 * size / 3; + totalSavings += savings; + summary.addFormatted("%r could save ~%s", resource.url, Number.bytesToString(savings)); + result.violationCount++; + } + } + if (!totalSavings) + return callback(null); + summary.value = String.sprintf("Compressing the following resources with gzip could reduce their transfer size by about two thirds (~%s):", Number.bytesToString(totalSavings)); + callback(result); + }, + + _isCompressed: function(resource) + { + var encodingHeader = resource.responseHeaders["Content-Encoding"]; + if (!encodingHeader) + return false; + + return /\b(?:gzip|deflate)\b/.test(encodingHeader); + }, + + _shouldCompress: function(resource) + { + return WebInspector.Resource.Type.isTextType(resource.type) && resource.domain && resource.resourceSize !== undefined && resource.resourceSize > 150; + } +} + +WebInspector.AuditRules.GzipRule.prototype.__proto__ = WebInspector.AuditRule.prototype; + +/** + * @constructor + * @extends {WebInspector.AuditRule} + */ +WebInspector.AuditRules.CombineExternalResourcesRule = function(id, name, type, resourceTypeName, allowedPerDomain) +{ + WebInspector.AuditRule.call(this, id, name); + this._type = type; + this._resourceTypeName = resourceTypeName; + this._allowedPerDomain = allowedPerDomain; +} + +WebInspector.AuditRules.CombineExternalResourcesRule.prototype = { + doRun: function(resources, result, callback, progressMonitor) + { + var domainToResourcesMap = WebInspector.AuditRules.getDomainToResourcesMap(resources, [this._type], false); + var penalizedResourceCount = 0; + // TODO: refactor according to the chosen i18n approach + var summary = result.addChild("", true); + for (var domain in domainToResourcesMap) { + var domainResources = domainToResourcesMap[domain]; + var extraResourceCount = domainResources.length - this._allowedPerDomain; + if (extraResourceCount <= 0) + continue; + penalizedResourceCount += extraResourceCount - 1; + summary.addChild(String.sprintf("%d %s resources served from %s.", domainResources.length, this._resourceTypeName, WebInspector.AuditRuleResult.resourceDomain(domain))); + result.violationCount += domainResources.length; + } + if (!penalizedResourceCount) + return callback(null); + + summary.value = "There are multiple resources served from same domain. Consider combining them into as few files as possible."; + callback(result); + } +} + +WebInspector.AuditRules.CombineExternalResourcesRule.prototype.__proto__ = WebInspector.AuditRule.prototype; + +/** + * @constructor + * @extends {WebInspector.AuditRules.CombineExternalResourcesRule} + */ +WebInspector.AuditRules.CombineJsResourcesRule = function(allowedPerDomain) { + WebInspector.AuditRules.CombineExternalResourcesRule.call(this, "page-externaljs", "Combine external JavaScript", WebInspector.Resource.Type.Script, "JavaScript", allowedPerDomain); +} + +WebInspector.AuditRules.CombineJsResourcesRule.prototype.__proto__ = WebInspector.AuditRules.CombineExternalResourcesRule.prototype; + +/** + * @constructor + * @extends {WebInspector.AuditRules.CombineExternalResourcesRule} + */ +WebInspector.AuditRules.CombineCssResourcesRule = function(allowedPerDomain) { + WebInspector.AuditRules.CombineExternalResourcesRule.call(this, "page-externalcss", "Combine external CSS", WebInspector.Resource.Type.Stylesheet, "CSS", allowedPerDomain); +} + +WebInspector.AuditRules.CombineCssResourcesRule.prototype.__proto__ = WebInspector.AuditRules.CombineExternalResourcesRule.prototype; + +/** + * @constructor + * @extends {WebInspector.AuditRule} + */ +WebInspector.AuditRules.MinimizeDnsLookupsRule = function(hostCountThreshold) { + WebInspector.AuditRule.call(this, "network-minimizelookups", "Minimize DNS lookups"); + this._hostCountThreshold = hostCountThreshold; +} + +WebInspector.AuditRules.MinimizeDnsLookupsRule.prototype = { + doRun: function(resources, result, callback, progressMonitor) + { + var summary = result.addChild(""); + var domainToResourcesMap = WebInspector.AuditRules.getDomainToResourcesMap(resources, undefined, false); + for (var domain in domainToResourcesMap) { + if (domainToResourcesMap[domain].length > 1) + continue; + var parsedURL = domain.asParsedURL(); + if (!parsedURL) + continue; + if (!parsedURL.host.search(WebInspector.AuditRules.IPAddressRegexp)) + continue; // an IP address + summary.addSnippet(domain); + result.violationCount++; + } + if (!summary.children || summary.children.length <= this._hostCountThreshold) + return callback(null); + + summary.value = "The following domains only serve one resource each. If possible, avoid the extra DNS lookups by serving these resources from existing domains."; + callback(result); + } +} + +WebInspector.AuditRules.MinimizeDnsLookupsRule.prototype.__proto__ = WebInspector.AuditRule.prototype; + +/** + * @constructor + * @extends {WebInspector.AuditRule} + */ +WebInspector.AuditRules.ParallelizeDownloadRule = function(optimalHostnameCount, minRequestThreshold, minBalanceThreshold) +{ + WebInspector.AuditRule.call(this, "network-parallelizehosts", "Parallelize downloads across hostnames"); + this._optimalHostnameCount = optimalHostnameCount; + this._minRequestThreshold = minRequestThreshold; + this._minBalanceThreshold = minBalanceThreshold; +} + +WebInspector.AuditRules.ParallelizeDownloadRule.prototype = { + doRun: function(resources, result, callback, progressMonitor) + { + function hostSorter(a, b) + { + var aCount = domainToResourcesMap[a].length; + var bCount = domainToResourcesMap[b].length; + return (aCount < bCount) ? 1 : (aCount == bCount) ? 0 : -1; + } + + var domainToResourcesMap = WebInspector.AuditRules.getDomainToResourcesMap( + resources, + [WebInspector.Resource.Type.Stylesheet, WebInspector.Resource.Type.Image], + true); + + var hosts = []; + for (var url in domainToResourcesMap) + hosts.push(url); + + if (!hosts.length) + return callback(null); // no hosts (local file or something) + + hosts.sort(hostSorter); + + var optimalHostnameCount = this._optimalHostnameCount; + if (hosts.length > optimalHostnameCount) + hosts.splice(optimalHostnameCount); + + var busiestHostResourceCount = domainToResourcesMap[hosts[0]].length; + var resourceCountAboveThreshold = busiestHostResourceCount - this._minRequestThreshold; + if (resourceCountAboveThreshold <= 0) + return callback(null); + + var avgResourcesPerHost = 0; + for (var i = 0, size = hosts.length; i < size; ++i) + avgResourcesPerHost += domainToResourcesMap[hosts[i]].length; + + // Assume optimal parallelization. + avgResourcesPerHost /= optimalHostnameCount; + avgResourcesPerHost = Math.max(avgResourcesPerHost, 1); + + var pctAboveAvg = (resourceCountAboveThreshold / avgResourcesPerHost) - 1.0; + var minBalanceThreshold = this._minBalanceThreshold; + if (pctAboveAvg < minBalanceThreshold) + return callback(null); + + var resourcesOnBusiestHost = domainToResourcesMap[hosts[0]]; + var entry = result.addChild(String.sprintf("This page makes %d parallelizable requests to %s. Increase download parallelization by distributing the following requests across multiple hostnames.", busiestHostResourceCount, hosts[0]), true); + for (var i = 0; i < resourcesOnBusiestHost.length; ++i) + entry.addURL(resourcesOnBusiestHost[i].url); + + result.violationCount = resourcesOnBusiestHost.length; + callback(result); + } +} + +WebInspector.AuditRules.ParallelizeDownloadRule.prototype.__proto__ = WebInspector.AuditRule.prototype; + +/** + * The reported CSS rule size is incorrect (parsed != original in WebKit), + * so use percentages instead, which gives a better approximation. + * @constructor + * @extends {WebInspector.AuditRule} + */ +WebInspector.AuditRules.UnusedCssRule = function() +{ + WebInspector.AuditRule.call(this, "page-unusedcss", "Remove unused CSS rules"); +} + +WebInspector.AuditRules.UnusedCssRule.prototype = { + doRun: function(resources, result, callback, progressMonitor) + { + var self = this; + + function evalCallback(styleSheets) { + if (progressMonitor.canceled) + return; + + if (!styleSheets.length) + return callback(null); + + var pseudoSelectorRegexp = /:hover|:link|:active|:visited|:focus|:before|:after/; + var selectors = []; + var testedSelectors = {}; + for (var i = 0; i < styleSheets.length; ++i) { + var styleSheet = styleSheets[i]; + for (var curRule = 0; curRule < styleSheet.rules.length; ++curRule) { + var selectorText = styleSheet.rules[curRule].selectorText; + if (selectorText.match(pseudoSelectorRegexp) || testedSelectors[selectorText]) + continue; + selectors.push(selectorText); + testedSelectors[selectorText] = 1; + } + } + + function selectorsCallback(callback, styleSheets, testedSelectors, foundSelectors) + { + if (progressMonitor.canceled) + return; + + var inlineBlockOrdinal = 0; + var totalStylesheetSize = 0; + var totalUnusedStylesheetSize = 0; + var summary; + + for (var i = 0; i < styleSheets.length; ++i) { + var styleSheet = styleSheets[i]; + var stylesheetSize = 0; + var unusedStylesheetSize = 0; + var unusedRules = []; + for (var curRule = 0; curRule < styleSheet.rules.length; ++curRule) { + var rule = styleSheet.rules[curRule]; + // Exact computation whenever source ranges are available. + var textLength = (rule.selectorRange && rule.style.range && rule.style.range.end) ? rule.style.range.end - rule.selectorRange.start + 1 : 0; + if (!textLength && rule.style.cssText) + textLength = rule.style.cssText.length + rule.selectorText.length; + stylesheetSize += textLength; + if (!testedSelectors[rule.selectorText] || foundSelectors[rule.selectorText]) + continue; + unusedStylesheetSize += textLength; + unusedRules.push(rule.selectorText); + } + totalStylesheetSize += stylesheetSize; + totalUnusedStylesheetSize += unusedStylesheetSize; + + if (!unusedRules.length) + continue; + + var resource = WebInspector.resourceForURL(styleSheet.sourceURL); + var isInlineBlock = resource && resource.type == WebInspector.Resource.Type.Document; + var url = !isInlineBlock ? WebInspector.AuditRuleResult.linkifyDisplayName(styleSheet.sourceURL) : String.sprintf("Inline block #%d", ++inlineBlockOrdinal); + var pctUnused = Math.round(100 * unusedStylesheetSize / stylesheetSize); + if (!summary) + summary = result.addChild("", true); + var entry = summary.addFormatted("%s: %s (%d%%) is not used by the current page.", url, Number.bytesToString(unusedStylesheetSize), pctUnused); + + for (var j = 0; j < unusedRules.length; ++j) + entry.addSnippet(unusedRules[j]); + + result.violationCount += unusedRules.length; + } + + if (!totalUnusedStylesheetSize) + return callback(null); + + var totalUnusedPercent = Math.round(100 * totalUnusedStylesheetSize / totalStylesheetSize); + summary.value = String.sprintf("%s (%d%%) of CSS is not used by the current page.", Number.bytesToString(totalUnusedStylesheetSize), totalUnusedPercent); + + callback(result); + } + + var foundSelectors = {}; + function queryCallback(boundSelectorsCallback, selector, styleSheets, testedSelectors, nodeId) + { + if (nodeId) + foundSelectors[selector] = true; + if (boundSelectorsCallback) + boundSelectorsCallback(foundSelectors); + } + + function documentLoaded(selectors, document) { + for (var i = 0; i < selectors.length; ++i) { + if (progressMonitor.canceled) + return; + WebInspector.domAgent.querySelector(document.id, selectors[i], queryCallback.bind(null, i === selectors.length - 1 ? selectorsCallback.bind(null, callback, styleSheets, testedSelectors) : null, selectors[i], styleSheets, testedSelectors)); + } + } + + WebInspector.domAgent.requestDocument(documentLoaded.bind(null, selectors)); + } + + function styleSheetCallback(styleSheets, sourceURL, continuation, styleSheet) + { + if (progressMonitor.canceled) + return; + + if (styleSheet) { + styleSheet.sourceURL = sourceURL; + styleSheets.push(styleSheet); + } + if (continuation) + continuation(styleSheets); + } + + function allStylesCallback(error, styleSheetInfos) + { + if (progressMonitor.canceled) + return; + + if (error || !styleSheetInfos || !styleSheetInfos.length) + return evalCallback([]); + var styleSheets = []; + for (var i = 0; i < styleSheetInfos.length; ++i) { + var info = styleSheetInfos[i]; + WebInspector.CSSStyleSheet.createForId(info.styleSheetId, styleSheetCallback.bind(null, styleSheets, info.sourceURL, i == styleSheetInfos.length - 1 ? evalCallback : null)); + } + } + + CSSAgent.getAllStyleSheets(allStylesCallback); + } +} + +WebInspector.AuditRules.UnusedCssRule.prototype.__proto__ = WebInspector.AuditRule.prototype; + +/** + * @constructor + * @extends {WebInspector.AuditRule} + */ +WebInspector.AuditRules.CacheControlRule = function(id, name) +{ + WebInspector.AuditRule.call(this, id, name); +} + +WebInspector.AuditRules.CacheControlRule.MillisPerMonth = 1000 * 60 * 60 * 24 * 30; + +WebInspector.AuditRules.CacheControlRule.prototype = { + + doRun: function(resources, result, callback, progressMonitor) + { + var cacheableAndNonCacheableResources = this._cacheableAndNonCacheableResources(resources); + if (cacheableAndNonCacheableResources[0].length) + this.runChecks(cacheableAndNonCacheableResources[0], result); + this.handleNonCacheableResources(cacheableAndNonCacheableResources[1], result); + + callback(result); + }, + + handleNonCacheableResources: function(resources, result) + { + }, + + _cacheableAndNonCacheableResources: function(resources) + { + var processedResources = [[], []]; + for (var i = 0; i < resources.length; ++i) { + var resource = resources[i]; + if (!this.isCacheableResource(resource)) + continue; + if (this._isExplicitlyNonCacheable(resource)) + processedResources[1].push(resource); + else + processedResources[0].push(resource); + } + return processedResources; + }, + + execCheck: function(messageText, resourceCheckFunction, resources, result) + { + var resourceCount = resources.length; + var urls = []; + for (var i = 0; i < resourceCount; ++i) { + if (resourceCheckFunction.call(this, resources[i])) + urls.push(resources[i].url); + } + if (urls.length) { + var entry = result.addChild(messageText, true); + entry.addURLs(urls); + result.violationCount += urls.length; + } + }, + + freshnessLifetimeGreaterThan: function(resource, timeMs) + { + var dateHeader = this.responseHeader(resource, "Date"); + if (!dateHeader) + return false; + + var dateHeaderMs = Date.parse(dateHeader); + if (isNaN(dateHeaderMs)) + return false; + + var freshnessLifetimeMs; + var maxAgeMatch = this.responseHeaderMatch(resource, "Cache-Control", "max-age=(\\d+)"); + + if (maxAgeMatch) + freshnessLifetimeMs = (maxAgeMatch[1]) ? 1000 * maxAgeMatch[1] : 0; + else { + var expiresHeader = this.responseHeader(resource, "Expires"); + if (expiresHeader) { + var expDate = Date.parse(expiresHeader); + if (!isNaN(expDate)) + freshnessLifetimeMs = expDate - dateHeaderMs; + } + } + + return (isNaN(freshnessLifetimeMs)) ? false : freshnessLifetimeMs > timeMs; + }, + + responseHeader: function(resource, header) + { + return resource.responseHeaders[header]; + }, + + hasResponseHeader: function(resource, header) + { + return resource.responseHeaders[header] !== undefined; + }, + + isCompressible: function(resource) + { + return WebInspector.Resource.Type.isTextType(resource.type); + }, + + isPubliclyCacheable: function(resource) + { + if (this._isExplicitlyNonCacheable(resource)) + return false; + + if (this.responseHeaderMatch(resource, "Cache-Control", "public")) + return true; + + return resource.url.indexOf("?") == -1 && !this.responseHeaderMatch(resource, "Cache-Control", "private"); + }, + + responseHeaderMatch: function(resource, header, regexp) + { + return resource.responseHeaders[header] + ? resource.responseHeaders[header].match(new RegExp(regexp, "im")) + : undefined; + }, + + hasExplicitExpiration: function(resource) + { + return this.hasResponseHeader(resource, "Date") && + (this.hasResponseHeader(resource, "Expires") || this.responseHeaderMatch(resource, "Cache-Control", "max-age")); + }, + + _isExplicitlyNonCacheable: function(resource) + { + var hasExplicitExp = this.hasExplicitExpiration(resource); + return this.responseHeaderMatch(resource, "Cache-Control", "(no-cache|no-store|must-revalidate)") || + this.responseHeaderMatch(resource, "Pragma", "no-cache") || + (hasExplicitExp && !this.freshnessLifetimeGreaterThan(resource, 0)) || + (!hasExplicitExp && resource.url && resource.url.indexOf("?") >= 0) || + (!hasExplicitExp && !this.isCacheableResource(resource)); + }, + + isCacheableResource: function(resource) + { + return resource.statusCode !== undefined && WebInspector.AuditRules.CacheableResponseCodes[resource.statusCode]; + } +} + +WebInspector.AuditRules.CacheControlRule.prototype.__proto__ = WebInspector.AuditRule.prototype; + +/** + * @constructor + * @extends {WebInspector.AuditRules.CacheControlRule} + */ +WebInspector.AuditRules.BrowserCacheControlRule = function() +{ + WebInspector.AuditRules.CacheControlRule.call(this, "http-browsercache", "Leverage browser caching"); +} + +WebInspector.AuditRules.BrowserCacheControlRule.prototype = { + handleNonCacheableResources: function(resources, result) + { + if (resources.length) { + var entry = result.addChild("The following resources are explicitly non-cacheable. Consider making them cacheable if possible:", true); + result.violationCount += resources.length; + for (var i = 0; i < resources.length; ++i) + entry.addURL(resources[i].url); + } + }, + + runChecks: function(resources, result, callback) + { + this.execCheck("The following resources are missing a cache expiration. Resources that do not specify an expiration may not be cached by browsers:", + this._missingExpirationCheck, resources, result); + this.execCheck("The following resources specify a \"Vary\" header that disables caching in most versions of Internet Explorer:", + this._varyCheck, resources, result); + this.execCheck("The following cacheable resources have a short freshness lifetime:", + this._oneMonthExpirationCheck, resources, result); + + // Unable to implement the favicon check due to the WebKit limitations. + this.execCheck("To further improve cache hit rate, specify an expiration one year in the future for the following cacheable resources:", + this._oneYearExpirationCheck, resources, result); + }, + + _missingExpirationCheck: function(resource) + { + return this.isCacheableResource(resource) && !this.hasResponseHeader(resource, "Set-Cookie") && !this.hasExplicitExpiration(resource); + }, + + _varyCheck: function(resource) + { + var varyHeader = this.responseHeader(resource, "Vary"); + if (varyHeader) { + varyHeader = varyHeader.replace(/User-Agent/gi, ""); + varyHeader = varyHeader.replace(/Accept-Encoding/gi, ""); + varyHeader = varyHeader.replace(/[, ]*/g, ""); + } + return varyHeader && varyHeader.length && this.isCacheableResource(resource) && this.freshnessLifetimeGreaterThan(resource, 0); + }, + + _oneMonthExpirationCheck: function(resource) + { + return this.isCacheableResource(resource) && + !this.hasResponseHeader(resource, "Set-Cookie") && + !this.freshnessLifetimeGreaterThan(resource, WebInspector.AuditRules.CacheControlRule.MillisPerMonth) && + this.freshnessLifetimeGreaterThan(resource, 0); + }, + + _oneYearExpirationCheck: function(resource) + { + return this.isCacheableResource(resource) && + !this.hasResponseHeader(resource, "Set-Cookie") && + !this.freshnessLifetimeGreaterThan(resource, 11 * WebInspector.AuditRules.CacheControlRule.MillisPerMonth) && + this.freshnessLifetimeGreaterThan(resource, WebInspector.AuditRules.CacheControlRule.MillisPerMonth); + } +} + +WebInspector.AuditRules.BrowserCacheControlRule.prototype.__proto__ = WebInspector.AuditRules.CacheControlRule.prototype; + +/** + * @constructor + * @extends {WebInspector.AuditRules.CacheControlRule} + */ +WebInspector.AuditRules.ProxyCacheControlRule = function() { + WebInspector.AuditRules.CacheControlRule.call(this, "http-proxycache", "Leverage proxy caching"); +} + +WebInspector.AuditRules.ProxyCacheControlRule.prototype = { + runChecks: function(resources, result, callback) + { + this.execCheck("Resources with a \"?\" in the URL are not cached by most proxy caching servers:", + this._questionMarkCheck, resources, result); + this.execCheck("Consider adding a \"Cache-Control: public\" header to the following resources:", + this._publicCachingCheck, resources, result); + this.execCheck("The following publicly cacheable resources contain a Set-Cookie header. This security vulnerability can cause cookies to be shared by multiple users.", + this._setCookieCacheableCheck, resources, result); + }, + + _questionMarkCheck: function(resource) + { + return resource.url.indexOf("?") >= 0 && !this.hasResponseHeader(resource, "Set-Cookie") && this.isPubliclyCacheable(resource); + }, + + _publicCachingCheck: function(resource) + { + return this.isCacheableResource(resource) && + !this.isCompressible(resource) && + !this.responseHeaderMatch(resource, "Cache-Control", "public") && + !this.hasResponseHeader(resource, "Set-Cookie"); + }, + + _setCookieCacheableCheck: function(resource) + { + return this.hasResponseHeader(resource, "Set-Cookie") && this.isPubliclyCacheable(resource); + } +} + +WebInspector.AuditRules.ProxyCacheControlRule.prototype.__proto__ = WebInspector.AuditRules.CacheControlRule.prototype; + +/** + * @constructor + * @extends {WebInspector.AuditRule} + */ +WebInspector.AuditRules.ImageDimensionsRule = function() +{ + WebInspector.AuditRule.call(this, "page-imagedims", "Specify image dimensions"); +} + +WebInspector.AuditRules.ImageDimensionsRule.prototype = { + doRun: function(resources, result, callback, progressMonitor) + { + var urlToNoDimensionCount = {}; + + function doneCallback() + { + for (var url in urlToNoDimensionCount) { + var entry = entry || result.addChild("A width and height should be specified for all images in order to speed up page display. The following image(s) are missing a width and/or height:", true); + var format = "%r"; + if (urlToNoDimensionCount[url] > 1) + format += " (%d uses)"; + entry.addFormatted(format, url, urlToNoDimensionCount[url]); + result.violationCount++; + } + callback(entry ? result : null); + } + + function imageStylesReady(imageId, styles, isLastStyle, computedStyle) + { + if (progressMonitor.canceled) + return; + + const node = WebInspector.domAgent.nodeForId(imageId); + var src = node.getAttribute("src"); + if (!src.asParsedURL()) { + for (var frameOwnerCandidate = node; frameOwnerCandidate; frameOwnerCandidate = frameOwnerCandidate.parentNode) { + if (frameOwnerCandidate.documentURL) { + var completeSrc = WebInspector.completeURL(frameOwnerCandidate.documentURL, src); + break; + } + } + } + if (completeSrc) + src = completeSrc; + + if (computedStyle.getPropertyValue("position") === "absolute") { + if (isLastStyle) + doneCallback(); + return; + } + + var widthFound = "width" in styles.styleAttributes; + var heightFound = "height" in styles.styleAttributes; + + var inlineStyle = styles.inlineStyle; + if (inlineStyle) { + if (inlineStyle.getPropertyValue("width") !== "") + widthFound = true; + if (inlineStyle.getPropertyValue("height") !== "") + heightFound = true; + } + + for (var i = styles.matchedCSSRules.length - 1; i >= 0 && !(widthFound && heightFound); --i) { + var style = styles.matchedCSSRules[i].style; + if (style.getPropertyValue("width") !== "") + widthFound = true; + if (style.getPropertyValue("height") !== "") + heightFound = true; + } + + if (!widthFound || !heightFound) { + if (src in urlToNoDimensionCount) + ++urlToNoDimensionCount[src]; + else + urlToNoDimensionCount[src] = 1; + } + + if (isLastStyle) + doneCallback(); + } + + function getStyles(nodeIds) + { + if (progressMonitor.canceled) + return; + var targetResult = {}; + + function inlineCallback(inlineStyle, styleAttributes) + { + targetResult.inlineStyle = inlineStyle; + targetResult.styleAttributes = styleAttributes; + } + + function matchedCallback(result) + { + if (result) + targetResult.matchedCSSRules = result.matchedCSSRules; + } + + if (!nodeIds || !nodeIds.length) + doneCallback(); + + for (var i = 0; nodeIds && i < nodeIds.length; ++i) { + WebInspector.cssModel.getMatchedStylesAsync(nodeIds[i], undefined, false, false, matchedCallback); + WebInspector.cssModel.getInlineStylesAsync(nodeIds[i], inlineCallback); + WebInspector.cssModel.getComputedStyleAsync(nodeIds[i], undefined, imageStylesReady.bind(null, nodeIds[i], targetResult, i === nodeIds.length - 1)); + } + } + + function onDocumentAvailable(root) + { + if (progressMonitor.canceled) + return; + WebInspector.domAgent.querySelectorAll(root.id, "img[src]", getStyles); + } + + if (progressMonitor.canceled) + return; + WebInspector.domAgent.requestDocument(onDocumentAvailable); + } +} + +WebInspector.AuditRules.ImageDimensionsRule.prototype.__proto__ = WebInspector.AuditRule.prototype; + +/** + * @constructor + * @extends {WebInspector.AuditRule} + */ +WebInspector.AuditRules.CssInHeadRule = function() +{ + WebInspector.AuditRule.call(this, "page-cssinhead", "Put CSS in the document head"); +} + +WebInspector.AuditRules.CssInHeadRule.prototype = { + doRun: function(resources, result, callback, progressMonitor) + { + function evalCallback(evalResult) + { + if (progressMonitor.canceled) + return; + + if (!evalResult) + return callback(null); + + var summary = result.addChild(""); + + var outputMessages = []; + for (var url in evalResult) { + var urlViolations = evalResult[url]; + if (urlViolations[0]) { + result.addFormatted("%s style block(s) in the %r body should be moved to the document head.", urlViolations[0], url); + result.violationCount += urlViolations[0]; + } + for (var i = 0; i < urlViolations[1].length; ++i) + result.addFormatted("Link node %r should be moved to the document head in %r", urlViolations[1][i], url); + result.violationCount += urlViolations[1].length; + } + summary.value = String.sprintf("CSS in the document body adversely impacts rendering performance."); + callback(result); + } + + function externalStylesheetsReceived(root, inlineStyleNodeIds, nodeIds) + { + if (progressMonitor.canceled) + return; + + if (!nodeIds) + return; + var externalStylesheetNodeIds = nodeIds; + var result = null; + if (inlineStyleNodeIds.length || externalStylesheetNodeIds.length) { + var urlToViolationsArray = {}; + var externalStylesheetHrefs = []; + for (var j = 0; j < externalStylesheetNodeIds.length; ++j) { + var linkNode = WebInspector.domAgent.nodeForId(externalStylesheetNodeIds[j]); + var completeHref = WebInspector.completeURL(linkNode.ownerDocument.documentURL, linkNode.getAttribute("href")); + externalStylesheetHrefs.push(completeHref || ""); + } + urlToViolationsArray[root.documentURL] = [inlineStyleNodeIds.length, externalStylesheetHrefs]; + result = urlToViolationsArray; + } + evalCallback(result); + } + + function inlineStylesReceived(root, nodeIds) + { + if (progressMonitor.canceled) + return; + + if (!nodeIds) + return; + WebInspector.domAgent.querySelectorAll(root.id, "body link[rel~='stylesheet'][href]", externalStylesheetsReceived.bind(null, root, nodeIds)); + } + + function onDocumentAvailable(root) + { + if (progressMonitor.canceled) + return; + + WebInspector.domAgent.querySelectorAll(root.id, "body style", inlineStylesReceived.bind(null, root)); + } + + WebInspector.domAgent.requestDocument(onDocumentAvailable); + } +} + +WebInspector.AuditRules.CssInHeadRule.prototype.__proto__ = WebInspector.AuditRule.prototype; + +/** + * @constructor + * @extends {WebInspector.AuditRule} + */ +WebInspector.AuditRules.StylesScriptsOrderRule = function() +{ + WebInspector.AuditRule.call(this, "page-stylescriptorder", "Optimize the order of styles and scripts"); +} + +WebInspector.AuditRules.StylesScriptsOrderRule.prototype = { + doRun: function(resources, result, callback, progressMonitor) + { + function evalCallback(resultValue) + { + if (progressMonitor.canceled) + return; + + if (!resultValue) + return callback(null); + + var lateCssUrls = resultValue[0]; + var cssBeforeInlineCount = resultValue[1]; + + var entry = result.addChild("The following external CSS files were included after an external JavaScript file in the document head. To ensure CSS files are downloaded in parallel, always include external CSS before external JavaScript.", true); + entry.addURLs(lateCssUrls); + result.violationCount += lateCssUrls.length; + + if (cssBeforeInlineCount) { + result.addChild(String.sprintf(" %d inline script block%s found in the head between an external CSS file and another resource. To allow parallel downloading, move the inline script before the external CSS file, or after the next resource.", cssBeforeInlineCount, cssBeforeInlineCount > 1 ? "s were" : " was")); + result.violationCount += cssBeforeInlineCount; + } + callback(result); + } + + function cssBeforeInlineReceived(lateStyleIds, nodeIds) + { + if (progressMonitor.canceled) + return; + + if (!nodeIds) + return; + + var cssBeforeInlineCount = nodeIds.length; + var result = null; + if (lateStyleIds.length || cssBeforeInlineCount) { + var lateStyleUrls = []; + for (var i = 0; i < lateStyleIds.length; ++i) { + var lateStyleNode = WebInspector.domAgent.nodeForId(lateStyleIds[i]); + var completeHref = WebInspector.completeURL(lateStyleNode.ownerDocument.documentURL, lateStyleNode.getAttribute("href")); + lateStyleUrls.push(completeHref || ""); + } + result = [ lateStyleUrls, cssBeforeInlineCount ]; + } + + evalCallback(result); + } + + function lateStylesReceived(root, nodeIds) + { + if (progressMonitor.canceled) + return; + + if (!nodeIds) + return; + + WebInspector.domAgent.querySelectorAll(root.id, "head link[rel~='stylesheet'][href] ~ script:not([src])", cssBeforeInlineReceived.bind(null, nodeIds)); + } + + function onDocumentAvailable(root) + { + if (progressMonitor.canceled) + return; + + WebInspector.domAgent.querySelectorAll(root.id, "head script[src] ~ link[rel~='stylesheet'][href]", lateStylesReceived.bind(null, root)); + } + + WebInspector.domAgent.requestDocument(onDocumentAvailable); + } +} + +WebInspector.AuditRules.StylesScriptsOrderRule.prototype.__proto__ = WebInspector.AuditRule.prototype; + +/** + * @constructor + * @extends {WebInspector.AuditRule} + */ +WebInspector.AuditRules.CookieRuleBase = function(id, name) +{ + WebInspector.AuditRule.call(this, id, name); +} + +WebInspector.AuditRules.CookieRuleBase.prototype = { + doRun: function(resources, result, callback, progressMonitor) + { + var self = this; + function resultCallback(receivedCookies, isAdvanced) { + if (progressMonitor.canceled) + return; + + self.processCookies(isAdvanced ? receivedCookies : [], resources, result); + callback(result); + } + + WebInspector.Cookies.getCookiesAsync(resultCallback); + }, + + mapResourceCookies: function(resourcesByDomain, allCookies, callback) + { + for (var i = 0; i < allCookies.length; ++i) { + for (var resourceDomain in resourcesByDomain) { + if (WebInspector.Cookies.cookieDomainMatchesResourceDomain(allCookies[i].domain, resourceDomain)) + this._callbackForResourceCookiePairs(resourcesByDomain[resourceDomain], allCookies[i], callback); + } + } + }, + + _callbackForResourceCookiePairs: function(resources, cookie, callback) + { + if (!resources) + return; + for (var i = 0; i < resources.length; ++i) { + if (WebInspector.Cookies.cookieMatchesResourceURL(cookie, resources[i].url)) + callback(resources[i], cookie); + } + } +} + +WebInspector.AuditRules.CookieRuleBase.prototype.__proto__ = WebInspector.AuditRule.prototype; + +/** + * @constructor + * @extends {WebInspector.AuditRules.CookieRuleBase} + */ +WebInspector.AuditRules.CookieSizeRule = function(avgBytesThreshold) +{ + WebInspector.AuditRules.CookieRuleBase.call(this, "http-cookiesize", "Minimize cookie size"); + this._avgBytesThreshold = avgBytesThreshold; + this._maxBytesThreshold = 1000; +} + +WebInspector.AuditRules.CookieSizeRule.prototype = { + _average: function(cookieArray) + { + var total = 0; + for (var i = 0; i < cookieArray.length; ++i) + total += cookieArray[i].size; + return cookieArray.length ? Math.round(total / cookieArray.length) : 0; + }, + + _max: function(cookieArray) + { + var result = 0; + for (var i = 0; i < cookieArray.length; ++i) + result = Math.max(cookieArray[i].size, result); + return result; + }, + + processCookies: function(allCookies, resources, result) + { + function maxSizeSorter(a, b) + { + return b.maxCookieSize - a.maxCookieSize; + } + + function avgSizeSorter(a, b) + { + return b.avgCookieSize - a.avgCookieSize; + } + + var cookiesPerResourceDomain = {}; + + function collectorCallback(resource, cookie) + { + var cookies = cookiesPerResourceDomain[resource.domain]; + if (!cookies) { + cookies = []; + cookiesPerResourceDomain[resource.domain] = cookies; + } + cookies.push(cookie); + } + + if (!allCookies.length) + return; + + var sortedCookieSizes = []; + + var domainToResourcesMap = WebInspector.AuditRules.getDomainToResourcesMap(resources, + null, + true); + var matchingResourceData = {}; + this.mapResourceCookies(domainToResourcesMap, allCookies, collectorCallback.bind(this)); + + for (var resourceDomain in cookiesPerResourceDomain) { + var cookies = cookiesPerResourceDomain[resourceDomain]; + sortedCookieSizes.push({ + domain: resourceDomain, + avgCookieSize: this._average(cookies), + maxCookieSize: this._max(cookies) + }); + } + var avgAllCookiesSize = this._average(allCookies); + + var hugeCookieDomains = []; + sortedCookieSizes.sort(maxSizeSorter); + + for (var i = 0, len = sortedCookieSizes.length; i < len; ++i) { + var maxCookieSize = sortedCookieSizes[i].maxCookieSize; + if (maxCookieSize > this._maxBytesThreshold) + hugeCookieDomains.push(WebInspector.AuditRuleResult.resourceDomain(sortedCookieSizes[i].domain) + ": " + Number.bytesToString(maxCookieSize)); + } + + var bigAvgCookieDomains = []; + sortedCookieSizes.sort(avgSizeSorter); + for (var i = 0, len = sortedCookieSizes.length; i < len; ++i) { + var domain = sortedCookieSizes[i].domain; + var avgCookieSize = sortedCookieSizes[i].avgCookieSize; + if (avgCookieSize > this._avgBytesThreshold && avgCookieSize < this._maxBytesThreshold) + bigAvgCookieDomains.push(WebInspector.AuditRuleResult.resourceDomain(domain) + ": " + Number.bytesToString(avgCookieSize)); + } + result.addChild(String.sprintf("The average cookie size for all requests on this page is %s", Number.bytesToString(avgAllCookiesSize))); + + var message; + if (hugeCookieDomains.length) { + var entry = result.addChild("The following domains have a cookie size in excess of 1KB. This is harmful because requests with cookies larger than 1KB typically cannot fit into a single network packet.", true); + entry.addURLs(hugeCookieDomains); + result.violationCount += hugeCookieDomains.length; + } + + if (bigAvgCookieDomains.length) { + var entry = result.addChild(String.sprintf("The following domains have an average cookie size in excess of %d bytes. Reducing the size of cookies for these domains can reduce the time it takes to send requests.", this._avgBytesThreshold), true); + entry.addURLs(bigAvgCookieDomains); + result.violationCount += bigAvgCookieDomains.length; + } + } +} + +WebInspector.AuditRules.CookieSizeRule.prototype.__proto__ = WebInspector.AuditRules.CookieRuleBase.prototype; + +/** + * @constructor + * @extends {WebInspector.AuditRules.CookieRuleBase} + */ +WebInspector.AuditRules.StaticCookielessRule = function(minResources) +{ + WebInspector.AuditRules.CookieRuleBase.call(this, "http-staticcookieless", "Serve static content from a cookieless domain"); + this._minResources = minResources; +} + +WebInspector.AuditRules.StaticCookielessRule.prototype = { + processCookies: function(allCookies, resources, result) + { + var domainToResourcesMap = WebInspector.AuditRules.getDomainToResourcesMap(resources, + [WebInspector.Resource.Type.Stylesheet, + WebInspector.Resource.Type.Image], + true); + var totalStaticResources = 0; + for (var domain in domainToResourcesMap) + totalStaticResources += domainToResourcesMap[domain].length; + if (totalStaticResources < this._minResources) + return; + var matchingResourceData = {}; + this.mapResourceCookies(domainToResourcesMap, allCookies, this._collectorCallback.bind(this, matchingResourceData)); + + var badUrls = []; + var cookieBytes = 0; + for (var url in matchingResourceData) { + badUrls.push(url); + cookieBytes += matchingResourceData[url] + } + if (badUrls.length < this._minResources) + return; + + var entry = result.addChild(String.sprintf("%s of cookies were sent with the following static resources. Serve these static resources from a domain that does not set cookies:", Number.bytesToString(cookieBytes)), true); + entry.addURLs(badUrls); + result.violationCount = badUrls.length; + }, + + _collectorCallback: function(matchingResourceData, resource, cookie) + { + matchingResourceData[resource.url] = (matchingResourceData[resource.url] || 0) + cookie.size; + } +} + +WebInspector.AuditRules.StaticCookielessRule.prototype.__proto__ = WebInspector.AuditRules.CookieRuleBase.prototype; +/* AuditCategories.js */ + +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.AuditCategory} + */ +WebInspector.AuditCategories.PagePerformance = function() { + WebInspector.AuditCategory.call(this, WebInspector.AuditCategories.PagePerformance.AuditCategoryName); +} + +WebInspector.AuditCategories.PagePerformance.AuditCategoryName = "Web Page Performance"; + +WebInspector.AuditCategories.PagePerformance.prototype = { + initialize: function() + { + this.addRule(new WebInspector.AuditRules.UnusedCssRule(), WebInspector.AuditRule.Severity.Warning); + this.addRule(new WebInspector.AuditRules.CssInHeadRule(), WebInspector.AuditRule.Severity.Severe); + this.addRule(new WebInspector.AuditRules.StylesScriptsOrderRule(), WebInspector.AuditRule.Severity.Severe); + } +} + +WebInspector.AuditCategories.PagePerformance.prototype.__proto__ = WebInspector.AuditCategory.prototype; + +/** + * @constructor + * @extends {WebInspector.AuditCategory} + */ +WebInspector.AuditCategories.NetworkUtilization = function() { + WebInspector.AuditCategory.call(this, WebInspector.AuditCategories.NetworkUtilization.AuditCategoryName); +} + +WebInspector.AuditCategories.NetworkUtilization.AuditCategoryName = "Network Utilization"; + +WebInspector.AuditCategories.NetworkUtilization.prototype = { + initialize: function() + { + this.addRule(new WebInspector.AuditRules.GzipRule(), WebInspector.AuditRule.Severity.Severe); + this.addRule(new WebInspector.AuditRules.ImageDimensionsRule(), WebInspector.AuditRule.Severity.Warning); + this.addRule(new WebInspector.AuditRules.CookieSizeRule(400), WebInspector.AuditRule.Severity.Warning); + this.addRule(new WebInspector.AuditRules.StaticCookielessRule(5), WebInspector.AuditRule.Severity.Warning); + this.addRule(new WebInspector.AuditRules.CombineJsResourcesRule(2), WebInspector.AuditRule.Severity.Severe); + this.addRule(new WebInspector.AuditRules.CombineCssResourcesRule(2), WebInspector.AuditRule.Severity.Severe); + this.addRule(new WebInspector.AuditRules.MinimizeDnsLookupsRule(4), WebInspector.AuditRule.Severity.Warning); + this.addRule(new WebInspector.AuditRules.ParallelizeDownloadRule(4, 10, 0.5), WebInspector.AuditRule.Severity.Warning); + this.addRule(new WebInspector.AuditRules.BrowserCacheControlRule(), WebInspector.AuditRule.Severity.Severe); + this.addRule(new WebInspector.AuditRules.ProxyCacheControlRule(), WebInspector.AuditRule.Severity.Warning); + } +} + +WebInspector.AuditCategories.NetworkUtilization.prototype.__proto__ = WebInspector.AuditCategory.prototype; +/* AuditFormatters.js */ + +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.applyFormatters = function(value) +{ + var formatter; + var type = typeof value; + var args; + + switch (type) { + case "string": + case "boolean": + case "number": + formatter = WebInspector.AuditFormatters.text; + args = [ value.toString() ]; + break; + + case "object": + if (value instanceof Node) + return value; + if (value instanceof Array) { + formatter = WebInspector.AuditFormatters.concat; + args = value; + } else if (value.type && value.arguments) { + formatter = WebInspector.AuditFormatters[value.type]; + args = value.arguments; + } + } + if (!formatter) + throw "Invalid value or formatter: " + type + JSON.stringify(value); + + return formatter.apply(null, args); +} + +WebInspector.AuditFormatters = { + text: function(text) + { + return document.createTextNode(text); + }, + + snippet: function(snippetText) + { + var div = document.createElement("div"); + div.textContent = snippetText; + div.className = "source-code"; + return div; + }, + + concat: function() + { + var parent = document.createElement("span"); + for (var arg = 0; arg < arguments.length; ++arg) + parent.appendChild(WebInspector.applyFormatters(arguments[arg])); + return parent; + }, + + url: function(url, displayText, allowExternalNavigation) + { + var a = document.createElement("a"); + a.href = url; + a.title = url; + a.textContent = displayText || url; + if (allowExternalNavigation) + a.target = "_blank"; + return a; + }, + + resourceLink: function(url, line) + { + // FIXME: use WebInspector.DebuggerPresentationModel.Linkifier + return WebInspector.linkifyResourceAsNode(url, line, "console-message-url webkit-html-resource-link"); + } +}; +/* NetworkItemView.js */ + +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @extends {WebInspector.TabbedPane} + * @constructor + */ +WebInspector.NetworkItemView = function(resource) +{ + WebInspector.TabbedPane.call(this); + + this.element.addStyleClass("network-item-view"); + + var headersView = new WebInspector.ResourceHeadersView(resource); + this.appendTab("headers", WebInspector.UIString("Headers"), headersView); + + var responseView = new WebInspector.ResourceResponseView(resource); + var previewView = new WebInspector.ResourcePreviewView(resource, responseView); + + this.appendTab("preview", WebInspector.UIString("Preview"), previewView); + this.appendTab("response", WebInspector.UIString("Response"), responseView); + + if (resource.requestCookies || resource.responseCookies) { + this._cookiesView = new WebInspector.ResourceCookiesView(resource); + this.appendTab("cookies", WebInspector.UIString("Cookies"), this._cookiesView); + } + + if (resource.timing) { + var timingView = new WebInspector.ResourceTimingView(resource); + this.appendTab("timing", WebInspector.UIString("Timing"), timingView); + } + + this.addEventListener(WebInspector.TabbedPane.EventTypes.TabSelected, this._tabSelected, this); +} + +WebInspector.NetworkItemView.prototype = { + wasShown: function() + { + WebInspector.TabbedPane.prototype.wasShown.call(this); + this._selectTab(); + }, + + /** + * @param {string=} tabId + */ + _selectTab: function(tabId) + { + if (!tabId) + tabId = WebInspector.settings.resourceViewTab.get(); + + if (!this.selectTab(tabId)) { + this._isInFallbackSelection = true; + this.selectTab("headers"); + delete this._isInFallbackSelection; + } + }, + + _tabSelected: function(event) + { + if (event.data.isUserGesture) + WebInspector.settings.resourceViewTab.set(event.data.tabId); + } +} + +WebInspector.NetworkItemView.prototype.__proto__ = WebInspector.TabbedPane.prototype; + +/** + * @extends {WebInspector.ResourceView} + * @constructor + */ +WebInspector.ResourceContentView = function(resource) +{ + WebInspector.ResourceView.call(this, resource); +} + +WebInspector.ResourceContentView.prototype = { + hasContent: function() + { + return true; + }, + + get innerView() + { + return this._innerView; + }, + + set innerView(innerView) + { + this._innerView = innerView; + }, + + wasShown: function() + { + this._ensureInnerViewShown(); + }, + + _ensureInnerViewShown: function() + { + if (this._innerViewShowRequested) + return; + this._innerViewShowRequested = true; + + function callback() + { + this._innerViewShowRequested = false; + this.contentLoaded(); + } + + this.resource.requestContent(callback.bind(this)); + }, + + contentLoaded: function() + { + // Should be implemented by subclasses. + }, + + canHighlightLine: function() + { + return this._innerView && this._innerView.canHighlightLine(); + }, + + highlightLine: function(line) + { + if (this.canHighlightLine()) + this._innerView.highlightLine(line); + } +} + +WebInspector.ResourceContentView.prototype.__proto__ = WebInspector.ResourceView.prototype; +/* EmptyView.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.View} + */ +WebInspector.EmptyView = function(text) +{ + WebInspector.View.call(this); + this._text = text; +} + +WebInspector.EmptyView.prototype = { + wasShown: function() + { + this.element.className = "storage-empty-view"; + this.element.textContent = this._text; + }, + + set text(text) + { + this._text = text; + if (this.visible) + this.element.textContent = this._text; + }, +} + +WebInspector.EmptyView.prototype.__proto__ = WebInspector.View.prototype; +/* ResourceHeadersView.js */ + +/* + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) IBM Corp. 2009 All rights reserved. + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @extends {WebInspector.View} + * @constructor + */ +WebInspector.ResourceHeadersView = function(resource) +{ + WebInspector.View.call(this); + this.registerRequiredCSS("resourceView.css"); + this.element.addStyleClass("resource-headers-view"); + + this._resource = resource; + + this._headersListElement = document.createElement("ol"); + this._headersListElement.className = "outline-disclosure"; + this.element.appendChild(this._headersListElement); + + this._headersTreeOutline = new TreeOutline(this._headersListElement); + this._headersTreeOutline.expandTreeElementsWhenArrowing = true; + + this._urlTreeElement = new TreeElement("", null, false); + this._urlTreeElement.selectable = false; + this._headersTreeOutline.appendChild(this._urlTreeElement); + + this._requestMethodTreeElement = new TreeElement("", null, false); + this._requestMethodTreeElement.selectable = false; + this._headersTreeOutline.appendChild(this._requestMethodTreeElement); + + this._statusCodeTreeElement = new TreeElement("", null, false); + this._statusCodeTreeElement.selectable = false; + this._headersTreeOutline.appendChild(this._statusCodeTreeElement); + + this._requestHeadersTreeElement = new TreeElement("", null, true); + this._requestHeadersTreeElement.expanded = true; + this._requestHeadersTreeElement.selectable = false; + this._headersTreeOutline.appendChild(this._requestHeadersTreeElement); + + this._decodeRequestParameters = true; + + this._showRequestHeadersText = false; + this._showResponseHeadersText = false; + + this._queryStringTreeElement = new TreeElement("", null, true); + this._queryStringTreeElement.expanded = true; + this._queryStringTreeElement.selectable = false; + this._queryStringTreeElement.hidden = true; + this._headersTreeOutline.appendChild(this._queryStringTreeElement); + + this._urlFragmentTreeElement = new TreeElement("", null, true); + this._urlFragmentTreeElement.expanded = true; + this._urlFragmentTreeElement.selectable = false; + this._urlFragmentTreeElement.hidden = true; + this._headersTreeOutline.appendChild(this._urlFragmentTreeElement); + + this._formDataTreeElement = new TreeElement("", null, true); + this._formDataTreeElement.expanded = true; + this._formDataTreeElement.selectable = false; + this._formDataTreeElement.hidden = true; + this._headersTreeOutline.appendChild(this._formDataTreeElement); + + this._requestPayloadTreeElement = new TreeElement(WebInspector.UIString("Request Payload"), null, true); + this._requestPayloadTreeElement.expanded = true; + this._requestPayloadTreeElement.selectable = false; + this._requestPayloadTreeElement.hidden = true; + this._headersTreeOutline.appendChild(this._requestPayloadTreeElement); + + this._responseHeadersTreeElement = new TreeElement("", null, true); + this._responseHeadersTreeElement.expanded = true; + this._responseHeadersTreeElement.selectable = false; + this._headersTreeOutline.appendChild(this._responseHeadersTreeElement); + + resource.addEventListener("requestHeaders changed", this._refreshRequestHeaders, this); + resource.addEventListener("responseHeaders changed", this._refreshResponseHeaders, this); + resource.addEventListener("finished", this._refreshHTTPInformation, this); + + this._refreshURL(); + this._refreshQueryString(); + this._refreshUrlFragment(); + this._refreshRequestHeaders(); + this._refreshResponseHeaders(); + this._refreshHTTPInformation(); +} + +WebInspector.ResourceHeadersView.prototype = { + /** + * @param {string} name + * @param {string} value + */ + _formatHeader: function(name, value) + { + var fragment = document.createDocumentFragment(); + fragment.createChild("div", "header-name").textContent = name + ":"; + fragment.createChild("div", "header-value source-code").textContent = value; + + return fragment; + }, + + /** + * @param {string} value + * @param {string} className + * @param {boolean} decodeParameters + */ + _formatParameter: function(value, className, decodeParameters) + { + var errorDecoding = false; + + if (decodeParameters) { + value = value.replace(/\+/g, " "); + if (value.indexOf("%") >= 0) { + try { + value = decodeURIComponent(value); + } catch(e) { + errorDecoding = true; + } + } + } + var div = document.createElement("div"); + div.className = className; + if (errorDecoding) + div.createChild("span", "error-message").textContent = WebInspector.UIString("(unable to decode value)"); + else + div.textContent = value; + return div; + }, + + _refreshURL: function() + { + this._urlTreeElement.title = this._formatHeader(WebInspector.UIString("Request URL"), this._resource.url); + }, + + _refreshQueryString: function() + { + var queryParameters = this._resource.queryParameters; + this._queryStringTreeElement.hidden = !queryParameters; + if (queryParameters) + this._refreshParms(WebInspector.UIString("Query String Parameters"), queryParameters, this._queryStringTreeElement); + }, + + _refreshUrlFragment: function() + { + var urlFragment = this._resource.urlFragment; + this._urlFragmentTreeElement.hidden = !urlFragment; + + if (!urlFragment) + return; + + var sectionTitle = WebInspector.UIString("URL fragment"); + + this._urlFragmentTreeElement.removeChildren(); + this._urlFragmentTreeElement.listItemElement.removeChildren(); + this._urlFragmentTreeElement.listItemElement.appendChild(document.createTextNode(sectionTitle)); + + var fragmentTreeElement = new TreeElement(null, null, false); + fragmentTreeElement.title = this._formatHeader("#", urlFragment); + fragmentTreeElement.selectable = false; + this._urlFragmentTreeElement.appendChild(fragmentTreeElement); + }, + + _refreshFormData: function() + { + this._formDataTreeElement.hidden = true; + this._requestPayloadTreeElement.hidden = true; + + var formData = this._resource.requestFormData; + if (!formData) + return; + + var formParameters = this._resource.formParameters; + if (formParameters) { + this._formDataTreeElement.hidden = false; + this._refreshParms(WebInspector.UIString("Form Data"), formParameters, this._formDataTreeElement); + } else { + this._requestPayloadTreeElement.hidden = false; + this._refreshRequestPayload(formData); + } + }, + + _refreshRequestPayload: function(formData) + { + this._requestPayloadTreeElement.removeChildren(); + + var title = document.createElement("div"); + title.className = "raw-form-data header-value source-code"; + title.textContent = formData; + + var parmTreeElement = new TreeElement(title, null, false); + parmTreeElement.selectable = false; + this._requestPayloadTreeElement.appendChild(parmTreeElement); + }, + + _refreshParms: function(title, parms, parmsTreeElement) + { + parmsTreeElement.removeChildren(); + + parmsTreeElement.listItemElement.removeChildren(); + parmsTreeElement.listItemElement.appendChild(document.createTextNode(title)); + + var headerCount = document.createElement("span"); + headerCount.addStyleClass("header-count"); + headerCount.textContent = WebInspector.UIString(" (%d)", parms.length); + parmsTreeElement.listItemElement.appendChild(headerCount); + + var toggleTitle = this._decodeRequestParameters ? WebInspector.UIString("view URL encoded") : WebInspector.UIString("view decoded"); + var toggleButton = this._createToggleButton(toggleTitle); + toggleButton.addEventListener("click", this._toggleURLdecoding.bind(this)); + parmsTreeElement.listItemElement.appendChild(toggleButton); + + + for (var i = 0; i < parms.length; ++i) { + var paramNameValue = document.createDocumentFragment(); + var name = this._formatParameter(parms[i].name + ":", "header-name", this._decodeRequestParameters); + var value = this._formatParameter(parms[i].value, "header-value source-code", this._decodeRequestParameters); + paramNameValue.appendChild(name); + paramNameValue.appendChild(value); + + var parmTreeElement = new TreeElement(paramNameValue, null, false); + parmTreeElement.selectable = false; + parmsTreeElement.appendChild(parmTreeElement); + } + }, + + _toggleURLdecoding: function(event) + { + this._decodeRequestParameters = !this._decodeRequestParameters; + this._refreshQueryString(); + this._refreshFormData(); + }, + + _getHeaderValue: function(headers, key) + { + var lowerKey = key.toLowerCase(); + for (var testKey in headers) { + if (testKey.toLowerCase() === lowerKey) + return headers[testKey]; + } + }, + + _refreshRequestHeaders: function() + { + var additionalRow = null; + if (typeof this._resource.webSocketRequestKey3 !== "undefined") + additionalRow = {header: "(Key3)", value: this._resource.webSocketRequestKey3}; + if (this._showRequestHeadersText) + this._refreshHeadersText(WebInspector.UIString("Request Headers"), this._resource.sortedRequestHeaders, this._resource.requestHeadersText, this._requestHeadersTreeElement); + else + this._refreshHeaders(WebInspector.UIString("Request Headers"), this._resource.sortedRequestHeaders, additionalRow, this._requestHeadersTreeElement); + + if (this._resource.requestHeadersText) { + var toggleButton = this._createHeadersToggleButton(this._showRequestHeadersText); + toggleButton.addEventListener("click", this._toggleRequestHeadersText.bind(this)); + this._requestHeadersTreeElement.listItemElement.appendChild(toggleButton); + } + + this._refreshFormData(); + }, + + _refreshResponseHeaders: function() + { + var additionalRow = null; + if (typeof this._resource.webSocketChallengeResponse !== "undefined") + additionalRow = {header: "(Challenge Response)", value: this._resource.webSocketChallengeResponse}; + if (this._showResponseHeadersText) + this._refreshHeadersText(WebInspector.UIString("Response Headers"), this._resource.sortedResponseHeaders, this._resource.responseHeadersText, this._responseHeadersTreeElement); + else + this._refreshHeaders(WebInspector.UIString("Response Headers"), this._resource.sortedResponseHeaders, additionalRow, this._responseHeadersTreeElement); + + if (this._resource.responseHeadersText) { + var toggleButton = this._createHeadersToggleButton(this._showResponseHeadersText); + toggleButton.addEventListener("click", this._toggleResponseHeadersText.bind(this)); + this._responseHeadersTreeElement.listItemElement.appendChild(toggleButton); + } + }, + + _refreshHTTPInformation: function() + { + var requestMethodElement = this._requestMethodTreeElement; + requestMethodElement.hidden = !this._resource.statusCode; + var statusCodeElement = this._statusCodeTreeElement; + statusCodeElement.hidden = !this._resource.statusCode; + + if (this._resource.statusCode) { + var statusImageSource = ""; + if (this._resource.statusCode < 300 || this._resource.statusCode === 304) + statusImageSource = "Images/successGreenDot.png"; + else if (this._resource.statusCode < 400) + statusImageSource = "Images/warningOrangeDot.png"; + else + statusImageSource = "Images/errorRedDot.png"; + + requestMethodElement.title = this._formatHeader(WebInspector.UIString("Request Method"), this._resource.requestMethod); + + var statusCodeFragment = document.createDocumentFragment(); + statusCodeFragment.createChild("div", "header-name").textContent = WebInspector.UIString("Status Code") + ":"; + + var statusCodeImage = statusCodeFragment.createChild("img", "resource-status-image"); + statusCodeImage.src = statusImageSource; + statusCodeImage.title = this._resource.statusCode + " " + this._resource.statusText; + var value = statusCodeFragment.createChild("div", "header-value source-code"); + value.textContent = this._resource.statusCode + " " + this._resource.statusText; + if (this._resource.cached) + value.createChild("span", "status-from-cache").textContent = " " + WebInspector.UIString("(from cache)"); + + statusCodeElement.title = statusCodeFragment; + } + }, + + _refreshHeadersTitle: function(title, headersTreeElement, headersLength) + { + headersTreeElement.listItemElement.removeChildren(); + headersTreeElement.listItemElement.appendChild(document.createTextNode(title)); + + var headerCount = document.createElement("span"); + headerCount.addStyleClass("header-count"); + headerCount.textContent = WebInspector.UIString(" (%d)", headersLength); + headersTreeElement.listItemElement.appendChild(headerCount); + }, + + _refreshHeaders: function(title, headers, additionalRow, headersTreeElement) + { + headersTreeElement.removeChildren(); + + var length = headers.length; + this._refreshHeadersTitle(title, headersTreeElement, length); + headersTreeElement.hidden = !length; + for (var i = 0; i < length; ++i) { + var headerTreeElement = new TreeElement(null, null, false); + headerTreeElement.title = this._formatHeader(headers[i].header, headers[i].value); + headerTreeElement.selectable = false; + headersTreeElement.appendChild(headerTreeElement); + } + + if (additionalRow) { + var headerTreeElement = new TreeElement(null, null, false); + headerTreeElement.title = this._formatHeader(additionalRow.header, additionalRow.value); + headerTreeElement.selectable = false; + headersTreeElement.appendChild(headerTreeElement); + } + }, + + _refreshHeadersText: function(title, headers, headersText, headersTreeElement) + { + headersTreeElement.removeChildren(); + + this._refreshHeadersTitle(title, headersTreeElement, headers.length); + var headerTreeElement = new TreeElement(null, null, false); + headerTreeElement.selectable = false; + headersTreeElement.appendChild(headerTreeElement); + + var headersTextElement = document.createElement("span"); + headersTextElement.addStyleClass("header-value"); + headersTextElement.addStyleClass("source-code"); + headersTextElement.textContent = String(headersText).trim(); + headerTreeElement.listItemElement.appendChild(headersTextElement); + }, + + _toggleRequestHeadersText: function(event) + { + this._showRequestHeadersText = !this._showRequestHeadersText; + this._refreshRequestHeaders(); + }, + + _toggleResponseHeadersText: function(event) + { + this._showResponseHeadersText = !this._showResponseHeadersText; + this._refreshResponseHeaders(); + }, + + _createToggleButton: function(title) + { + var button = document.createElement("span"); + button.addStyleClass("header-toggle"); + button.textContent = title; + return button; + }, + + _createHeadersToggleButton: function(isHeadersTextShown) + { + var toggleTitle = isHeadersTextShown ? WebInspector.UIString("view parsed") : WebInspector.UIString("view source"); + return this._createToggleButton(toggleTitle); + } +} + +WebInspector.ResourceHeadersView.prototype.__proto__ = WebInspector.View.prototype; +/* ResourceCookiesView.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @extends {WebInspector.View} + * @constructor + */ +WebInspector.ResourceCookiesView = function(resource) +{ + WebInspector.View.call(this); + this.element.addStyleClass("resource-cookies-view"); + + this._resource = resource; + + resource.addEventListener("requestHeaders changed", this._refreshCookies, this); + resource.addEventListener("responseHeaders changed", this._refreshCookies, this); +} + +WebInspector.ResourceCookiesView.prototype = { + wasShown: function() + { + if (!this._gotCookies) { + if (!this._emptyView) { + this._emptyView = new WebInspector.EmptyView(WebInspector.UIString("This request has no cookies.")); + this._emptyView.show(this.element); + } + return; + } + + if (!this._cookiesTable) + this._buildCookiesTable(); + }, + + get _gotCookies() + { + return !!(this._resource.requestCookies || this._resource.responseCookies); + }, + + _buildCookiesTable: function() + { + this.detachChildViews(); + + this._cookiesTable = new WebInspector.CookiesTable(null, true); + this._cookiesTable.addCookiesFolder(WebInspector.UIString("Request Cookies"), this._resource.requestCookies); + this._cookiesTable.addCookiesFolder(WebInspector.UIString("Response Cookies"), this._resource.responseCookies); + this._cookiesTable.show(this.element); + }, + + _refreshCookies: function() + { + delete this._cookiesTable; + if (!this._gotCookies || !this.isShowing()) + return; + this._buildCookiesTable(); + this._cookiesTable.updateWidths(); + } +} + +WebInspector.ResourceCookiesView.prototype.__proto__ = WebInspector.View.prototype; +/* ResourceTimingView.js */ + +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @extends {WebInspector.View} + * @constructor + */ +WebInspector.ResourceTimingView = function(resource) +{ + WebInspector.View.call(this); + this.element.addStyleClass("resource-timing-view"); + + this._resource = resource; + + resource.addEventListener("timing changed", this._refresh, this); +} + +WebInspector.ResourceTimingView.prototype = { + wasShown: function() + { + if (!this._resource.timing) { + if (!this._emptyView) { + this._emptyView = new WebInspector.EmptyView(WebInspector.UIString("This request has no detailed timing info.")); + this._emptyView.show(this.element); + this.innerView = this._emptyView; + } + return; + } + + if (this._emptyView) { + this._emptyView.detach(); + delete this._emptyView; + } + + this._refresh(); + }, + + _refresh: function() + { + if (this._tableElement) + this._tableElement.parentElement.removeChild(this._tableElement); + + this._tableElement = WebInspector.ResourceTimingView.createTimingTable(this._resource); + this.element.appendChild(this._tableElement); + } +} + +WebInspector.ResourceTimingView.createTimingTable = function(resource) +{ + var tableElement = document.createElement("table"); + var rows = []; + + function addRow(title, className, start, end) + { + var row = {}; + row.title = title; + row.className = className; + row.start = start; + row.end = end; + rows.push(row); + } + + if (resource.timing.proxyStart !== -1) + addRow(WebInspector.UIString("Proxy"), "proxy", resource.timing.proxyStart, resource.timing.proxyEnd); + + if (resource.timing.dnsStart !== -1) + addRow(WebInspector.UIString("DNS Lookup"), "dns", resource.timing.dnsStart, resource.timing.dnsEnd); + + if (resource.timing.connectStart !== -1) { + if (resource.connectionReused) + addRow(WebInspector.UIString("Blocking"), "connecting", resource.timing.connectStart, resource.timing.connectEnd); + else { + var connectStart = resource.timing.connectStart; + // Connection includes DNS, subtract it here. + if (resource.timing.dnsStart !== -1) + connectStart += resource.timing.dnsEnd - resource.timing.dnsStart; + addRow(WebInspector.UIString("Connecting"), "connecting", connectStart, resource.timing.connectEnd); + } + } + + if (resource.timing.sslStart !== -1) + addRow(WebInspector.UIString("SSL"), "ssl", resource.timing.sslStart, resource.timing.sslEnd); + + var sendStart = resource.timing.sendStart; + if (resource.timing.sslStart !== -1) + sendStart += resource.timing.sslEnd - resource.timing.sslStart; + + addRow(WebInspector.UIString("Sending"), "sending", resource.timing.sendStart, resource.timing.sendEnd); + addRow(WebInspector.UIString("Waiting"), "waiting", resource.timing.sendEnd, resource.timing.receiveHeadersEnd); + addRow(WebInspector.UIString("Receiving"), "receiving", (resource.responseReceivedTime - resource.timing.requestTime) * 1000, (resource.endTime - resource.timing.requestTime) * 1000); + + const chartWidth = 200; + var total = (resource.endTime - resource.timing.requestTime) * 1000; + var scale = chartWidth / total; + + for (var i = 0; i < rows.length; ++i) { + var tr = document.createElement("tr"); + tableElement.appendChild(tr); + + var td = document.createElement("td"); + td.textContent = rows[i].title; + tr.appendChild(td); + + td = document.createElement("td"); + td.width = chartWidth + "px"; + + var row = document.createElement("div"); + row.className = "network-timing-row"; + td.appendChild(row); + + var bar = document.createElement("span"); + bar.className = "network-timing-bar " + rows[i].className; + bar.style.left = scale * rows[i].start + "px"; + bar.style.right = scale * (total - rows[i].end) + "px"; + bar.style.backgroundColor = rows[i].color; + bar.textContent = "\u200B"; // Important for 0-time items to have 0 width. + row.appendChild(bar); + + var title = document.createElement("span"); + title.className = "network-timing-bar-title"; + 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"; + title.textContent = Number.secondsToString((rows[i].end - rows[i].start) / 1000); + row.appendChild(title); + + tr.appendChild(td); + } + return tableElement; +} + +WebInspector.ResourceTimingView.prototype.__proto__ = WebInspector.View.prototype; +/* ResourceJSONView.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @extends {WebInspector.ResourceView} + * @constructor + */ +WebInspector.ResourceJSONView = function(resource, parsedJSON) +{ + WebInspector.ResourceView.call(this, resource); + this._parsedJSON = parsedJSON; + this.element.addStyleClass("json"); +} + +WebInspector.ResourceJSONView.parseJSON = function(text) +{ + var prefix = ""; + + // Trim while(1), for(;;), weird numbers, etc. We need JSON start. + var start = /[{[]/.exec(text); + if (start && start.index) { + prefix = text.substring(0, start.index); + text = text.substring(start.index); + } + + try { + return new WebInspector.ParsedJSON(JSON.parse(text), prefix, ""); + } catch (e) { + return; + } +} + +WebInspector.ResourceJSONView.parseJSONP = function(text) +{ + // Taking everything between first and last parentheses + var start = text.indexOf("("); + var end = text.lastIndexOf(")"); + if (start == -1 || end == -1 || end < start) + return; + + var prefix = text.substring(0, start + 1); + var suffix = text.substring(end); + text = text.substring(start + 1, end); + + try { + return new WebInspector.ParsedJSON(JSON.parse(text), prefix, suffix); + } catch (e) { + return; + } +} + +WebInspector.ResourceJSONView.prototype = { + hasContent: function() + { + return true; + }, + + wasShown: function() + { + this._initialize(); + }, + + _initialize: function() + { + if (this._initialized) + return; + this._initialized = true; + + var obj = WebInspector.RemoteObject.fromLocalObject(this._parsedJSON.data); + var title = this._parsedJSON.prefix + obj.description + this._parsedJSON.suffix; + var section = new WebInspector.ObjectPropertiesSection(obj, title); + section.expand(); + section.editable = false; + this.element.appendChild(section.element); + } +} + +WebInspector.ResourceJSONView.prototype.__proto__ = WebInspector.ResourceView.prototype; + +/** + * @constructor + */ +WebInspector.ParsedJSON = function(data, prefix, suffix) +{ + this.data = data; + this.prefix = prefix; + this.suffix = suffix; +} +/* ResourceHTMLView.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.ResourceView} + */ +WebInspector.ResourceHTMLView = function(resource) +{ + WebInspector.ResourceView.call(this, resource); + this.element.addStyleClass("html"); +} + +WebInspector.ResourceHTMLView.prototype = { + hasContent: function() + { + return true; + }, + + wasShown: function() + { + this._createIFrame(); + }, + + willHide: function(parentElement) + { + this.element.removeChildren(); + }, + + _createIFrame: function() + { + // We need to create iframe again each time because contentDocument + // is deleted when iframe is removed from its parent. + this.element.removeChildren(); + var iframe = document.createElement("iframe"); + this.element.appendChild(iframe); + iframe.setAttribute("sandbox", ""); // Forbid to run JavaScript and set unique origin. + + iframe.contentDocument.body.innerHTML = this.resource.content; + } +} + +WebInspector.ResourceHTMLView.prototype.__proto__ = WebInspector.ResourceView.prototype; +/* ResourceResponseView.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @extends {WebInspector.ResourceContentView} + * @constructor + */ +WebInspector.ResourceResponseView = function(resource) +{ + WebInspector.ResourceContentView.call(this, resource); +} + +WebInspector.ResourceResponseView.prototype = { + get sourceView() + { + if (!this._sourceView && WebInspector.ResourceView.hasTextContent(this.resource)) + this._sourceView = new WebInspector.ResourceSourceFrame(this.resource); + return this._sourceView; + }, + + contentLoaded: function() + { + if (!this.resource.content || !this.sourceView) { + if (!this._emptyView) { + this._emptyView = new WebInspector.EmptyView(WebInspector.UIString("This request has no response data available.")); + this._emptyView.show(this.element); + this.innerView = this._emptyView; + } + } else { + if (this._emptyView) { + this._emptyView.detach(); + delete this._emptyView; + } + + this.sourceView.show(this.element); + this.innerView = this.sourceView; + } + } +} + +WebInspector.ResourceResponseView.prototype.__proto__ = WebInspector.ResourceContentView.prototype; +/* ResourcePreviewView.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @extends {WebInspector.ResourceContentView} + * @constructor + */ +WebInspector.ResourcePreviewView = function(resource, responseView) +{ + WebInspector.ResourceContentView.call(this, resource); + this._responseView = responseView; +} + +WebInspector.ResourcePreviewView.prototype = { + contentLoaded: function() + { + if (!this.resource.content) { + if (!this._emptyView) { + this._emptyView = this._createEmptyView(); + this._emptyView.show(this.element); + this.innerView = this._emptyView; + } + } else { + if (this._emptyView) { + this._emptyView.detach(); + delete this._emptyView; + } + + if (!this._previewView) + this._previewView = this._createPreviewView(); + this._previewView.show(this.element); + this.innerView = this._previewView; + } + }, + + _createEmptyView: function() + { + return new WebInspector.EmptyView(WebInspector.UIString("This request has no preview available.")); + }, + + _createPreviewView: function() + { + if (this.resource.hasErrorStatusCode() && this.resource.content) + return new WebInspector.ResourceHTMLView(this.resource); + + if (this.resource.category === WebInspector.resourceCategories.xhr && this.resource.content) { + var parsedJSON = WebInspector.ResourceJSONView.parseJSON(this.resource.content); + if (parsedJSON) + return new WebInspector.ResourceJSONView(this.resource, parsedJSON); + } + + if (this.resource.content && this.resource.category === WebInspector.resourceCategories.scripts && this.resource.mimeType === "application/json") { + var parsedJSONP = WebInspector.ResourceJSONView.parseJSONP(this.resource.content); + if (parsedJSONP) + return new WebInspector.ResourceJSONView(this.resource, parsedJSONP); + } + + if (this._responseView.sourceView) + return this._responseView.sourceView; + + if (this.resource.category === WebInspector.resourceCategories.other) + return this._createEmptyView(); + + return WebInspector.ResourceView.nonSourceViewForResource(this.resource); + } +} + +WebInspector.ResourcePreviewView.prototype.__proto__ = WebInspector.ResourceContentView.prototype; +/* ScriptFormatter.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + */ +WebInspector.ScriptFormatter = function() +{ + this._tasks = []; +} + +/** + * @param {Array.} lineEndings + * @param {DebuggerAgent.Location} location + * @return {number} + */ +WebInspector.ScriptFormatter.locationToPosition = function(lineEndings, location) +{ + var position = location.lineNumber ? lineEndings[location.lineNumber - 1] + 1 : 0; + return position + location.columnNumber; +} + +/** + * @param {Array.} lineEndings + * @param {number} position + * @return {DebuggerAgent.Location} + */ +WebInspector.ScriptFormatter.positionToLocation = function(lineEndings, position) +{ + var lineNumber = lineEndings.upperBound(position - 1); + if (!lineNumber) + var columnNumber = position; + else + var columnNumber = position - lineEndings[lineNumber - 1] - 1; + return new WebInspector.DebuggerModel.Location(lineNumber, columnNumber); +} + +WebInspector.ScriptFormatter.prototype = { + /** + * @param {string} mimeType + * @param {string} content + * @param {function(string, WebInspector.FormattedSourceMapping)} callback + */ + formatContent: function(mimeType, content, callback) + { + content = content.replace(/\r\n?|[\n\u2028\u2029]/g, "\n").replace(/^\uFEFF/, ''); + const method = "format"; + var parameters = { mimeType: mimeType, content: content, indentString: " " }; + this._tasks.push({ data: parameters, callback: callback }); + this._worker.postMessage({ method: method, params: parameters }); + }, + + /** + * @param {WebInspector.Event} event + */ + _didFormatContent: function(event) + { + var task = this._tasks.shift(); + var originalContent = task.data.content; + var formattedContent = event.data.content; + var mapping = event.data["mapping"]; + var sourceMapping = new WebInspector.FormattedSourceMapping(originalContent.lineEndings(), formattedContent.lineEndings(), mapping); + task.callback(formattedContent, sourceMapping); + }, + + /** + * @return {Worker} + */ + get _worker() + { + if (!this._cachedWorker) { + this._cachedWorker = new Worker("ScriptFormatterWorker.js"); + this._cachedWorker.onmessage = this._didFormatContent.bind(this); + } + return this._cachedWorker; + } +} + +/** + * @constructor + */ +WebInspector.FormatterMappingPayload = function() +{ + this.original = []; + this.formatted = []; +} + +/** + * @constructor + * @param {Array.} originalLineEndings + * @param {Array.} formattedLineEndings + * @param {WebInspector.FormatterMappingPayload} mapping + */ +WebInspector.FormattedSourceMapping = function(originalLineEndings, formattedLineEndings, mapping) +{ + this._originalLineEndings = originalLineEndings; + this._formattedLineEndings = formattedLineEndings; + this._mapping = mapping; +} + +WebInspector.FormattedSourceMapping.prototype = { + /** + * @param {DebuggerAgent.Location} location + * @return {DebuggerAgent.Location} + */ + originalToFormatted: function(location) + { + var originalPosition = WebInspector.ScriptFormatter.locationToPosition(this._originalLineEndings, location); + var formattedPosition = this._convertPosition(this._mapping.original, this._mapping.formatted, originalPosition); + return WebInspector.ScriptFormatter.positionToLocation(this._formattedLineEndings, formattedPosition); + }, + + /** + * @param {DebuggerAgent.Location} location + * @return {DebuggerAgent.Location} + */ + formattedToOriginal: function(location) + { + var formattedPosition = WebInspector.ScriptFormatter.locationToPosition(this._formattedLineEndings, location); + var originalPosition = this._convertPosition(this._mapping.formatted, this._mapping.original, formattedPosition); + return WebInspector.ScriptFormatter.positionToLocation(this._originalLineEndings, originalPosition); + }, + + /** + * @param {Array.} positions1 + * @param {Array.} positions2 + * @param {number} position + * @return {number} + */ + _convertPosition: function(positions1, positions2, position) + { + var index = positions1.upperBound(position) - 1; + var convertedPosition = positions2[index] + position - positions1[index]; + if (index < positions2.length - 1 && convertedPosition > positions2[index + 1]) + convertedPosition = positions2[index + 1]; + return convertedPosition; + } +} +/* DOMSyntaxHighlighter.js */ + +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + */ +WebInspector.DOMSyntaxHighlighter = function(mimeType, stripExtraWhitespace) +{ + this._tokenizer = WebInspector.SourceTokenizer.Registry.getInstance().getTokenizer(mimeType); + this._stripExtraWhitespace = stripExtraWhitespace; +} + +WebInspector.DOMSyntaxHighlighter.prototype = { + createSpan: function(content, className) + { + var span = document.createElement("span"); + span.className = "webkit-" + className; + if (this._stripExtraWhitespace) + content = content.replace(/^[\n\r]*/, "").replace(/\s*$/, ""); + span.appendChild(document.createTextNode(content)); + return span; + }, + + syntaxHighlightNode: function(node) + { + this._tokenizer.condition = this._tokenizer.createInitialCondition(); + var lines = node.textContent.split("\n"); + node.removeChildren(); + + for (var i = lines[0].length ? 0 : 1; i < lines.length; ++i) { + var line = lines[i]; + var plainTextStart = 0; + this._tokenizer.line = line; + var column = 0; + do { + var newColumn = this._tokenizer.nextToken(column); + var tokenType = this._tokenizer.tokenType; + if (tokenType) { + if (column > plainTextStart) { + var plainText = line.substring(plainTextStart, column); + node.appendChild(document.createTextNode(plainText)); + } + var token = line.substring(column, newColumn); + node.appendChild(this.createSpan(token, tokenType)); + plainTextStart = newColumn; + } + column = newColumn; + } while (column < line.length) + + if (plainTextStart < line.length) { + var plainText = line.substring(plainTextStart, line.length); + node.appendChild(document.createTextNode(plainText)); + } + if (i < lines.length - 1) + node.appendChild(document.createElement("br")); + } + } +} +/* TextEditorModel.js */ + +/* + * Copyright (C) 2009 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + */ +WebInspector.TextRange = function(startLine, startColumn, endLine, endColumn) +{ + this.startLine = startLine; + this.startColumn = startColumn; + this.endLine = endLine; + this.endColumn = endColumn; +} + +WebInspector.TextRange.prototype = { + isEmpty: function() + { + return this.startLine === this.endLine && this.startColumn === this.endColumn; + }, + + get linesCount() + { + return this.endLine - this.startLine; + }, + + collapseToEnd: function() + { + return new WebInspector.TextRange(this.endLine, this.endColumn, this.endLine, this.endColumn); + }, + + normalize: function() + { + if (this.startLine > this.endLine || (this.startLine === this.endLine && this.startColumn > this.endColumn)) + return new WebInspector.TextRange(this.endLine, this.endColumn, this.startLine, this.startColumn); + else + return this; + }, + + clone: function() + { + return new WebInspector.TextRange(this.startLine, this.startColumn, this.endLine, this.endColumn); + } +} + +/** + * @constructor + */ +WebInspector.TextEditorModel = function() +{ + this._lines = [""]; + this._attributes = []; + this._undoStack = []; + this._noPunctuationRegex = /[^ !%&()*+,-.:;<=>?\[\]\^{|}~]+/; + this._lineBreak = "\n"; +} + +WebInspector.TextEditorModel.Indent = { + TwoSpaces: " ", + FourSpaces: " ", + EightSpaces: " ", + TabCharacter: "\t" +} + +WebInspector.TextEditorModel.endsWithBracketRegex = /[{(\[]\s*$/; + +WebInspector.TextEditorModel.prototype = { + set changeListener(changeListener) + { + this._changeListener = changeListener; + }, + + get linesCount() + { + return this._lines.length; + }, + + get text() + { + return this._lines.join(this._lineBreak); + }, + + get lineBreak() + { + return this._lineBreak; + }, + + line: function(lineNumber) + { + if (lineNumber >= this._lines.length) + throw "Out of bounds:" + lineNumber; + return this._lines[lineNumber]; + }, + + lineLength: function(lineNumber) + { + return this._lines[lineNumber].length; + }, + + setText: function(range, text) + { + text = text || ""; + if (!range) { + range = new WebInspector.TextRange(0, 0, this._lines.length - 1, this._lines[this._lines.length - 1].length); + this._lineBreak = /\r\n/.test(text) ? "\r\n" : "\n"; + } + var command = this._pushUndoableCommand(range); + var newRange = this._innerSetText(range, text); + command.range = newRange.clone(); + + if (this._changeListener) + this._changeListener(range, newRange, command.text, text); + return newRange; + }, + + _innerSetText: function(range, text) + { + this._eraseRange(range); + if (text === "") + return new WebInspector.TextRange(range.startLine, range.startColumn, range.startLine, range.startColumn); + + var newLines = text.split(/\r?\n/); + + var prefix = this._lines[range.startLine].substring(0, range.startColumn); + var suffix = this._lines[range.startLine].substring(range.startColumn); + + var postCaret = prefix.length; + // Insert text. + if (newLines.length === 1) { + this._setLine(range.startLine, prefix + newLines[0] + suffix); + postCaret += newLines[0].length; + } else { + this._setLine(range.startLine, prefix + newLines[0]); + for (var i = 1; i < newLines.length; ++i) + this._insertLine(range.startLine + i, newLines[i]); + this._setLine(range.startLine + newLines.length - 1, newLines[newLines.length - 1] + suffix); + postCaret = newLines[newLines.length - 1].length; + } + return new WebInspector.TextRange(range.startLine, range.startColumn, + range.startLine + newLines.length - 1, postCaret); + }, + + _eraseRange: function(range) + { + if (range.isEmpty()) + return; + + var prefix = this._lines[range.startLine].substring(0, range.startColumn); + var suffix = this._lines[range.endLine].substring(range.endColumn); + + if (range.endLine > range.startLine) + this._removeLines(range.startLine + 1, range.endLine - range.startLine); + this._setLine(range.startLine, prefix + suffix); + }, + + _setLine: function(lineNumber, text) + { + this._lines[lineNumber] = text; + }, + + _removeLines: function(fromLine, count) + { + this._lines.splice(fromLine, count); + this._attributes.splice(fromLine, count); + }, + + _insertLine: function(lineNumber, text) + { + this._lines.splice(lineNumber, 0, text); + this._attributes.splice(lineNumber, 0, {}); + }, + + wordRange: function(lineNumber, column) + { + return new WebInspector.TextRange(lineNumber, this.wordStart(lineNumber, column, true), lineNumber, this.wordEnd(lineNumber, column, true)); + }, + + wordStart: function(lineNumber, column, gapless) + { + var line = this._lines[lineNumber]; + var prefix = line.substring(0, column).split("").reverse().join(""); + var prefixMatch = this._noPunctuationRegex.exec(prefix); + return prefixMatch && (!gapless || prefixMatch.index === 0) ? column - prefixMatch.index - prefixMatch[0].length : column; + }, + + wordEnd: function(lineNumber, column, gapless) + { + var line = this._lines[lineNumber]; + var suffix = line.substring(column); + var suffixMatch = this._noPunctuationRegex.exec(suffix); + return suffixMatch && (!gapless || suffixMatch.index === 0) ? column + suffixMatch.index + suffixMatch[0].length : column; + }, + + copyRange: function(range) + { + if (!range) + range = new WebInspector.TextRange(0, 0, this._lines.length - 1, this._lines[this._lines.length - 1].length); + + var clip = []; + if (range.startLine === range.endLine) { + clip.push(this._lines[range.startLine].substring(range.startColumn, range.endColumn)); + return clip.join(this._lineBreak); + } + clip.push(this._lines[range.startLine].substring(range.startColumn)); + for (var i = range.startLine + 1; i < range.endLine; ++i) + clip.push(this._lines[i]); + clip.push(this._lines[range.endLine].substring(0, range.endColumn)); + return clip.join(this._lineBreak); + }, + + setAttribute: function(line, name, value) + { + var attrs = this._attributes[line]; + if (!attrs) { + attrs = {}; + this._attributes[line] = attrs; + } + attrs[name] = value; + }, + + getAttribute: function(line, name) + { + var attrs = this._attributes[line]; + return attrs ? attrs[name] : null; + }, + + removeAttribute: function(line, name) + { + var attrs = this._attributes[line]; + if (attrs) + delete attrs[name]; + }, + + _pushUndoableCommand: function(range) + { + var command = { + text: this.copyRange(range), + startLine: range.startLine, + startColumn: range.startColumn, + endLine: range.startLine, + endColumn: range.startColumn + }; + if (this._inUndo) + this._redoStack.push(command); + else { + if (!this._inRedo) + this._redoStack = []; + this._undoStack.push(command); + } + return command; + }, + + undo: function(callback) + { + this._markRedoableState(); + + this._inUndo = true; + var range = this._doUndo(this._undoStack, callback); + delete this._inUndo; + + return range; + }, + + redo: function(callback) + { + this.markUndoableState(); + + this._inRedo = true; + var range = this._doUndo(this._redoStack, callback); + delete this._inRedo; + + return range; + }, + + _doUndo: function(stack, callback) + { + var range = null; + for (var i = stack.length - 1; i >= 0; --i) { + var command = stack[i]; + stack.length = i; + + range = this.setText(command.range, command.text); + if (callback) + callback(command.range, range); + if (i > 0 && stack[i - 1].explicit) + return range; + } + return range; + }, + + markUndoableState: function() + { + if (this._undoStack.length) + this._undoStack[this._undoStack.length - 1].explicit = true; + }, + + _markRedoableState: function() + { + if (this._redoStack.length) + this._redoStack[this._redoStack.length - 1].explicit = true; + }, + + resetUndoStack: function() + { + this._undoStack = []; + } +} + +WebInspector.settings.textEditorIndent = WebInspector.settings.createSetting("textEditorIndent", WebInspector.TextEditorModel.Indent.FourSpaces); +/* TextEditorHighlighter.js */ + +/* + * Copyright (C) 2009 Google Inc. All rights reserved. + * Copyright (C) 2009 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + */ +WebInspector.TextEditorHighlighter = function(textModel, damageCallback) +{ + this._textModel = textModel; + this._tokenizer = WebInspector.SourceTokenizer.Registry.getInstance().getTokenizer("text/html"); + this._damageCallback = damageCallback; + this._highlightChunkLimit = 1000; +} + +WebInspector.TextEditorHighlighter._MaxLineCount = 10000; + +WebInspector.TextEditorHighlighter.prototype = { + set mimeType(mimeType) + { + var tokenizer = WebInspector.SourceTokenizer.Registry.getInstance().getTokenizer(mimeType); + if (tokenizer) + this._tokenizer = tokenizer; + }, + + set highlightChunkLimit(highlightChunkLimit) + { + this._highlightChunkLimit = highlightChunkLimit; + }, + + /** + * @param {boolean=} forceRun + */ + highlight: function(endLine, forceRun) + { + if (this._textModel.linesCount > WebInspector.TextEditorHighlighter._MaxLineCount) + return; + + // First check if we have work to do. + var state = this._textModel.getAttribute(endLine - 1, "highlight"); + if (state && state.postConditionStringified) { + // Last line is highlighted, just exit. + return; + } + + this._requestedEndLine = endLine; + + if (this._highlightTimer && !forceRun) { + // There is a timer scheduled, it will catch the new job based on the new endLine set. + return; + } + + // We will be highlighting. First rewind to the last highlighted line to gain proper highlighter context. + var startLine = endLine; + while (startLine > 0) { + state = this._textModel.getAttribute(startLine - 1, "highlight"); + if (state && state.postConditionStringified) + break; + startLine--; + } + + // Do small highlight synchronously. This will provide instant highlight on PageUp / PageDown, gentle scrolling. + this._highlightInChunks(startLine, endLine); + }, + + updateHighlight: function(startLine, endLine) + { + if (this._textModel.linesCount > WebInspector.TextEditorHighlighter._MaxLineCount) + return; + + // Start line was edited, we should highlight everything until endLine. + this._clearHighlightState(startLine); + + if (startLine) { + var state = this._textModel.getAttribute(startLine - 1, "highlight"); + if (!state || !state.postConditionStringified) { + // Highlighter did not reach this point yet, nothing to update. It will reach it on subsequent timer tick and do the job. + return false; + } + } + + var restored = this._highlightLines(startLine, endLine); + if (!restored) { + for (var i = this._lastHighlightedLine; i < this._textModel.linesCount; ++i) { + var state = this._textModel.getAttribute(i, "highlight"); + if (!state && i > endLine) + break; + this._textModel.setAttribute(i, "highlight-outdated", state); + this._textModel.removeAttribute(i, "highlight"); + } + + if (this._highlightTimer) { + clearTimeout(this._highlightTimer); + this._requestedEndLine = endLine; + this._highlightTimer = setTimeout(this._highlightInChunks.bind(this, this._lastHighlightedLine, this._requestedEndLine), 10); + } + } + return restored; + }, + + _highlightInChunks: function(startLine, endLine) + { + delete this._highlightTimer; + + // First we always check if we have work to do. Could be that user scrolled back and we can quit. + var state = this._textModel.getAttribute(this._requestedEndLine - 1, "highlight"); + if (state && state.postConditionStringified) + return; + + if (this._requestedEndLine !== endLine) { + // User keeps updating the job in between of our timer ticks. Just reschedule self, don't eat CPU (they must be scrolling). + this._highlightTimer = setTimeout(this._highlightInChunks.bind(this, startLine, this._requestedEndLine), 100); + return; + } + + // The textModel may have been already updated. + if (this._requestedEndLine > this._textModel.linesCount) + this._requestedEndLine = this._textModel.linesCount; + + this._highlightLines(startLine, this._requestedEndLine); + + // Schedule tail highlight if necessary. + if (this._lastHighlightedLine < this._requestedEndLine) + this._highlightTimer = setTimeout(this._highlightInChunks.bind(this, this._lastHighlightedLine, this._requestedEndLine), 10); + }, + + _highlightLines: function(startLine, endLine) + { + // Restore highlighter context taken from previous line. + var state = this._textModel.getAttribute(startLine - 1, "highlight"); + var postConditionStringified = state ? state.postConditionStringified : JSON.stringify(this._tokenizer.createInitialCondition()); + + var tokensCount = 0; + for (var lineNumber = startLine; lineNumber < endLine; ++lineNumber) { + state = this._selectHighlightState(lineNumber, postConditionStringified); + if (state.postConditionStringified) { + // This line is already highlighted. + postConditionStringified = state.postConditionStringified; + } else { + var lastHighlightedColumn = 0; + if (state.midConditionStringified) { + lastHighlightedColumn = state.lastHighlightedColumn; + postConditionStringified = state.midConditionStringified; + } + + var line = this._textModel.line(lineNumber); + this._tokenizer.line = line; + this._tokenizer.condition = JSON.parse(postConditionStringified); + + // Highlight line. + do { + var newColumn = this._tokenizer.nextToken(lastHighlightedColumn); + var tokenType = this._tokenizer.tokenType; + if (tokenType) + state[lastHighlightedColumn] = { length: newColumn - lastHighlightedColumn, tokenType: tokenType }; + lastHighlightedColumn = newColumn; + if (++tokensCount > this._highlightChunkLimit) + break; + } while (lastHighlightedColumn < line.length); + + postConditionStringified = JSON.stringify(this._tokenizer.condition); + + if (lastHighlightedColumn < line.length) { + // Too much work for single chunk - exit. + state.lastHighlightedColumn = lastHighlightedColumn; + state.midConditionStringified = postConditionStringified; + break; + } else { + delete state.lastHighlightedColumn; + delete state.midConditionStringified; + state.postConditionStringified = postConditionStringified; + } + } + + var nextLineState = this._textModel.getAttribute(lineNumber + 1, "highlight"); + if (nextLineState && nextLineState.preConditionStringified === state.postConditionStringified) { + // Following lines are up to date, no need re-highlight. + ++lineNumber; + this._damageCallback(startLine, lineNumber); + + // Advance the "pointer" to the last highlighted line within the given chunk. + for (; lineNumber < endLine; ++lineNumber) { + state = this._textModel.getAttribute(lineNumber, "highlight"); + if (!state || !state.postConditionStringified) + break; + } + this._lastHighlightedLine = lineNumber; + return true; + } + } + + this._damageCallback(startLine, lineNumber); + this._lastHighlightedLine = lineNumber; + return false; + }, + + _selectHighlightState: function(lineNumber, preConditionStringified) + { + var state = this._textModel.getAttribute(lineNumber, "highlight"); + if (state && state.preConditionStringified === preConditionStringified) + return state; + + var outdatedState = this._textModel.getAttribute(lineNumber, "highlight-outdated"); + if (outdatedState && outdatedState.preConditionStringified === preConditionStringified) { + // Swap states. + this._textModel.setAttribute(lineNumber, "highlight", outdatedState); + this._textModel.setAttribute(lineNumber, "highlight-outdated", state); + return outdatedState; + } + + if (state) + this._textModel.setAttribute(lineNumber, "highlight-outdated", state); + + state = {}; + state.preConditionStringified = preConditionStringified; + this._textModel.setAttribute(lineNumber, "highlight", state); + return state; + }, + + _clearHighlightState: function(lineNumber) + { + this._textModel.removeAttribute(lineNumber, "highlight"); + this._textModel.removeAttribute(lineNumber, "highlight-outdated"); + } +} +/* SourceTokenizer.js */ + +/* Generated by re2c 0.13.5 on Tue Jan 26 01:16:33 2010 */ +/* + * Copyright (C) 2009 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + */ +WebInspector.SourceTokenizer = function() +{ +} + +WebInspector.SourceTokenizer.prototype = { + set line(line) { + this._line = line; + }, + + set condition(condition) + { + this._condition = condition; + }, + + get condition() + { + return this._condition; + }, + + getLexCondition: function() + { + return this.condition.lexCondition; + }, + + setLexCondition: function(lexCondition) + { + this.condition.lexCondition = lexCondition; + }, + + _charAt: function(cursor) + { + return cursor < this._line.length ? this._line.charAt(cursor) : "\n"; + }, + + createInitialCondition: function() + { + }, + + nextToken: function(cursor) + { + } +} + +/** + * @constructor + */ +WebInspector.SourceTokenizer.Registry = function() { + this._tokenizers = {}; + this._tokenizerConstructors = { + "text/css": "SourceCSSTokenizer", + "text/html": "SourceHTMLTokenizer", + "text/javascript": "SourceJavaScriptTokenizer" + }; +} + +WebInspector.SourceTokenizer.Registry.getInstance = function() +{ + if (!WebInspector.SourceTokenizer.Registry._instance) + WebInspector.SourceTokenizer.Registry._instance = new WebInspector.SourceTokenizer.Registry(); + return WebInspector.SourceTokenizer.Registry._instance; +} + +WebInspector.SourceTokenizer.Registry.prototype = { + getTokenizer: function(mimeType) + { + if (!this._tokenizerConstructors[mimeType]) + return null; + var tokenizerClass = this._tokenizerConstructors[mimeType]; + var tokenizer = this._tokenizers[tokenizerClass]; + if (!tokenizer) { + tokenizer = new WebInspector[tokenizerClass](); + this._tokenizers[tokenizerClass] = tokenizer; + } + return tokenizer; + } +} +/* SourceCSSTokenizer.js */ + +/* Generated by re2c 0.13.5 on Mon Dec 19 17:28:29 2011 */ +/* + * Copyright (C) 2009 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// Generate js file as follows: + +/* +re2c -isc Source/WebCore/inspector/front-end/SourceCSSTokenizer.re2js \ + | sed 's|^yy\([^:]*\)*\:|case \1:|' \ + | sed 's|[*]cursor[+][+]|this._charAt(cursor++)|' \ + | sed 's|[[*][+][+]cursor|this._charAt(++cursor)|' \ + | sed 's|[*]cursor|this._charAt(cursor)|' \ + | sed 's|yych = \*\([^;]*\)|yych = this._charAt\1|' \ + | sed 's|{ gotoCase = \([^; continue; };]*\)|{ gotoCase = \1; continue; }|' \ + | sed 's|unsigned\ int|var|' \ + | sed 's|var\ yych|case 1: case 1: var yych|' > Source/WebCore/inspector/front-end/SourceCSSTokenizer.js +*/ + +/** + * @constructor + * @extends {WebInspector.SourceTokenizer} + */ +WebInspector.SourceCSSTokenizer = function() +{ + WebInspector.SourceTokenizer.call(this); + + this._propertyKeywords = WebInspector.CSSCompletions.cssNameCompletions.keySet(); + this._colorKeywords = WebInspector.CSSKeywordCompletions.colors(); + + this._valueKeywords = [ + "above", "absolute", "activeborder", "activecaption", "afar", "after-white-space", "ahead", "alias", "all", "all-scroll", + "alternate", "always", "amharic", "amharic-abegede", "antialiased", "appworkspace", "arabic-indic", "armenian", "asterisks", + "auto", "avoid", "background", "backwards", "baseline", "below", "bidi-override", "binary", "bengali", "blink", + "block", "block-axis", "bold", "bolder", "border", "border-box", "both", "bottom", "break-all", "break-word", "button", + "button-bevel", "buttonface", "buttonhighlight", "buttonshadow", "buttontext", "cambodian", "capitalize", "caps-lock-indicator", + "caption", "captiontext", "caret", "cell", "center", "checkbox", "circle", "cjk-earthly-branch", "cjk-heavenly-stem", "cjk-ideographic", + "clear", "clip", "close-quote", "col-resize", "collapse", "compact", "condensed", "contain", "content", "content-box", "context-menu", + "continuous", "copy", "cover", "crop", "cross", "crosshair", "currentcolor", "cursive", "dashed", "decimal", "decimal-leading-zero", "default", + "default-button", "destination-atop", "destination-in", "destination-out", "destination-over", "devanagari", "disc", "discard", "document", + "dot-dash", "dot-dot-dash", "dotted", "double", "down", "e-resize", "ease", "ease-in", "ease-in-out", "ease-out", "element", + "ellipsis", "embed", "end", "ethiopic", "ethiopic-abegede", "ethiopic-abegede-am-et", "ethiopic-abegede-gez", + "ethiopic-abegede-ti-er", "ethiopic-abegede-ti-et", "ethiopic-halehame-aa-er", "ethiopic-halehame-aa-et", + "ethiopic-halehame-am-et", "ethiopic-halehame-gez", "ethiopic-halehame-om-et", "ethiopic-halehame-sid-et", + "ethiopic-halehame-so-et", "ethiopic-halehame-ti-er", "ethiopic-halehame-ti-et", "ethiopic-halehame-tig", "ew-resize", "expanded", + "extra-condensed", "extra-expanded", "fantasy", "fast", "fill", "fixed", "flat", "footnotes", "forwards", "from", "geometricPrecision", + "georgian", "graytext", "groove", "gujarati", "gurmukhi", "hand", "hangul", "hangul-consonant", "hebrew", "help", + "hidden", "hide", "higher", "highlight", "highlighttext", "hiragana", "hiragana-iroha", "horizontal", "hsl", "hsla", "icon", "ignore", + "inactiveborder", "inactivecaption", "inactivecaptiontext", "infinite", "infobackground", "infotext", "inherit", "initial", "inline", + "inline-axis", "inline-block", "inline-table", "inset", "inside", "intrinsic", "invert", "italic", "justify", "kannada", "katakana", + "katakana-iroha", "khmer", "landscape", "lao", "large", "larger", "left", "level", "lighter", "line-through", "linear", "lines", + "list-button", "list-item", "listbox", "listitem", "local", "logical", "loud", "lower", "lower-alpha", "lower-armenian", "lower-greek", + "lower-hexadecimal", "lower-latin", "lower-norwegian", "lower-roman", "lowercase", "ltr", "malayalam", "match", "media-controls-background", + "media-current-time-display", "media-fullscreen-button", "media-mute-button", "media-play-button", "media-return-to-realtime-button", + "media-rewind-button", "media-seek-back-button", "media-seek-forward-button", "media-slider", "media-sliderthumb", "media-time-remaining-display", + "media-volume-slider", "media-volume-slider-container", "media-volume-sliderthumb", "medium", "menu", "menulist", "menulist-button", + "menulist-text", "menulist-textfield", "menutext", "message-box", "middle", "min-intrinsic", "mix", "mongolian", "monospace", "move", "multiple", + "myanmar", "n-resize", "narrower", "navy", "ne-resize", "nesw-resize", "no-close-quote", "no-drop", "no-open-quote", "no-repeat", "none", + "normal", "not-allowed", "nowrap", "ns-resize", "nw-resize", "nwse-resize", "oblique", "octal", "open-quote", "optimizeLegibility", + "optimizeSpeed", "oriya", "oromo", "outset", "outside", "overlay", "overline", "padding", "padding-box", "painted", "paused", + "persian", "plus-darker", "plus-lighter", "pointer", "portrait", "pre", "pre-line", "pre-wrap", "preserve-3d", "progress", + "push-button", "radio", "read-only", "read-write", "read-write-plaintext-only", "relative", "repeat", "repeat-x", + "repeat-y", "reset", "reverse", "rgb", "rgba", "ridge", "right", "round", "row-resize", "rtl", "run-in", "running", "s-resize", "sans-serif", + "scroll", "scrollbar", "se-resize", "searchfield", "searchfield-cancel-button", "searchfield-decoration", "searchfield-results-button", + "searchfield-results-decoration", "semi-condensed", "semi-expanded", "separate", "serif", "show", "sidama", "single", + "skip-white-space", "slide", "slider-horizontal", "slider-vertical", "sliderthumb-horizontal", "sliderthumb-vertical", "slow", + "small", "small-caps", "small-caption", "smaller", "solid", "somali", "source-atop", "source-in", "source-out", "source-over", + "space", "square", "square-button", "start", "static", "status-bar", "stretch", "stroke", "sub", "subpixel-antialiased", "super", + "sw-resize", "table", "table-caption", "table-cell", "table-column", "table-column-group", "table-footer-group", "table-header-group", + "table-row", "table-row-group", "telugu", "text", "text-bottom", "text-top", "textarea", "textfield", "thai", "thick", "thin", + "threeddarkshadow", "threedface", "threedhighlight", "threedlightshadow", "threedshadow", "tibetan", "tigre", "tigrinya-er", "tigrinya-er-abegede", + "tigrinya-et", "tigrinya-et-abegede", "to", "top", "transparent", "ultra-condensed", "ultra-expanded", "underline", "up", "upper-alpha", "upper-armenian", + "upper-greek", "upper-hexadecimal", "upper-latin", "upper-norwegian", "upper-roman", "uppercase", "urdu", "url", "vertical", "vertical-text", "visible", + "visibleFill", "visiblePainted", "visibleStroke", "visual", "w-resize", "wait", "wave", "white", "wider", "window", "windowframe", "windowtext", + "x-large", "x-small", "xor", "xx-large", "xx-small", "yellow", "-wap-marquee", "-webkit-activelink", "-webkit-auto", "-webkit-baseline-middle", + "-webkit-body", "-webkit-box", "-webkit-center", "-webkit-control", "-webkit-focus-ring-color", "-webkit-grab", "-webkit-grabbing", + "-webkit-gradient", "-webkit-inline-box", "-webkit-left", "-webkit-link", "-webkit-marquee", "-webkit-mini-control", "-webkit-nowrap", "-webkit-pictograph", + "-webkit-right", "-webkit-small-control", "-webkit-text", "-webkit-xxx-large", "-webkit-zoom-in", "-webkit-zoom-out", + ].keySet(); + + this._mediaTypes = ["all", "aural", "braille", "embossed", "handheld", "import", "print", "projection", "screen", "tty", "tv"].keySet(); + + this._lexConditions = { + INITIAL: 0, + COMMENT: 1, + DSTRING: 2, + SSTRING: 3 + }; + + this._parseConditions = { + INITIAL: 0, + PROPERTY: 1, + PROPERTY_VALUE: 2, + AT_RULE: 3, + AT_MEDIA_RULE: 4 + }; + + this.case_INITIAL = 1000; + this.case_COMMENT = 1002; + this.case_DSTRING = 1003; + this.case_SSTRING = 1004; + + this.condition = this.createInitialCondition(); +} + +WebInspector.SourceCSSTokenizer.prototype = { + createInitialCondition: function() + { + return { lexCondition: this._lexConditions.INITIAL, parseCondition: this._parseConditions.INITIAL }; + }, + + /** + * @param {boolean=} stringEnds + */ + _stringToken: function(cursor, stringEnds) + { + if (this._isPropertyValue()) + this.tokenType = "css-string"; + else + this.tokenType = null; + return cursor; + }, + + _isPropertyValue: function() + { + return this._condition.parseCondition === this._parseConditions.PROPERTY_VALUE || this._condition.parseCondition === this._parseConditions.AT_RULE; + }, + + nextToken: function(cursor) + { + var cursorOnEnter = cursor; + var gotoCase = 1; + var YYMARKER; + while (1) { + switch (gotoCase) + // Following comment is replaced with generated state machine. + + { + case 1: var yych; + var yyaccept = 0; + if (this.getLexCondition() < 2) { + if (this.getLexCondition() < 1) { + { gotoCase = this.case_INITIAL; continue; }; + } else { + { gotoCase = this.case_COMMENT; continue; }; + } + } else { + if (this.getLexCondition() < 3) { + { gotoCase = this.case_DSTRING; continue; }; + } else { + { gotoCase = this.case_SSTRING; continue; }; + } + } +/* *********************************** */ +case this.case_COMMENT: + + yych = this._charAt(cursor); + if (yych <= '\f') { + if (yych == '\n') { gotoCase = 4; continue; }; + { gotoCase = 3; continue; }; + } else { + if (yych <= '\r') { gotoCase = 4; continue; }; + if (yych == '*') { gotoCase = 6; continue; }; + { gotoCase = 3; continue; }; + } +case 2: + { this.tokenType = "css-comment"; return cursor; } +case 3: + yyaccept = 0; + yych = this._charAt(YYMARKER = ++cursor); + { gotoCase = 12; continue; }; +case 4: + ++cursor; + { this.tokenType = null; return cursor; } +case 6: + yyaccept = 1; + yych = this._charAt(YYMARKER = ++cursor); + if (yych == '*') { gotoCase = 9; continue; }; + if (yych != '/') { gotoCase = 11; continue; }; +case 7: + ++cursor; + this.setLexCondition(this._lexConditions.INITIAL); + { this.tokenType = "css-comment"; return cursor; } +case 9: + ++cursor; + yych = this._charAt(cursor); + if (yych == '*') { gotoCase = 9; continue; }; + if (yych == '/') { gotoCase = 7; continue; }; +case 11: + yyaccept = 0; + YYMARKER = ++cursor; + yych = this._charAt(cursor); +case 12: + if (yych <= '\f') { + if (yych == '\n') { gotoCase = 2; continue; }; + { gotoCase = 11; continue; }; + } else { + if (yych <= '\r') { gotoCase = 2; continue; }; + if (yych == '*') { gotoCase = 9; continue; }; + { gotoCase = 11; continue; }; + } +/* *********************************** */ +case this.case_DSTRING: + yych = this._charAt(cursor); + if (yych <= '\r') { + if (yych == '\n') { gotoCase = 17; continue; }; + if (yych <= '\f') { gotoCase = 16; continue; }; + { gotoCase = 17; continue; }; + } else { + if (yych <= '"') { + if (yych <= '!') { gotoCase = 16; continue; }; + { gotoCase = 19; continue; }; + } else { + if (yych == '\\') { gotoCase = 21; continue; }; + { gotoCase = 16; continue; }; + } + } +case 15: + { return this._stringToken(cursor); } +case 16: + yyaccept = 0; + yych = this._charAt(YYMARKER = ++cursor); + { gotoCase = 23; continue; }; +case 17: + ++cursor; +case 18: + { this.tokenType = null; return cursor; } +case 19: + ++cursor; +case 20: + this.setLexCondition(this._lexConditions.INITIAL); + { return this._stringToken(cursor, true); } +case 21: + yych = this._charAt(++cursor); + if (yych <= 'e') { + if (yych <= '\'') { + if (yych == '"') { gotoCase = 22; continue; }; + if (yych <= '&') { gotoCase = 18; continue; }; + } else { + if (yych <= '\\') { + if (yych <= '[') { gotoCase = 18; continue; }; + } else { + if (yych != 'b') { gotoCase = 18; continue; }; + } + } + } else { + if (yych <= 'r') { + if (yych <= 'm') { + if (yych >= 'g') { gotoCase = 18; continue; }; + } else { + if (yych <= 'n') { gotoCase = 22; continue; }; + if (yych <= 'q') { gotoCase = 18; continue; }; + } + } else { + if (yych <= 't') { + if (yych <= 's') { gotoCase = 18; continue; }; + } else { + if (yych != 'v') { gotoCase = 18; continue; }; + } + } + } +case 22: + yyaccept = 0; + YYMARKER = ++cursor; + yych = this._charAt(cursor); +case 23: + if (yych <= '\r') { + if (yych == '\n') { gotoCase = 15; continue; }; + if (yych <= '\f') { gotoCase = 22; continue; }; + { gotoCase = 15; continue; }; + } else { + if (yych <= '"') { + if (yych <= '!') { gotoCase = 22; continue; }; + { gotoCase = 26; continue; }; + } else { + if (yych != '\\') { gotoCase = 22; continue; }; + } + } + ++cursor; + yych = this._charAt(cursor); + if (yych <= 'e') { + if (yych <= '\'') { + if (yych == '"') { gotoCase = 22; continue; }; + if (yych >= '\'') { gotoCase = 22; continue; }; + } else { + if (yych <= '\\') { + if (yych >= '\\') { gotoCase = 22; continue; }; + } else { + if (yych == 'b') { gotoCase = 22; continue; }; + } + } + } else { + if (yych <= 'r') { + if (yych <= 'm') { + if (yych <= 'f') { gotoCase = 22; continue; }; + } else { + if (yych <= 'n') { gotoCase = 22; continue; }; + if (yych >= 'r') { gotoCase = 22; continue; }; + } + } else { + if (yych <= 't') { + if (yych >= 't') { gotoCase = 22; continue; }; + } else { + if (yych == 'v') { gotoCase = 22; continue; }; + } + } + } + cursor = YYMARKER; + { gotoCase = 15; continue; }; +case 26: + ++cursor; + yych = this._charAt(cursor); + { gotoCase = 20; continue; }; +/* *********************************** */ +case this.case_INITIAL: + yych = this._charAt(cursor); + if (yych <= ';') { + if (yych <= '\'') { + if (yych <= '"') { + if (yych <= ' ') { gotoCase = 29; continue; }; + if (yych <= '!') { gotoCase = 31; continue; }; + { gotoCase = 33; continue; }; + } else { + if (yych <= '#') { gotoCase = 34; continue; }; + if (yych <= '$') { gotoCase = 31; continue; }; + if (yych >= '\'') { gotoCase = 35; continue; }; + } + } else { + if (yych <= '.') { + if (yych <= ',') { gotoCase = 29; continue; }; + if (yych <= '-') { gotoCase = 36; continue; }; + { gotoCase = 37; continue; }; + } else { + if (yych <= '/') { gotoCase = 38; continue; }; + if (yych <= '9') { gotoCase = 39; continue; }; + if (yych <= ':') { gotoCase = 41; continue; }; + { gotoCase = 43; continue; }; + } + } + } else { + if (yych <= '^') { + if (yych <= '?') { + if (yych == '=') { gotoCase = 31; continue; }; + } else { + if (yych == '\\') { gotoCase = 29; continue; }; + if (yych <= ']') { gotoCase = 31; continue; }; + } + } else { + if (yych <= 'z') { + if (yych != '`') { gotoCase = 31; continue; }; + } else { + if (yych <= '{') { gotoCase = 45; continue; }; + if (yych == '}') { gotoCase = 47; continue; }; + } + } + } +case 29: + ++cursor; +case 30: + { this.tokenType = null; return cursor; } +case 31: + ++cursor; + yych = this._charAt(cursor); + { gotoCase = 50; continue; }; +case 32: + { + var token = this._line.substring(cursorOnEnter, cursor); + if (this._condition.parseCondition === this._parseConditions.INITIAL) { + if (token === "@media") { + this.tokenType = "css-at-rule"; + this._condition.parseCondition = this._parseConditions.AT_MEDIA_RULE; + } else if (token.indexOf("@") === 0) { + this.tokenType = "css-at-rule"; + this._condition.parseCondition = this._parseConditions.AT_RULE; + } else + this.tokenType = "css-selector"; + } else if ((this._condition.parseCondition === this._parseConditions.AT_MEDIA_RULE || this._condition.parseCondition === this._parseConditions.AT_RULE) && token in this._mediaTypes) + this.tokenType = "css-keyword"; + else if (this._condition.parseCondition === this._parseConditions.PROPERTY && token in this._propertyKeywords) + this.tokenType = "css-property"; + else if (this._isPropertyValue()) { + if (token in this._valueKeywords) + this.tokenType = "css-keyword"; + else if (token in this._colorKeywords) { + // FIXME: this does not convert tokens toLowerCase() for the sake of speed. + this.tokenType = "css-color"; + } else if (token === "!important") + this.tokenType = "css-important"; + } else + this.tokenType = null; + return cursor; + } +case 33: + yyaccept = 0; + yych = this._charAt(YYMARKER = ++cursor); + if (yych <= '-') { + if (yych <= '!') { + if (yych <= '\f') { + if (yych == '\n') { gotoCase = 32; continue; }; + { gotoCase = 128; continue; }; + } else { + if (yych <= '\r') { gotoCase = 32; continue; }; + if (yych <= ' ') { gotoCase = 128; continue; }; + { gotoCase = 126; continue; }; + } + } else { + if (yych <= '$') { + if (yych <= '"') { gotoCase = 115; continue; }; + if (yych <= '#') { gotoCase = 128; continue; }; + { gotoCase = 126; continue; }; + } else { + if (yych == '\'') { gotoCase = 126; continue; }; + if (yych <= ',') { gotoCase = 128; continue; }; + { gotoCase = 126; continue; }; + } + } + } else { + if (yych <= '[') { + if (yych <= '<') { + if (yych <= '.') { gotoCase = 128; continue; }; + if (yych <= '9') { gotoCase = 126; continue; }; + { gotoCase = 128; continue; }; + } else { + if (yych <= '=') { gotoCase = 126; continue; }; + if (yych <= '?') { gotoCase = 128; continue; }; + { gotoCase = 126; continue; }; + } + } else { + if (yych <= '^') { + if (yych <= '\\') { gotoCase = 130; continue; }; + if (yych <= ']') { gotoCase = 126; continue; }; + { gotoCase = 128; continue; }; + } else { + if (yych == '`') { gotoCase = 128; continue; }; + if (yych <= 'z') { gotoCase = 126; continue; }; + { gotoCase = 128; continue; }; + } + } + } +case 34: + yych = this._charAt(++cursor); + if (yych <= '@') { + if (yych <= '/') { gotoCase = 30; continue; }; + if (yych <= '9') { gotoCase = 123; continue; }; + { gotoCase = 30; continue; }; + } else { + if (yych <= 'F') { gotoCase = 123; continue; }; + if (yych <= '`') { gotoCase = 30; continue; }; + if (yych <= 'f') { gotoCase = 123; continue; }; + { gotoCase = 30; continue; }; + } +case 35: + yyaccept = 0; + yych = this._charAt(YYMARKER = ++cursor); + if (yych <= '-') { + if (yych <= '"') { + if (yych <= '\f') { + if (yych == '\n') { gotoCase = 32; continue; }; + { gotoCase = 117; continue; }; + } else { + if (yych <= '\r') { gotoCase = 32; continue; }; + if (yych <= ' ') { gotoCase = 117; continue; }; + { gotoCase = 113; continue; }; + } + } else { + if (yych <= '&') { + if (yych == '$') { gotoCase = 113; continue; }; + { gotoCase = 117; continue; }; + } else { + if (yych <= '\'') { gotoCase = 115; continue; }; + if (yych <= ',') { gotoCase = 117; continue; }; + { gotoCase = 113; continue; }; + } + } + } else { + if (yych <= '[') { + if (yych <= '<') { + if (yych <= '.') { gotoCase = 117; continue; }; + if (yych <= '9') { gotoCase = 113; continue; }; + { gotoCase = 117; continue; }; + } else { + if (yych <= '=') { gotoCase = 113; continue; }; + if (yych <= '?') { gotoCase = 117; continue; }; + { gotoCase = 113; continue; }; + } + } else { + if (yych <= '^') { + if (yych <= '\\') { gotoCase = 119; continue; }; + if (yych <= ']') { gotoCase = 113; continue; }; + { gotoCase = 117; continue; }; + } else { + if (yych == '`') { gotoCase = 117; continue; }; + if (yych <= 'z') { gotoCase = 113; continue; }; + { gotoCase = 117; continue; }; + } + } + } +case 36: + yyaccept = 0; + yych = this._charAt(YYMARKER = ++cursor); + if (yych == '.') { gotoCase = 66; continue; }; + if (yych <= '/') { gotoCase = 50; continue; }; + if (yych <= '9') { gotoCase = 51; continue; }; + { gotoCase = 50; continue; }; +case 37: + yych = this._charAt(++cursor); + if (yych <= '/') { gotoCase = 30; continue; }; + if (yych <= '9') { gotoCase = 69; continue; }; + { gotoCase = 30; continue; }; +case 38: + yyaccept = 0; + yych = this._charAt(YYMARKER = ++cursor); + if (yych == '*') { gotoCase = 105; continue; }; + { gotoCase = 50; continue; }; +case 39: + yyaccept = 1; + yych = this._charAt(YYMARKER = ++cursor); + switch (yych) { + case '!': + case '"': + case '$': + case '\'': + case '-': + case '/': + case '=': + case '@': + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + case 'I': + case 'J': + case 'K': + case 'L': + case 'M': + case 'N': + case 'O': + case 'P': + case 'Q': + case 'R': + case 'S': + case 'T': + case 'U': + case 'V': + case 'W': + case 'X': + case 'Y': + case 'Z': + case '[': + case ']': + case 'a': + case 'b': + case 'f': + case 'h': + case 'j': + case 'l': + case 'n': + case 'o': + case 'q': + case 'u': + case 'v': + case 'w': + case 'x': + case 'y': + case 'z': { gotoCase = 49; continue; }; + case '%': { gotoCase = 68; continue; }; + case '.': { gotoCase = 66; continue; }; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { gotoCase = 51; continue; }; + case 'H': { gotoCase = 53; continue; }; + case '_': { gotoCase = 54; continue; }; + case 'c': { gotoCase = 55; continue; }; + case 'd': { gotoCase = 56; continue; }; + case 'e': { gotoCase = 57; continue; }; + case 'g': { gotoCase = 58; continue; }; + case 'i': { gotoCase = 59; continue; }; + case 'k': { gotoCase = 60; continue; }; + case 'm': { gotoCase = 61; continue; }; + case 'p': { gotoCase = 62; continue; }; + case 'r': { gotoCase = 63; continue; }; + case 's': { gotoCase = 64; continue; }; + case 't': { gotoCase = 65; continue; }; + default: { gotoCase = 40; continue; }; + } +case 40: + { + if (this._isPropertyValue()) + this.tokenType = "css-number"; + else + this.tokenType = null; + return cursor; + } +case 41: + ++cursor; + { + this.tokenType = null; + if (this._condition.parseCondition === this._parseConditions.PROPERTY) + this._condition.parseCondition = this._parseConditions.PROPERTY_VALUE; + return cursor; + } +case 43: + ++cursor; + { + this.tokenType = null; + if (this._condition.parseCondition === this._parseConditions.AT_RULE) + this._condition.parseCondition = this._parseConditions.INITIAL; + else + this._condition.parseCondition = this._parseConditions.PROPERTY; + return cursor; + } +case 45: + ++cursor; + { + this.tokenType = null; + if (this._condition.parseCondition === this._parseConditions.AT_MEDIA_RULE) + this._condition.parseCondition = this._parseConditions.INITIAL; + else + this._condition.parseCondition = this._parseConditions.PROPERTY; + return cursor; + } +case 47: + ++cursor; + { + this.tokenType = null; + this._condition.parseCondition = this._parseConditions.INITIAL; + return cursor; + } +case 49: + ++cursor; + yych = this._charAt(cursor); +case 50: + if (yych <= '9') { + if (yych <= '&') { + if (yych <= '"') { + if (yych <= ' ') { gotoCase = 32; continue; }; + { gotoCase = 49; continue; }; + } else { + if (yych == '$') { gotoCase = 49; continue; }; + { gotoCase = 32; continue; }; + } + } else { + if (yych <= ',') { + if (yych <= '\'') { gotoCase = 49; continue; }; + { gotoCase = 32; continue; }; + } else { + if (yych == '.') { gotoCase = 32; continue; }; + { gotoCase = 49; continue; }; + } + } + } else { + if (yych <= '\\') { + if (yych <= '=') { + if (yych <= '<') { gotoCase = 32; continue; }; + { gotoCase = 49; continue; }; + } else { + if (yych <= '?') { gotoCase = 32; continue; }; + if (yych <= '[') { gotoCase = 49; continue; }; + { gotoCase = 32; continue; }; + } + } else { + if (yych <= '_') { + if (yych == '^') { gotoCase = 32; continue; }; + { gotoCase = 49; continue; }; + } else { + if (yych <= '`') { gotoCase = 32; continue; }; + if (yych <= 'z') { gotoCase = 49; continue; }; + { gotoCase = 32; continue; }; + } + } + } +case 51: + yyaccept = 1; + YYMARKER = ++cursor; + yych = this._charAt(cursor); + switch (yych) { + case '!': + case '"': + case '$': + case '\'': + case '-': + case '/': + case '=': + case '@': + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + case 'I': + case 'J': + case 'K': + case 'L': + case 'M': + case 'N': + case 'O': + case 'P': + case 'Q': + case 'R': + case 'S': + case 'T': + case 'U': + case 'V': + case 'W': + case 'X': + case 'Y': + case 'Z': + case '[': + case ']': + case 'a': + case 'b': + case 'f': + case 'h': + case 'j': + case 'l': + case 'n': + case 'o': + case 'q': + case 'u': + case 'v': + case 'w': + case 'x': + case 'y': + case 'z': { gotoCase = 49; continue; }; + case '%': { gotoCase = 68; continue; }; + case '.': { gotoCase = 66; continue; }; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { gotoCase = 51; continue; }; + case 'H': { gotoCase = 53; continue; }; + case '_': { gotoCase = 54; continue; }; + case 'c': { gotoCase = 55; continue; }; + case 'd': { gotoCase = 56; continue; }; + case 'e': { gotoCase = 57; continue; }; + case 'g': { gotoCase = 58; continue; }; + case 'i': { gotoCase = 59; continue; }; + case 'k': { gotoCase = 60; continue; }; + case 'm': { gotoCase = 61; continue; }; + case 'p': { gotoCase = 62; continue; }; + case 'r': { gotoCase = 63; continue; }; + case 's': { gotoCase = 64; continue; }; + case 't': { gotoCase = 65; continue; }; + default: { gotoCase = 40; continue; }; + } +case 53: + yych = this._charAt(++cursor); + if (yych == 'z') { gotoCase = 64; continue; }; + { gotoCase = 50; continue; }; +case 54: + yych = this._charAt(++cursor); + if (yych == '_') { gotoCase = 102; continue; }; + { gotoCase = 50; continue; }; +case 55: + yych = this._charAt(++cursor); + if (yych == 'm') { gotoCase = 64; continue; }; + { gotoCase = 50; continue; }; +case 56: + yych = this._charAt(++cursor); + if (yych == 'e') { gotoCase = 101; continue; }; + { gotoCase = 50; continue; }; +case 57: + yych = this._charAt(++cursor); + if (yych == 'm') { gotoCase = 64; continue; }; + if (yych == 'x') { gotoCase = 64; continue; }; + { gotoCase = 50; continue; }; +case 58: + yych = this._charAt(++cursor); + if (yych == 'r') { gotoCase = 99; continue; }; + { gotoCase = 50; continue; }; +case 59: + yych = this._charAt(++cursor); + if (yych == 'n') { gotoCase = 64; continue; }; + { gotoCase = 50; continue; }; +case 60: + yych = this._charAt(++cursor); + if (yych == 'H') { gotoCase = 98; continue; }; + { gotoCase = 50; continue; }; +case 61: + yych = this._charAt(++cursor); + if (yych == 'm') { gotoCase = 64; continue; }; + if (yych == 's') { gotoCase = 64; continue; }; + { gotoCase = 50; continue; }; +case 62: + yych = this._charAt(++cursor); + if (yych <= 's') { + if (yych == 'c') { gotoCase = 64; continue; }; + { gotoCase = 50; continue; }; + } else { + if (yych <= 't') { gotoCase = 64; continue; }; + if (yych == 'x') { gotoCase = 64; continue; }; + { gotoCase = 50; continue; }; + } +case 63: + yych = this._charAt(++cursor); + if (yych == 'a') { gotoCase = 96; continue; }; + if (yych == 'e') { gotoCase = 97; continue; }; + { gotoCase = 50; continue; }; +case 64: + yych = this._charAt(++cursor); + if (yych <= '9') { + if (yych <= '&') { + if (yych <= '"') { + if (yych <= ' ') { gotoCase = 40; continue; }; + { gotoCase = 49; continue; }; + } else { + if (yych == '$') { gotoCase = 49; continue; }; + { gotoCase = 40; continue; }; + } + } else { + if (yych <= ',') { + if (yych <= '\'') { gotoCase = 49; continue; }; + { gotoCase = 40; continue; }; + } else { + if (yych == '.') { gotoCase = 40; continue; }; + { gotoCase = 49; continue; }; + } + } + } else { + if (yych <= '\\') { + if (yych <= '=') { + if (yych <= '<') { gotoCase = 40; continue; }; + { gotoCase = 49; continue; }; + } else { + if (yych <= '?') { gotoCase = 40; continue; }; + if (yych <= '[') { gotoCase = 49; continue; }; + { gotoCase = 40; continue; }; + } + } else { + if (yych <= '_') { + if (yych == '^') { gotoCase = 40; continue; }; + { gotoCase = 49; continue; }; + } else { + if (yych <= '`') { gotoCase = 40; continue; }; + if (yych <= 'z') { gotoCase = 49; continue; }; + { gotoCase = 40; continue; }; + } + } + } +case 65: + yych = this._charAt(++cursor); + if (yych == 'u') { gotoCase = 94; continue; }; + { gotoCase = 50; continue; }; +case 66: + yych = this._charAt(++cursor); + if (yych <= '/') { gotoCase = 67; continue; }; + if (yych <= '9') { gotoCase = 69; continue; }; +case 67: + cursor = YYMARKER; + if (yyaccept <= 0) { + { gotoCase = 32; continue; }; + } else { + { gotoCase = 40; continue; }; + } +case 68: + yych = this._charAt(++cursor); + { gotoCase = 40; continue; }; +case 69: + yyaccept = 1; + YYMARKER = ++cursor; + yych = this._charAt(cursor); + if (yych <= 'f') { + if (yych <= 'H') { + if (yych <= '/') { + if (yych == '%') { gotoCase = 68; continue; }; + { gotoCase = 40; continue; }; + } else { + if (yych <= '9') { gotoCase = 69; continue; }; + if (yych <= 'G') { gotoCase = 40; continue; }; + { gotoCase = 81; continue; }; + } + } else { + if (yych <= 'b') { + if (yych == '_') { gotoCase = 73; continue; }; + { gotoCase = 40; continue; }; + } else { + if (yych <= 'c') { gotoCase = 75; continue; }; + if (yych <= 'd') { gotoCase = 78; continue; }; + if (yych >= 'f') { gotoCase = 40; continue; }; + } + } + } else { + if (yych <= 'm') { + if (yych <= 'i') { + if (yych <= 'g') { gotoCase = 79; continue; }; + if (yych <= 'h') { gotoCase = 40; continue; }; + { gotoCase = 77; continue; }; + } else { + if (yych == 'k') { gotoCase = 82; continue; }; + if (yych <= 'l') { gotoCase = 40; continue; }; + { gotoCase = 76; continue; }; + } + } else { + if (yych <= 'q') { + if (yych == 'p') { gotoCase = 74; continue; }; + { gotoCase = 40; continue; }; + } else { + if (yych <= 'r') { gotoCase = 72; continue; }; + if (yych <= 's') { gotoCase = 68; continue; }; + if (yych <= 't') { gotoCase = 80; continue; }; + { gotoCase = 40; continue; }; + } + } + } + yych = this._charAt(++cursor); + if (yych == 'm') { gotoCase = 68; continue; }; + if (yych == 'x') { gotoCase = 68; continue; }; + { gotoCase = 67; continue; }; +case 72: + yych = this._charAt(++cursor); + if (yych == 'a') { gotoCase = 92; continue; }; + if (yych == 'e') { gotoCase = 93; continue; }; + { gotoCase = 67; continue; }; +case 73: + yych = this._charAt(++cursor); + if (yych == '_') { gotoCase = 89; continue; }; + { gotoCase = 67; continue; }; +case 74: + yych = this._charAt(++cursor); + if (yych <= 's') { + if (yych == 'c') { gotoCase = 68; continue; }; + { gotoCase = 67; continue; }; + } else { + if (yych <= 't') { gotoCase = 68; continue; }; + if (yych == 'x') { gotoCase = 68; continue; }; + { gotoCase = 67; continue; }; + } +case 75: + yych = this._charAt(++cursor); + if (yych == 'm') { gotoCase = 68; continue; }; + { gotoCase = 67; continue; }; +case 76: + yych = this._charAt(++cursor); + if (yych == 'm') { gotoCase = 68; continue; }; + if (yych == 's') { gotoCase = 68; continue; }; + { gotoCase = 67; continue; }; +case 77: + yych = this._charAt(++cursor); + if (yych == 'n') { gotoCase = 68; continue; }; + { gotoCase = 67; continue; }; +case 78: + yych = this._charAt(++cursor); + if (yych == 'e') { gotoCase = 88; continue; }; + { gotoCase = 67; continue; }; +case 79: + yych = this._charAt(++cursor); + if (yych == 'r') { gotoCase = 86; continue; }; + { gotoCase = 67; continue; }; +case 80: + yych = this._charAt(++cursor); + if (yych == 'u') { gotoCase = 84; continue; }; + { gotoCase = 67; continue; }; +case 81: + yych = this._charAt(++cursor); + if (yych == 'z') { gotoCase = 68; continue; }; + { gotoCase = 67; continue; }; +case 82: + yych = this._charAt(++cursor); + if (yych != 'H') { gotoCase = 67; continue; }; + yych = this._charAt(++cursor); + if (yych == 'z') { gotoCase = 68; continue; }; + { gotoCase = 67; continue; }; +case 84: + yych = this._charAt(++cursor); + if (yych != 'r') { gotoCase = 67; continue; }; + yych = this._charAt(++cursor); + if (yych == 'n') { gotoCase = 68; continue; }; + { gotoCase = 67; continue; }; +case 86: + yych = this._charAt(++cursor); + if (yych != 'a') { gotoCase = 67; continue; }; + yych = this._charAt(++cursor); + if (yych == 'd') { gotoCase = 68; continue; }; + { gotoCase = 67; continue; }; +case 88: + yych = this._charAt(++cursor); + if (yych == 'g') { gotoCase = 68; continue; }; + { gotoCase = 67; continue; }; +case 89: + yych = this._charAt(++cursor); + if (yych != 'q') { gotoCase = 67; continue; }; + yych = this._charAt(++cursor); + if (yych != 'e') { gotoCase = 67; continue; }; + yych = this._charAt(++cursor); + if (yych == 'm') { gotoCase = 68; continue; }; + { gotoCase = 67; continue; }; +case 92: + yych = this._charAt(++cursor); + if (yych == 'd') { gotoCase = 68; continue; }; + { gotoCase = 67; continue; }; +case 93: + yych = this._charAt(++cursor); + if (yych == 'm') { gotoCase = 68; continue; }; + { gotoCase = 67; continue; }; +case 94: + yych = this._charAt(++cursor); + if (yych != 'r') { gotoCase = 50; continue; }; + yych = this._charAt(++cursor); + if (yych == 'n') { gotoCase = 64; continue; }; + { gotoCase = 50; continue; }; +case 96: + yych = this._charAt(++cursor); + if (yych == 'd') { gotoCase = 64; continue; }; + { gotoCase = 50; continue; }; +case 97: + yych = this._charAt(++cursor); + if (yych == 'm') { gotoCase = 64; continue; }; + { gotoCase = 50; continue; }; +case 98: + yych = this._charAt(++cursor); + if (yych == 'z') { gotoCase = 64; continue; }; + { gotoCase = 50; continue; }; +case 99: + yych = this._charAt(++cursor); + if (yych != 'a') { gotoCase = 50; continue; }; + yych = this._charAt(++cursor); + if (yych == 'd') { gotoCase = 64; continue; }; + { gotoCase = 50; continue; }; +case 101: + yych = this._charAt(++cursor); + if (yych == 'g') { gotoCase = 64; continue; }; + { gotoCase = 50; continue; }; +case 102: + yych = this._charAt(++cursor); + if (yych != 'q') { gotoCase = 50; continue; }; + yych = this._charAt(++cursor); + if (yych != 'e') { gotoCase = 50; continue; }; + yych = this._charAt(++cursor); + if (yych == 'm') { gotoCase = 64; continue; }; + { gotoCase = 50; continue; }; +case 105: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '\f') { + if (yych == '\n') { gotoCase = 109; continue; }; + { gotoCase = 105; continue; }; + } else { + if (yych <= '\r') { gotoCase = 109; continue; }; + if (yych != '*') { gotoCase = 105; continue; }; + } +case 107: + ++cursor; + yych = this._charAt(cursor); + if (yych == '*') { gotoCase = 107; continue; }; + if (yych == '/') { gotoCase = 111; continue; }; + { gotoCase = 105; continue; }; +case 109: + ++cursor; + this.setLexCondition(this._lexConditions.COMMENT); + { this.tokenType = "css-comment"; return cursor; } +case 111: + ++cursor; + { this.tokenType = "css-comment"; return cursor; } +case 113: + yyaccept = 0; + YYMARKER = ++cursor; + yych = this._charAt(cursor); + if (yych <= '-') { + if (yych <= '"') { + if (yych <= '\f') { + if (yych == '\n') { gotoCase = 32; continue; }; + { gotoCase = 117; continue; }; + } else { + if (yych <= '\r') { gotoCase = 32; continue; }; + if (yych <= ' ') { gotoCase = 117; continue; }; + { gotoCase = 113; continue; }; + } + } else { + if (yych <= '&') { + if (yych == '$') { gotoCase = 113; continue; }; + { gotoCase = 117; continue; }; + } else { + if (yych <= '\'') { gotoCase = 115; continue; }; + if (yych <= ',') { gotoCase = 117; continue; }; + { gotoCase = 113; continue; }; + } + } + } else { + if (yych <= '[') { + if (yych <= '<') { + if (yych <= '.') { gotoCase = 117; continue; }; + if (yych <= '9') { gotoCase = 113; continue; }; + { gotoCase = 117; continue; }; + } else { + if (yych <= '=') { gotoCase = 113; continue; }; + if (yych <= '?') { gotoCase = 117; continue; }; + { gotoCase = 113; continue; }; + } + } else { + if (yych <= '^') { + if (yych <= '\\') { gotoCase = 119; continue; }; + if (yych <= ']') { gotoCase = 113; continue; }; + { gotoCase = 117; continue; }; + } else { + if (yych == '`') { gotoCase = 117; continue; }; + if (yych <= 'z') { gotoCase = 113; continue; }; + { gotoCase = 117; continue; }; + } + } + } +case 115: + ++cursor; + if ((yych = this._charAt(cursor)) <= '9') { + if (yych <= '&') { + if (yych <= '"') { + if (yych >= '!') { gotoCase = 49; continue; }; + } else { + if (yych == '$') { gotoCase = 49; continue; }; + } + } else { + if (yych <= ',') { + if (yych <= '\'') { gotoCase = 49; continue; }; + } else { + if (yych != '.') { gotoCase = 49; continue; }; + } + } + } else { + if (yych <= '\\') { + if (yych <= '=') { + if (yych >= '=') { gotoCase = 49; continue; }; + } else { + if (yych <= '?') { gotoCase = 116; continue; }; + if (yych <= '[') { gotoCase = 49; continue; }; + } + } else { + if (yych <= '_') { + if (yych != '^') { gotoCase = 49; continue; }; + } else { + if (yych <= '`') { gotoCase = 116; continue; }; + if (yych <= 'z') { gotoCase = 49; continue; }; + } + } + } +case 116: + { return this._stringToken(cursor, true); } +case 117: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '\r') { + if (yych == '\n') { gotoCase = 67; continue; }; + if (yych <= '\f') { gotoCase = 117; continue; }; + { gotoCase = 67; continue; }; + } else { + if (yych <= '\'') { + if (yych <= '&') { gotoCase = 117; continue; }; + { gotoCase = 122; continue; }; + } else { + if (yych != '\\') { gotoCase = 117; continue; }; + } + } +case 119: + ++cursor; + yych = this._charAt(cursor); + if (yych <= 'a') { + if (yych <= '!') { + if (yych <= '\n') { + if (yych <= '\t') { gotoCase = 67; continue; }; + } else { + if (yych != '\r') { gotoCase = 67; continue; }; + } + } else { + if (yych <= '\'') { + if (yych <= '"') { gotoCase = 117; continue; }; + if (yych <= '&') { gotoCase = 67; continue; }; + { gotoCase = 117; continue; }; + } else { + if (yych == '\\') { gotoCase = 117; continue; }; + { gotoCase = 67; continue; }; + } + } + } else { + if (yych <= 'q') { + if (yych <= 'f') { + if (yych <= 'b') { gotoCase = 117; continue; }; + if (yych <= 'e') { gotoCase = 67; continue; }; + { gotoCase = 117; continue; }; + } else { + if (yych == 'n') { gotoCase = 117; continue; }; + { gotoCase = 67; continue; }; + } + } else { + if (yych <= 't') { + if (yych == 's') { gotoCase = 67; continue; }; + { gotoCase = 117; continue; }; + } else { + if (yych == 'v') { gotoCase = 117; continue; }; + { gotoCase = 67; continue; }; + } + } + } + ++cursor; + this.setLexCondition(this._lexConditions.SSTRING); + { return this._stringToken(cursor); } +case 122: + yych = this._charAt(++cursor); + { gotoCase = 116; continue; }; +case 123: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '@') { + if (yych <= '/') { gotoCase = 125; continue; }; + if (yych <= '9') { gotoCase = 123; continue; }; + } else { + if (yych <= 'F') { gotoCase = 123; continue; }; + if (yych <= '`') { gotoCase = 125; continue; }; + if (yych <= 'f') { gotoCase = 123; continue; }; + } +case 125: + { + if (this._isPropertyValue()) + this.tokenType = "css-color"; + else + this.tokenType = null; + return cursor; + } +case 126: + yyaccept = 0; + YYMARKER = ++cursor; + yych = this._charAt(cursor); + if (yych <= '-') { + if (yych <= '!') { + if (yych <= '\f') { + if (yych == '\n') { gotoCase = 32; continue; }; + } else { + if (yych <= '\r') { gotoCase = 32; continue; }; + if (yych >= '!') { gotoCase = 126; continue; }; + } + } else { + if (yych <= '$') { + if (yych <= '"') { gotoCase = 115; continue; }; + if (yych >= '$') { gotoCase = 126; continue; }; + } else { + if (yych == '\'') { gotoCase = 126; continue; }; + if (yych >= '-') { gotoCase = 126; continue; }; + } + } + } else { + if (yych <= '[') { + if (yych <= '<') { + if (yych <= '.') { gotoCase = 128; continue; }; + if (yych <= '9') { gotoCase = 126; continue; }; + } else { + if (yych <= '=') { gotoCase = 126; continue; }; + if (yych >= '@') { gotoCase = 126; continue; }; + } + } else { + if (yych <= '^') { + if (yych <= '\\') { gotoCase = 130; continue; }; + if (yych <= ']') { gotoCase = 126; continue; }; + } else { + if (yych == '`') { gotoCase = 128; continue; }; + if (yych <= 'z') { gotoCase = 126; continue; }; + } + } + } +case 128: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '\r') { + if (yych == '\n') { gotoCase = 67; continue; }; + if (yych <= '\f') { gotoCase = 128; continue; }; + { gotoCase = 67; continue; }; + } else { + if (yych <= '"') { + if (yych <= '!') { gotoCase = 128; continue; }; + { gotoCase = 122; continue; }; + } else { + if (yych != '\\') { gotoCase = 128; continue; }; + } + } +case 130: + ++cursor; + yych = this._charAt(cursor); + if (yych <= 'a') { + if (yych <= '!') { + if (yych <= '\n') { + if (yych <= '\t') { gotoCase = 67; continue; }; + } else { + if (yych != '\r') { gotoCase = 67; continue; }; + } + } else { + if (yych <= '\'') { + if (yych <= '"') { gotoCase = 128; continue; }; + if (yych <= '&') { gotoCase = 67; continue; }; + { gotoCase = 128; continue; }; + } else { + if (yych == '\\') { gotoCase = 128; continue; }; + { gotoCase = 67; continue; }; + } + } + } else { + if (yych <= 'q') { + if (yych <= 'f') { + if (yych <= 'b') { gotoCase = 128; continue; }; + if (yych <= 'e') { gotoCase = 67; continue; }; + { gotoCase = 128; continue; }; + } else { + if (yych == 'n') { gotoCase = 128; continue; }; + { gotoCase = 67; continue; }; + } + } else { + if (yych <= 't') { + if (yych == 's') { gotoCase = 67; continue; }; + { gotoCase = 128; continue; }; + } else { + if (yych == 'v') { gotoCase = 128; continue; }; + { gotoCase = 67; continue; }; + } + } + } + ++cursor; + this.setLexCondition(this._lexConditions.DSTRING); + { return this._stringToken(cursor); } +/* *********************************** */ +case this.case_SSTRING: + yych = this._charAt(cursor); + if (yych <= '\r') { + if (yych == '\n') { gotoCase = 137; continue; }; + if (yych <= '\f') { gotoCase = 136; continue; }; + { gotoCase = 137; continue; }; + } else { + if (yych <= '\'') { + if (yych <= '&') { gotoCase = 136; continue; }; + { gotoCase = 139; continue; }; + } else { + if (yych == '\\') { gotoCase = 141; continue; }; + { gotoCase = 136; continue; }; + } + } +case 135: + { return this._stringToken(cursor); } +case 136: + yyaccept = 0; + yych = this._charAt(YYMARKER = ++cursor); + { gotoCase = 143; continue; }; +case 137: + ++cursor; +case 138: + { this.tokenType = null; return cursor; } +case 139: + ++cursor; +case 140: + this.setLexCondition(this._lexConditions.INITIAL); + { return this._stringToken(cursor, true); } +case 141: + yych = this._charAt(++cursor); + if (yych <= 'e') { + if (yych <= '\'') { + if (yych == '"') { gotoCase = 142; continue; }; + if (yych <= '&') { gotoCase = 138; continue; }; + } else { + if (yych <= '\\') { + if (yych <= '[') { gotoCase = 138; continue; }; + } else { + if (yych != 'b') { gotoCase = 138; continue; }; + } + } + } else { + if (yych <= 'r') { + if (yych <= 'm') { + if (yych >= 'g') { gotoCase = 138; continue; }; + } else { + if (yych <= 'n') { gotoCase = 142; continue; }; + if (yych <= 'q') { gotoCase = 138; continue; }; + } + } else { + if (yych <= 't') { + if (yych <= 's') { gotoCase = 138; continue; }; + } else { + if (yych != 'v') { gotoCase = 138; continue; }; + } + } + } +case 142: + yyaccept = 0; + YYMARKER = ++cursor; + yych = this._charAt(cursor); +case 143: + if (yych <= '\r') { + if (yych == '\n') { gotoCase = 135; continue; }; + if (yych <= '\f') { gotoCase = 142; continue; }; + { gotoCase = 135; continue; }; + } else { + if (yych <= '\'') { + if (yych <= '&') { gotoCase = 142; continue; }; + { gotoCase = 146; continue; }; + } else { + if (yych != '\\') { gotoCase = 142; continue; }; + } + } + ++cursor; + yych = this._charAt(cursor); + if (yych <= 'e') { + if (yych <= '\'') { + if (yych == '"') { gotoCase = 142; continue; }; + if (yych >= '\'') { gotoCase = 142; continue; }; + } else { + if (yych <= '\\') { + if (yych >= '\\') { gotoCase = 142; continue; }; + } else { + if (yych == 'b') { gotoCase = 142; continue; }; + } + } + } else { + if (yych <= 'r') { + if (yych <= 'm') { + if (yych <= 'f') { gotoCase = 142; continue; }; + } else { + if (yych <= 'n') { gotoCase = 142; continue; }; + if (yych >= 'r') { gotoCase = 142; continue; }; + } + } else { + if (yych <= 't') { + if (yych >= 't') { gotoCase = 142; continue; }; + } else { + if (yych == 'v') { gotoCase = 142; continue; }; + } + } + } + cursor = YYMARKER; + { gotoCase = 135; continue; }; +case 146: + ++cursor; + yych = this._charAt(cursor); + { gotoCase = 140; continue; }; + } + + } + } +} + +WebInspector.SourceCSSTokenizer.prototype.__proto__ = WebInspector.SourceTokenizer.prototype; +/* SourceHTMLTokenizer.js */ + +/* Generated by re2c 0.13.5 on Fri May 6 13:47:06 2011 */ +/* + * Copyright (C) 2009 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// Generate js file as follows: +// +// re2c -isc WebCore/inspector/front-end/SourceHTMLTokenizer.re2js \ +// | sed 's|^yy\([^:]*\)*\:|case \1:|' \ +// | sed 's|[*]cursor[+][+]|this._charAt(cursor++)|' \ +// | sed 's|[[*][+][+]cursor|this._charAt(++cursor)|' \ +// | sed 's|[*]cursor|this._charAt(cursor)|' \ +// | sed 's|yych = \*\([^;]*\)|yych = this._charAt\1|' \ +// | sed 's|{ gotoCase = \([^; continue; };]*\)|{ gotoCase = \1; continue; }|' \ +// | sed 's|unsigned\ int|var|' \ +// | sed 's|var\ yych|case 1: case 1: var yych|' + +/** + * @constructor + * @extends {WebInspector.SourceTokenizer} + */ +WebInspector.SourceHTMLTokenizer = function() +{ + WebInspector.SourceTokenizer.call(this); + + // The order is determined by the generated code. + this._lexConditions = { + INITIAL: 0, + COMMENT: 1, + DOCTYPE: 2, + TAG: 3, + DSTRING: 4, + SSTRING: 5 + }; + this.case_INITIAL = 1000; + this.case_COMMENT = 1001; + this.case_DOCTYPE = 1002; + this.case_TAG = 1003; + this.case_DSTRING = 1004; + this.case_SSTRING = 1005; + + this._parseConditions = { + INITIAL: 0, + ATTRIBUTE: 1, + ATTRIBUTE_VALUE: 2, + LINKIFY: 4, + A_NODE: 8, + SCRIPT: 16, + STYLE: 32 + }; + + this.condition = this.createInitialCondition(); +} + +WebInspector.SourceHTMLTokenizer.prototype = { + createInitialCondition: function() + { + return { lexCondition: this._lexConditions.INITIAL, parseCondition: this._parseConditions.INITIAL }; + }, + + set line(line) { + if (this._condition.internalJavaScriptTokenizerCondition) { + var match = /<\/script/i.exec(line); + if (match) { + this._internalJavaScriptTokenizer.line = line.substring(0, match.index); + } else + this._internalJavaScriptTokenizer.line = line; + } else if (this._condition.internalCSSTokenizerCondition) { + var match = /<\/style/i.exec(line); + if (match) { + this._internalCSSTokenizer.line = line.substring(0, match.index); + } else + this._internalCSSTokenizer.line = line; + } + this._line = line; + }, + + _isExpectingAttribute: function() + { + return this._condition.parseCondition & this._parseConditions.ATTRIBUTE; + }, + + _isExpectingAttributeValue: function() + { + return this._condition.parseCondition & this._parseConditions.ATTRIBUTE_VALUE; + }, + + _setExpectingAttribute: function() + { + if (this._isExpectingAttributeValue()) + this._condition.parseCondition ^= this._parseConditions.ATTRIBUTE_VALUE; + this._condition.parseCondition |= this._parseConditions.ATTRIBUTE; + }, + + _setExpectingAttributeValue: function() + { + if (this._isExpectingAttribute()) + this._condition.parseCondition ^= this._parseConditions.ATTRIBUTE; + this._condition.parseCondition |= this._parseConditions.ATTRIBUTE_VALUE; + }, + + /** + * @param {boolean=} stringEnds + */ + _stringToken: function(cursor, stringEnds) + { + if (!this._isExpectingAttributeValue()) { + this.tokenType = null; + return cursor; + } + this.tokenType = this._attrValueTokenType(); + if (stringEnds) + this._setExpectingAttribute(); + return cursor; + }, + + _attrValueTokenType: function() + { + if (this._condition.parseCondition & this._parseConditions.LINKIFY) { + if (this._condition.parseCondition & this._parseConditions.A_NODE) + return "html-external-link"; + return "html-resource-link"; + } + return "html-attribute-value"; + }, + + get _internalJavaScriptTokenizer() + { + return WebInspector.SourceTokenizer.Registry.getInstance().getTokenizer("text/javascript"); + }, + + get _internalCSSTokenizer() + { + return WebInspector.SourceTokenizer.Registry.getInstance().getTokenizer("text/css"); + }, + + scriptStarted: function(cursor) + { + this._condition.internalJavaScriptTokenizerCondition = this._internalJavaScriptTokenizer.createInitialCondition(); + }, + + scriptEnded: function(cursor) + { + }, + + styleSheetStarted: function(cursor) + { + this._condition.internalCSSTokenizerCondition = this._internalCSSTokenizer.createInitialCondition(); + }, + + styleSheetEnded: function(cursor) + { + }, + + nextToken: function(cursor) + { + if (this._condition.internalJavaScriptTokenizerCondition) { + // Re-set line to force detection first. + this.line = this._line; + if (cursor !== this._internalJavaScriptTokenizer._line.length) { + // Tokenizer is stateless, so restore its condition before tokenizing and save it after. + this._internalJavaScriptTokenizer.condition = this._condition.internalJavaScriptTokenizerCondition; + var result = this._internalJavaScriptTokenizer.nextToken(cursor); + this.tokenType = this._internalJavaScriptTokenizer.tokenType; + this._condition.internalJavaScriptTokenizerCondition = this._internalJavaScriptTokenizer.condition; + return result; + } else if (cursor !== this._line.length) + delete this._condition.internalJavaScriptTokenizerCondition; + } else if (this._condition.internalCSSTokenizerCondition) { + // Re-set line to force detection first. + this.line = this._line; + if (cursor !== this._internalCSSTokenizer._line.length) { + // Tokenizer is stateless, so restore its condition before tokenizing and save it after. + this._internalCSSTokenizer.condition = this._condition.internalCSSTokenizerCondition; + var result = this._internalCSSTokenizer.nextToken(cursor); + this.tokenType = this._internalCSSTokenizer.tokenType; + this._condition.internalCSSTokenizerCondition = this._internalCSSTokenizer.condition; + return result; + } else if (cursor !== this._line.length) + delete this._condition.internalCSSTokenizerCondition; + } + + var cursorOnEnter = cursor; + var gotoCase = 1; + var YYMARKER; + while (1) { + switch (gotoCase) + // Following comment is replaced with generated state machine. + + { + case 1: var yych; + var yyaccept = 0; + if (this.getLexCondition() < 3) { + if (this.getLexCondition() < 1) { + { gotoCase = this.case_INITIAL; continue; }; + } else { + if (this.getLexCondition() < 2) { + { gotoCase = this.case_COMMENT; continue; }; + } else { + { gotoCase = this.case_DOCTYPE; continue; }; + } + } + } else { + if (this.getLexCondition() < 4) { + { gotoCase = this.case_TAG; continue; }; + } else { + if (this.getLexCondition() < 5) { + { gotoCase = this.case_DSTRING; continue; }; + } else { + { gotoCase = this.case_SSTRING; continue; }; + } + } + } +/* *********************************** */ +case this.case_COMMENT: + + yych = this._charAt(cursor); + if (yych <= '\f') { + if (yych == '\n') { gotoCase = 4; continue; }; + { gotoCase = 3; continue; }; + } else { + if (yych <= '\r') { gotoCase = 4; continue; }; + if (yych == '-') { gotoCase = 6; continue; }; + { gotoCase = 3; continue; }; + } +case 2: + { this.tokenType = "html-comment"; return cursor; } +case 3: + yyaccept = 0; + yych = this._charAt(YYMARKER = ++cursor); + { gotoCase = 9; continue; }; +case 4: + ++cursor; +case 5: + { this.tokenType = null; return cursor; } +case 6: + yyaccept = 1; + yych = this._charAt(YYMARKER = ++cursor); + if (yych != '-') { gotoCase = 5; continue; }; +case 7: + ++cursor; + yych = this._charAt(cursor); + if (yych == '>') { gotoCase = 10; continue; }; +case 8: + yyaccept = 0; + YYMARKER = ++cursor; + yych = this._charAt(cursor); +case 9: + if (yych <= '\f') { + if (yych == '\n') { gotoCase = 2; continue; }; + { gotoCase = 8; continue; }; + } else { + if (yych <= '\r') { gotoCase = 2; continue; }; + if (yych == '-') { gotoCase = 12; continue; }; + { gotoCase = 8; continue; }; + } +case 10: + ++cursor; + this.setLexCondition(this._lexConditions.INITIAL); + { this.tokenType = "html-comment"; return cursor; } +case 12: + ++cursor; + yych = this._charAt(cursor); + if (yych == '-') { gotoCase = 7; continue; }; + cursor = YYMARKER; + if (yyaccept <= 0) { + { gotoCase = 2; continue; }; + } else { + { gotoCase = 5; continue; }; + } +/* *********************************** */ +case this.case_DOCTYPE: + yych = this._charAt(cursor); + if (yych <= '\f') { + if (yych == '\n') { gotoCase = 18; continue; }; + { gotoCase = 17; continue; }; + } else { + if (yych <= '\r') { gotoCase = 18; continue; }; + if (yych == '>') { gotoCase = 20; continue; }; + { gotoCase = 17; continue; }; + } +case 16: + { this.tokenType = "html-doctype"; return cursor; } +case 17: + yych = this._charAt(++cursor); + { gotoCase = 23; continue; }; +case 18: + ++cursor; + { this.tokenType = null; return cursor; } +case 20: + ++cursor; + this.setLexCondition(this._lexConditions.INITIAL); + { this.tokenType = "html-doctype"; return cursor; } +case 22: + ++cursor; + yych = this._charAt(cursor); +case 23: + if (yych <= '\f') { + if (yych == '\n') { gotoCase = 16; continue; }; + { gotoCase = 22; continue; }; + } else { + if (yych <= '\r') { gotoCase = 16; continue; }; + if (yych == '>') { gotoCase = 16; continue; }; + { gotoCase = 22; continue; }; + } +/* *********************************** */ +case this.case_DSTRING: + yych = this._charAt(cursor); + if (yych <= '\f') { + if (yych == '\n') { gotoCase = 28; continue; }; + { gotoCase = 27; continue; }; + } else { + if (yych <= '\r') { gotoCase = 28; continue; }; + if (yych == '"') { gotoCase = 30; continue; }; + { gotoCase = 27; continue; }; + } +case 26: + { return this._stringToken(cursor); } +case 27: + yych = this._charAt(++cursor); + { gotoCase = 34; continue; }; +case 28: + ++cursor; + { this.tokenType = null; return cursor; } +case 30: + ++cursor; +case 31: + this.setLexCondition(this._lexConditions.TAG); + { return this._stringToken(cursor, true); } +case 32: + yych = this._charAt(++cursor); + { gotoCase = 31; continue; }; +case 33: + ++cursor; + yych = this._charAt(cursor); +case 34: + if (yych <= '\f') { + if (yych == '\n') { gotoCase = 26; continue; }; + { gotoCase = 33; continue; }; + } else { + if (yych <= '\r') { gotoCase = 26; continue; }; + if (yych == '"') { gotoCase = 32; continue; }; + { gotoCase = 33; continue; }; + } +/* *********************************** */ +case this.case_INITIAL: + yych = this._charAt(cursor); + if (yych == '<') { gotoCase = 39; continue; }; + ++cursor; + { this.tokenType = null; return cursor; } +case 39: + yyaccept = 0; + yych = this._charAt(YYMARKER = ++cursor); + if (yych <= '/') { + if (yych == '!') { gotoCase = 44; continue; }; + if (yych >= '/') { gotoCase = 41; continue; }; + } else { + if (yych <= 'S') { + if (yych >= 'S') { gotoCase = 42; continue; }; + } else { + if (yych == 's') { gotoCase = 42; continue; }; + } + } +case 40: + this.setLexCondition(this._lexConditions.TAG); + { + if (this._condition.parseCondition & (this._parseConditions.SCRIPT | this._parseConditions.STYLE)) { + // Do not tokenize script and style tag contents, keep lexer state, even though processing "<". + this.setLexCondition(this._lexConditions.INITIAL); + this.tokenType = null; + return cursor; + } + + this._condition.parseCondition = this._parseConditions.INITIAL; + this.tokenType = "html-tag"; + return cursor; + } +case 41: + yyaccept = 0; + yych = this._charAt(YYMARKER = ++cursor); + if (yych == 'S') { gotoCase = 73; continue; }; + if (yych == 's') { gotoCase = 73; continue; }; + { gotoCase = 40; continue; }; +case 42: + yych = this._charAt(++cursor); + if (yych <= 'T') { + if (yych == 'C') { gotoCase = 62; continue; }; + if (yych >= 'T') { gotoCase = 63; continue; }; + } else { + if (yych <= 'c') { + if (yych >= 'c') { gotoCase = 62; continue; }; + } else { + if (yych == 't') { gotoCase = 63; continue; }; + } + } +case 43: + cursor = YYMARKER; + { gotoCase = 40; continue; }; +case 44: + yych = this._charAt(++cursor); + if (yych <= 'C') { + if (yych != '-') { gotoCase = 43; continue; }; + } else { + if (yych <= 'D') { gotoCase = 46; continue; }; + if (yych == 'd') { gotoCase = 46; continue; }; + { gotoCase = 43; continue; }; + } + yych = this._charAt(++cursor); + if (yych == '-') { gotoCase = 54; continue; }; + { gotoCase = 43; continue; }; +case 46: + yych = this._charAt(++cursor); + if (yych == 'O') { gotoCase = 47; continue; }; + if (yych != 'o') { gotoCase = 43; continue; }; +case 47: + yych = this._charAt(++cursor); + if (yych == 'C') { gotoCase = 48; continue; }; + if (yych != 'c') { gotoCase = 43; continue; }; +case 48: + yych = this._charAt(++cursor); + if (yych == 'T') { gotoCase = 49; continue; }; + if (yych != 't') { gotoCase = 43; continue; }; +case 49: + yych = this._charAt(++cursor); + if (yych == 'Y') { gotoCase = 50; continue; }; + if (yych != 'y') { gotoCase = 43; continue; }; +case 50: + yych = this._charAt(++cursor); + if (yych == 'P') { gotoCase = 51; continue; }; + if (yych != 'p') { gotoCase = 43; continue; }; +case 51: + yych = this._charAt(++cursor); + if (yych == 'E') { gotoCase = 52; continue; }; + if (yych != 'e') { gotoCase = 43; continue; }; +case 52: + ++cursor; + this.setLexCondition(this._lexConditions.DOCTYPE); + { this.tokenType = "html-doctype"; return cursor; } +case 54: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '\f') { + if (yych == '\n') { gotoCase = 57; continue; }; + { gotoCase = 54; continue; }; + } else { + if (yych <= '\r') { gotoCase = 57; continue; }; + if (yych != '-') { gotoCase = 54; continue; }; + } + ++cursor; + yych = this._charAt(cursor); + if (yych == '-') { gotoCase = 59; continue; }; + { gotoCase = 43; continue; }; +case 57: + ++cursor; + this.setLexCondition(this._lexConditions.COMMENT); + { this.tokenType = "html-comment"; return cursor; } +case 59: + ++cursor; + yych = this._charAt(cursor); + if (yych != '>') { gotoCase = 54; continue; }; + ++cursor; + { this.tokenType = "html-comment"; return cursor; } +case 62: + yych = this._charAt(++cursor); + if (yych == 'R') { gotoCase = 68; continue; }; + if (yych == 'r') { gotoCase = 68; continue; }; + { gotoCase = 43; continue; }; +case 63: + yych = this._charAt(++cursor); + if (yych == 'Y') { gotoCase = 64; continue; }; + if (yych != 'y') { gotoCase = 43; continue; }; +case 64: + yych = this._charAt(++cursor); + if (yych == 'L') { gotoCase = 65; continue; }; + if (yych != 'l') { gotoCase = 43; continue; }; +case 65: + yych = this._charAt(++cursor); + if (yych == 'E') { gotoCase = 66; continue; }; + if (yych != 'e') { gotoCase = 43; continue; }; +case 66: + ++cursor; + this.setLexCondition(this._lexConditions.TAG); + { + if (this._condition.parseCondition & this._parseConditions.STYLE) { + // Do not tokenize style tag contents, keep lexer state, even though processing "<". + this.setLexCondition(this._lexConditions.INITIAL); + this.tokenType = null; + return cursor; + } + this.tokenType = "html-tag"; + this._condition.parseCondition = this._parseConditions.STYLE; + this._setExpectingAttribute(); + return cursor; + } +case 68: + yych = this._charAt(++cursor); + if (yych == 'I') { gotoCase = 69; continue; }; + if (yych != 'i') { gotoCase = 43; continue; }; +case 69: + yych = this._charAt(++cursor); + if (yych == 'P') { gotoCase = 70; continue; }; + if (yych != 'p') { gotoCase = 43; continue; }; +case 70: + yych = this._charAt(++cursor); + if (yych == 'T') { gotoCase = 71; continue; }; + if (yych != 't') { gotoCase = 43; continue; }; +case 71: + ++cursor; + this.setLexCondition(this._lexConditions.TAG); + { + if (this._condition.parseCondition & this._parseConditions.SCRIPT) { + // Do not tokenize script tag contents, keep lexer state, even though processing "<". + this.setLexCondition(this._lexConditions.INITIAL); + this.tokenType = null; + return cursor; + } + this.tokenType = "html-tag"; + this._condition.parseCondition = this._parseConditions.SCRIPT; + this._setExpectingAttribute(); + return cursor; + } +case 73: + yych = this._charAt(++cursor); + if (yych <= 'T') { + if (yych == 'C') { gotoCase = 75; continue; }; + if (yych <= 'S') { gotoCase = 43; continue; }; + } else { + if (yych <= 'c') { + if (yych <= 'b') { gotoCase = 43; continue; }; + { gotoCase = 75; continue; }; + } else { + if (yych != 't') { gotoCase = 43; continue; }; + } + } + yych = this._charAt(++cursor); + if (yych == 'Y') { gotoCase = 81; continue; }; + if (yych == 'y') { gotoCase = 81; continue; }; + { gotoCase = 43; continue; }; +case 75: + yych = this._charAt(++cursor); + if (yych == 'R') { gotoCase = 76; continue; }; + if (yych != 'r') { gotoCase = 43; continue; }; +case 76: + yych = this._charAt(++cursor); + if (yych == 'I') { gotoCase = 77; continue; }; + if (yych != 'i') { gotoCase = 43; continue; }; +case 77: + yych = this._charAt(++cursor); + if (yych == 'P') { gotoCase = 78; continue; }; + if (yych != 'p') { gotoCase = 43; continue; }; +case 78: + yych = this._charAt(++cursor); + if (yych == 'T') { gotoCase = 79; continue; }; + if (yych != 't') { gotoCase = 43; continue; }; +case 79: + ++cursor; + this.setLexCondition(this._lexConditions.TAG); + { + this.tokenType = "html-tag"; + this._condition.parseCondition = this._parseConditions.INITIAL; + this.scriptEnded(cursor - 8); + return cursor; + } +case 81: + yych = this._charAt(++cursor); + if (yych == 'L') { gotoCase = 82; continue; }; + if (yych != 'l') { gotoCase = 43; continue; }; +case 82: + yych = this._charAt(++cursor); + if (yych == 'E') { gotoCase = 83; continue; }; + if (yych != 'e') { gotoCase = 43; continue; }; +case 83: + ++cursor; + this.setLexCondition(this._lexConditions.TAG); + { + this.tokenType = "html-tag"; + this._condition.parseCondition = this._parseConditions.INITIAL; + this.styleSheetEnded(cursor - 7); + return cursor; + } +/* *********************************** */ +case this.case_SSTRING: + yych = this._charAt(cursor); + if (yych <= '\f') { + if (yych == '\n') { gotoCase = 89; continue; }; + { gotoCase = 88; continue; }; + } else { + if (yych <= '\r') { gotoCase = 89; continue; }; + if (yych == '\'') { gotoCase = 91; continue; }; + { gotoCase = 88; continue; }; + } +case 87: + { return this._stringToken(cursor); } +case 88: + yych = this._charAt(++cursor); + { gotoCase = 95; continue; }; +case 89: + ++cursor; + { this.tokenType = null; return cursor; } +case 91: + ++cursor; +case 92: + this.setLexCondition(this._lexConditions.TAG); + { return this._stringToken(cursor, true); } +case 93: + yych = this._charAt(++cursor); + { gotoCase = 92; continue; }; +case 94: + ++cursor; + yych = this._charAt(cursor); +case 95: + if (yych <= '\f') { + if (yych == '\n') { gotoCase = 87; continue; }; + { gotoCase = 94; continue; }; + } else { + if (yych <= '\r') { gotoCase = 87; continue; }; + if (yych == '\'') { gotoCase = 93; continue; }; + { gotoCase = 94; continue; }; + } +/* *********************************** */ +case this.case_TAG: + yych = this._charAt(cursor); + if (yych <= '&') { + if (yych <= '\r') { + if (yych == '\n') { gotoCase = 100; continue; }; + if (yych >= '\r') { gotoCase = 100; continue; }; + } else { + if (yych <= ' ') { + if (yych >= ' ') { gotoCase = 100; continue; }; + } else { + if (yych == '"') { gotoCase = 102; continue; }; + } + } + } else { + if (yych <= '>') { + if (yych <= ';') { + if (yych <= '\'') { gotoCase = 103; continue; }; + } else { + if (yych <= '<') { gotoCase = 100; continue; }; + if (yych <= '=') { gotoCase = 104; continue; }; + { gotoCase = 106; continue; }; + } + } else { + if (yych <= '[') { + if (yych >= '[') { gotoCase = 100; continue; }; + } else { + if (yych == ']') { gotoCase = 100; continue; }; + } + } + } + ++cursor; + yych = this._charAt(cursor); + { gotoCase = 119; continue; }; +case 99: + { + if (this._condition.parseCondition === this._parseConditions.SCRIPT || this._condition.parseCondition === this._parseConditions.STYLE) { + // Fall through if expecting attributes. + this.tokenType = null; + return cursor; + } + + if (this._condition.parseCondition === this._parseConditions.INITIAL) { + this.tokenType = "html-tag"; + this._setExpectingAttribute(); + var token = this._line.substring(cursorOnEnter, cursor); + if (token === "a") + this._condition.parseCondition |= this._parseConditions.A_NODE; + else if (this._condition.parseCondition & this._parseConditions.A_NODE) + this._condition.parseCondition ^= this._parseConditions.A_NODE; + } else if (this._isExpectingAttribute()) { + var token = this._line.substring(cursorOnEnter, cursor); + if (token === "href" || token === "src") + this._condition.parseCondition |= this._parseConditions.LINKIFY; + else if (this._condition.parseCondition |= this._parseConditions.LINKIFY) + this._condition.parseCondition ^= this._parseConditions.LINKIFY; + this.tokenType = "html-attribute-name"; + } else if (this._isExpectingAttributeValue()) + this.tokenType = this._attrValueTokenType(); + else + this.tokenType = null; + return cursor; + } +case 100: + ++cursor; + { this.tokenType = null; return cursor; } +case 102: + yyaccept = 0; + yych = this._charAt(YYMARKER = ++cursor); + { gotoCase = 115; continue; }; +case 103: + yyaccept = 0; + yych = this._charAt(YYMARKER = ++cursor); + { gotoCase = 109; continue; }; +case 104: + ++cursor; + { + if (this._isExpectingAttribute()) + this._setExpectingAttributeValue(); + this.tokenType = null; + return cursor; + } +case 106: + ++cursor; + this.setLexCondition(this._lexConditions.INITIAL); + { + this.tokenType = "html-tag"; + if (this._condition.parseCondition & this._parseConditions.SCRIPT) { + this.scriptStarted(cursor); + // Do not tokenize script tag contents. + return cursor; + } + + if (this._condition.parseCondition & this._parseConditions.STYLE) { + this.styleSheetStarted(cursor); + // Do not tokenize style tag contents. + return cursor; + } + + this._condition.parseCondition = this._parseConditions.INITIAL; + return cursor; + } +case 108: + ++cursor; + yych = this._charAt(cursor); +case 109: + if (yych <= '\f') { + if (yych != '\n') { gotoCase = 108; continue; }; + } else { + if (yych <= '\r') { gotoCase = 110; continue; }; + if (yych == '\'') { gotoCase = 112; continue; }; + { gotoCase = 108; continue; }; + } +case 110: + ++cursor; + this.setLexCondition(this._lexConditions.SSTRING); + { return this._stringToken(cursor); } +case 112: + ++cursor; + { return this._stringToken(cursor, true); } +case 114: + ++cursor; + yych = this._charAt(cursor); +case 115: + if (yych <= '\f') { + if (yych != '\n') { gotoCase = 114; continue; }; + } else { + if (yych <= '\r') { gotoCase = 116; continue; }; + if (yych == '"') { gotoCase = 112; continue; }; + { gotoCase = 114; continue; }; + } +case 116: + ++cursor; + this.setLexCondition(this._lexConditions.DSTRING); + { return this._stringToken(cursor); } +case 118: + ++cursor; + yych = this._charAt(cursor); +case 119: + if (yych <= '"') { + if (yych <= '\r') { + if (yych == '\n') { gotoCase = 99; continue; }; + if (yych <= '\f') { gotoCase = 118; continue; }; + { gotoCase = 99; continue; }; + } else { + if (yych == ' ') { gotoCase = 99; continue; }; + if (yych <= '!') { gotoCase = 118; continue; }; + { gotoCase = 99; continue; }; + } + } else { + if (yych <= '>') { + if (yych == '\'') { gotoCase = 99; continue; }; + if (yych <= ';') { gotoCase = 118; continue; }; + { gotoCase = 99; continue; }; + } else { + if (yych <= '[') { + if (yych <= 'Z') { gotoCase = 118; continue; }; + { gotoCase = 99; continue; }; + } else { + if (yych == ']') { gotoCase = 99; continue; }; + { gotoCase = 118; continue; }; + } + } + } + } + + } + } +} + +WebInspector.SourceHTMLTokenizer.prototype.__proto__ = WebInspector.SourceTokenizer.prototype; +/* SourceJavaScriptTokenizer.js */ + +/* Generated by re2c 0.13.5 on Fri May 13 20:01:13 2011 */ +/* + * Copyright (C) 2009 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// Generate js file as follows: +// +// re2c -isc WebCore/inspector/front-end/SourceJavaScriptTokenizer.re2js \ +// | sed 's|^yy\([^:]*\)*\:|case \1:|' \ +// | sed 's|[*]cursor[+][+]|this._charAt(cursor++)|' \ +// | sed 's|[[*][+][+]cursor|this._charAt(++cursor)|' \ +// | sed 's|[*]cursor|this._charAt(cursor)|' \ +// | sed 's|yych = \*\([^;]*\)|yych = this._charAt\1|' \ +// | sed 's|{ gotoCase = \([^; continue; };]*\)|{ gotoCase = \1; continue; }|' \ +// | sed 's|yych <= \(0x[0-9a-fA-f]+\)|yych <= String.fromCharCode(\1)|' \ +// | sed 's|unsigned\ int|var|' \ +// | sed 's|var\ yych|case 1: case 1: var yych|' + +/** + * @constructor + * @extends {WebInspector.SourceTokenizer} + */ +WebInspector.SourceJavaScriptTokenizer = function() +{ + WebInspector.SourceTokenizer.call(this); + + this._keywords = [ + "null", "true", "false", "break", "case", "catch", "const", "default", "finally", "for", + "instanceof", "new", "var", "continue", "function", "return", "void", "delete", "if", + "this", "do", "while", "else", "in", "switch", "throw", "try", "typeof", "debugger", + "class", "enum", "export", "extends", "import", "super", "get", "set", "with" + ].keySet(); + + this._lexConditions = { + DIV: 0, + NODIV: 1, + COMMENT: 2, + DSTRING: 3, + SSTRING: 4, + REGEX: 5 + }; + + this.case_DIV = 1000; + this.case_NODIV = 1001; + this.case_COMMENT = 1002; + this.case_DSTRING = 1003; + this.case_SSTRING = 1004; + this.case_REGEX = 1005; + + this.condition = this.createInitialCondition(); +} + +WebInspector.SourceJavaScriptTokenizer.prototype = { + createInitialCondition: function() + { + return { lexCondition: this._lexConditions.NODIV }; + }, + + nextToken: function(cursor) + { + var cursorOnEnter = cursor; + var gotoCase = 1; + var YYMARKER; + while (1) { + switch (gotoCase) + // Following comment is replaced with generated state machine. + + { + case 1: var yych; + var yyaccept = 0; + if (this.getLexCondition() < 3) { + if (this.getLexCondition() < 1) { + { gotoCase = this.case_DIV; continue; }; + } else { + if (this.getLexCondition() < 2) { + { gotoCase = this.case_NODIV; continue; }; + } else { + { gotoCase = this.case_COMMENT; continue; }; + } + } + } else { + if (this.getLexCondition() < 4) { + { gotoCase = this.case_DSTRING; continue; }; + } else { + if (this.getLexCondition() < 5) { + { gotoCase = this.case_SSTRING; continue; }; + } else { + { gotoCase = this.case_REGEX; continue; }; + } + } + } +/* *********************************** */ +case this.case_COMMENT: + + yych = this._charAt(cursor); + if (yych <= '\f') { + if (yych == '\n') { gotoCase = 4; continue; }; + { gotoCase = 3; continue; }; + } else { + if (yych <= '\r') { gotoCase = 4; continue; }; + if (yych == '*') { gotoCase = 6; continue; }; + { gotoCase = 3; continue; }; + } +case 2: + { this.tokenType = "javascript-comment"; return cursor; } +case 3: + yyaccept = 0; + yych = this._charAt(YYMARKER = ++cursor); + { gotoCase = 12; continue; }; +case 4: + ++cursor; + { this.tokenType = null; return cursor; } +case 6: + yyaccept = 1; + yych = this._charAt(YYMARKER = ++cursor); + if (yych == '*') { gotoCase = 9; continue; }; + if (yych != '/') { gotoCase = 11; continue; }; +case 7: + ++cursor; + this.setLexCondition(this._lexConditions.NODIV); + { this.tokenType = "javascript-comment"; return cursor; } +case 9: + ++cursor; + yych = this._charAt(cursor); + if (yych == '*') { gotoCase = 9; continue; }; + if (yych == '/') { gotoCase = 7; continue; }; +case 11: + yyaccept = 0; + YYMARKER = ++cursor; + yych = this._charAt(cursor); +case 12: + if (yych <= '\f') { + if (yych == '\n') { gotoCase = 2; continue; }; + { gotoCase = 11; continue; }; + } else { + if (yych <= '\r') { gotoCase = 2; continue; }; + if (yych == '*') { gotoCase = 9; continue; }; + { gotoCase = 11; continue; }; + } +/* *********************************** */ +case this.case_DIV: + yych = this._charAt(cursor); + if (yych <= '9') { + if (yych <= '(') { + if (yych <= '#') { + if (yych <= ' ') { gotoCase = 15; continue; }; + if (yych <= '!') { gotoCase = 17; continue; }; + if (yych <= '"') { gotoCase = 19; continue; }; + } else { + if (yych <= '%') { + if (yych <= '$') { gotoCase = 20; continue; }; + { gotoCase = 22; continue; }; + } else { + if (yych <= '&') { gotoCase = 23; continue; }; + if (yych <= '\'') { gotoCase = 24; continue; }; + { gotoCase = 25; continue; }; + } + } + } else { + if (yych <= ',') { + if (yych <= ')') { gotoCase = 26; continue; }; + if (yych <= '*') { gotoCase = 28; continue; }; + if (yych <= '+') { gotoCase = 29; continue; }; + { gotoCase = 25; continue; }; + } else { + if (yych <= '.') { + if (yych <= '-') { gotoCase = 30; continue; }; + { gotoCase = 31; continue; }; + } else { + if (yych <= '/') { gotoCase = 32; continue; }; + if (yych <= '0') { gotoCase = 34; continue; }; + { gotoCase = 36; continue; }; + } + } + } + } else { + if (yych <= '\\') { + if (yych <= '>') { + if (yych <= ';') { gotoCase = 25; continue; }; + if (yych <= '<') { gotoCase = 37; continue; }; + if (yych <= '=') { gotoCase = 38; continue; }; + { gotoCase = 39; continue; }; + } else { + if (yych <= '@') { + if (yych <= '?') { gotoCase = 25; continue; }; + } else { + if (yych <= 'Z') { gotoCase = 20; continue; }; + if (yych <= '[') { gotoCase = 25; continue; }; + { gotoCase = 40; continue; }; + } + } + } else { + if (yych <= 'z') { + if (yych <= '^') { + if (yych <= ']') { gotoCase = 25; continue; }; + { gotoCase = 41; continue; }; + } else { + if (yych != '`') { gotoCase = 20; continue; }; + } + } else { + if (yych <= '|') { + if (yych <= '{') { gotoCase = 25; continue; }; + { gotoCase = 42; continue; }; + } else { + if (yych <= '~') { gotoCase = 25; continue; }; + if (yych >= 0x80) { gotoCase = 20; continue; }; + } + } + } + } +case 15: + ++cursor; +case 16: + { this.tokenType = null; return cursor; } +case 17: + ++cursor; + if ((yych = this._charAt(cursor)) == '=') { gotoCase = 115; continue; }; +case 18: + this.setLexCondition(this._lexConditions.NODIV); + { this.tokenType = null; return cursor; } +case 19: + yyaccept = 0; + yych = this._charAt(YYMARKER = ++cursor); + if (yych == '\n') { gotoCase = 16; continue; }; + if (yych == '\r') { gotoCase = 16; continue; }; + { gotoCase = 107; continue; }; +case 20: + yyaccept = 1; + yych = this._charAt(YYMARKER = ++cursor); + { gotoCase = 50; continue; }; +case 21: + { + var token = this._line.substring(cursorOnEnter, cursor); + if (this._keywords[token] === true && token !== "__proto__") + this.tokenType = "javascript-keyword"; + else + this.tokenType = "javascript-ident"; + return cursor; + } +case 22: + yych = this._charAt(++cursor); + if (yych == '=') { gotoCase = 43; continue; }; + { gotoCase = 18; continue; }; +case 23: + yych = this._charAt(++cursor); + if (yych == '&') { gotoCase = 43; continue; }; + if (yych == '=') { gotoCase = 43; continue; }; + { gotoCase = 18; continue; }; +case 24: + yyaccept = 0; + yych = this._charAt(YYMARKER = ++cursor); + if (yych == '\n') { gotoCase = 16; continue; }; + if (yych == '\r') { gotoCase = 16; continue; }; + { gotoCase = 96; continue; }; +case 25: + yych = this._charAt(++cursor); + { gotoCase = 18; continue; }; +case 26: + ++cursor; + { this.tokenType = null; return cursor; } +case 28: + yych = this._charAt(++cursor); + if (yych == '=') { gotoCase = 43; continue; }; + { gotoCase = 18; continue; }; +case 29: + yych = this._charAt(++cursor); + if (yych == '+') { gotoCase = 43; continue; }; + if (yych == '=') { gotoCase = 43; continue; }; + { gotoCase = 18; continue; }; +case 30: + yych = this._charAt(++cursor); + if (yych == '-') { gotoCase = 43; continue; }; + if (yych == '=') { gotoCase = 43; continue; }; + { gotoCase = 18; continue; }; +case 31: + yych = this._charAt(++cursor); + if (yych <= '/') { gotoCase = 18; continue; }; + if (yych <= '9') { gotoCase = 89; continue; }; + { gotoCase = 18; continue; }; +case 32: + yyaccept = 2; + yych = this._charAt(YYMARKER = ++cursor); + if (yych <= '.') { + if (yych == '*') { gotoCase = 78; continue; }; + } else { + if (yych <= '/') { gotoCase = 80; continue; }; + if (yych == '=') { gotoCase = 77; continue; }; + } +case 33: + this.setLexCondition(this._lexConditions.NODIV); + { this.tokenType = null; return cursor; } +case 34: + yyaccept = 3; + yych = this._charAt(YYMARKER = ++cursor); + if (yych <= 'E') { + if (yych <= '/') { + if (yych == '.') { gotoCase = 63; continue; }; + } else { + if (yych <= '7') { gotoCase = 72; continue; }; + if (yych >= 'E') { gotoCase = 62; continue; }; + } + } else { + if (yych <= 'd') { + if (yych == 'X') { gotoCase = 74; continue; }; + } else { + if (yych <= 'e') { gotoCase = 62; continue; }; + if (yych == 'x') { gotoCase = 74; continue; }; + } + } +case 35: + { this.tokenType = "javascript-number"; return cursor; } +case 36: + yyaccept = 3; + yych = this._charAt(YYMARKER = ++cursor); + if (yych <= '9') { + if (yych == '.') { gotoCase = 63; continue; }; + if (yych <= '/') { gotoCase = 35; continue; }; + { gotoCase = 60; continue; }; + } else { + if (yych <= 'E') { + if (yych <= 'D') { gotoCase = 35; continue; }; + { gotoCase = 62; continue; }; + } else { + if (yych == 'e') { gotoCase = 62; continue; }; + { gotoCase = 35; continue; }; + } + } +case 37: + yych = this._charAt(++cursor); + if (yych <= ';') { gotoCase = 18; continue; }; + if (yych <= '<') { gotoCase = 59; continue; }; + if (yych <= '=') { gotoCase = 43; continue; }; + { gotoCase = 18; continue; }; +case 38: + yych = this._charAt(++cursor); + if (yych == '=') { gotoCase = 58; continue; }; + { gotoCase = 18; continue; }; +case 39: + yych = this._charAt(++cursor); + if (yych <= '<') { gotoCase = 18; continue; }; + if (yych <= '=') { gotoCase = 43; continue; }; + if (yych <= '>') { gotoCase = 56; continue; }; + { gotoCase = 18; continue; }; +case 40: + yyaccept = 0; + yych = this._charAt(YYMARKER = ++cursor); + if (yych == 'u') { gotoCase = 44; continue; }; + { gotoCase = 16; continue; }; +case 41: + yych = this._charAt(++cursor); + if (yych == '=') { gotoCase = 43; continue; }; + { gotoCase = 18; continue; }; +case 42: + yych = this._charAt(++cursor); + if (yych == '=') { gotoCase = 43; continue; }; + if (yych != '|') { gotoCase = 18; continue; }; +case 43: + yych = this._charAt(++cursor); + { gotoCase = 18; continue; }; +case 44: + yych = this._charAt(++cursor); + if (yych <= '@') { + if (yych <= '/') { gotoCase = 45; continue; }; + if (yych <= '9') { gotoCase = 46; continue; }; + } else { + if (yych <= 'F') { gotoCase = 46; continue; }; + if (yych <= '`') { gotoCase = 45; continue; }; + if (yych <= 'f') { gotoCase = 46; continue; }; + } +case 45: + cursor = YYMARKER; + if (yyaccept <= 1) { + if (yyaccept <= 0) { + { gotoCase = 16; continue; }; + } else { + { gotoCase = 21; continue; }; + } + } else { + if (yyaccept <= 2) { + { gotoCase = 33; continue; }; + } else { + { gotoCase = 35; continue; }; + } + } +case 46: + yych = this._charAt(++cursor); + if (yych <= '@') { + if (yych <= '/') { gotoCase = 45; continue; }; + if (yych >= ':') { gotoCase = 45; continue; }; + } else { + if (yych <= 'F') { gotoCase = 47; continue; }; + if (yych <= '`') { gotoCase = 45; continue; }; + if (yych >= 'g') { gotoCase = 45; continue; }; + } +case 47: + yych = this._charAt(++cursor); + if (yych <= '@') { + if (yych <= '/') { gotoCase = 45; continue; }; + if (yych >= ':') { gotoCase = 45; continue; }; + } else { + if (yych <= 'F') { gotoCase = 48; continue; }; + if (yych <= '`') { gotoCase = 45; continue; }; + if (yych >= 'g') { gotoCase = 45; continue; }; + } +case 48: + yych = this._charAt(++cursor); + if (yych <= '@') { + if (yych <= '/') { gotoCase = 45; continue; }; + if (yych >= ':') { gotoCase = 45; continue; }; + } else { + if (yych <= 'F') { gotoCase = 49; continue; }; + if (yych <= '`') { gotoCase = 45; continue; }; + if (yych >= 'g') { gotoCase = 45; continue; }; + } +case 49: + yyaccept = 1; + YYMARKER = ++cursor; + yych = this._charAt(cursor); +case 50: + if (yych <= '[') { + if (yych <= '/') { + if (yych == '$') { gotoCase = 49; continue; }; + { gotoCase = 21; continue; }; + } else { + if (yych <= '9') { gotoCase = 49; continue; }; + if (yych <= '@') { gotoCase = 21; continue; }; + if (yych <= 'Z') { gotoCase = 49; continue; }; + { gotoCase = 21; continue; }; + } + } else { + if (yych <= '_') { + if (yych <= '\\') { gotoCase = 51; continue; }; + if (yych <= '^') { gotoCase = 21; continue; }; + { gotoCase = 49; continue; }; + } else { + if (yych <= '`') { gotoCase = 21; continue; }; + if (yych <= 'z') { gotoCase = 49; continue; }; + if (yych <= String.fromCharCode(0x7F)) { gotoCase = 21; continue; }; + { gotoCase = 49; continue; }; + } + } +case 51: + ++cursor; + yych = this._charAt(cursor); + if (yych != 'u') { gotoCase = 45; continue; }; + ++cursor; + yych = this._charAt(cursor); + if (yych <= '@') { + if (yych <= '/') { gotoCase = 45; continue; }; + if (yych >= ':') { gotoCase = 45; continue; }; + } else { + if (yych <= 'F') { gotoCase = 53; continue; }; + if (yych <= '`') { gotoCase = 45; continue; }; + if (yych >= 'g') { gotoCase = 45; continue; }; + } +case 53: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '@') { + if (yych <= '/') { gotoCase = 45; continue; }; + if (yych >= ':') { gotoCase = 45; continue; }; + } else { + if (yych <= 'F') { gotoCase = 54; continue; }; + if (yych <= '`') { gotoCase = 45; continue; }; + if (yych >= 'g') { gotoCase = 45; continue; }; + } +case 54: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '@') { + if (yych <= '/') { gotoCase = 45; continue; }; + if (yych >= ':') { gotoCase = 45; continue; }; + } else { + if (yych <= 'F') { gotoCase = 55; continue; }; + if (yych <= '`') { gotoCase = 45; continue; }; + if (yych >= 'g') { gotoCase = 45; continue; }; + } +case 55: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '@') { + if (yych <= '/') { gotoCase = 45; continue; }; + if (yych <= '9') { gotoCase = 49; continue; }; + { gotoCase = 45; continue; }; + } else { + if (yych <= 'F') { gotoCase = 49; continue; }; + if (yych <= '`') { gotoCase = 45; continue; }; + if (yych <= 'f') { gotoCase = 49; continue; }; + { gotoCase = 45; continue; }; + } +case 56: + yych = this._charAt(++cursor); + if (yych <= '<') { gotoCase = 18; continue; }; + if (yych <= '=') { gotoCase = 43; continue; }; + if (yych >= '?') { gotoCase = 18; continue; }; + yych = this._charAt(++cursor); + if (yych == '=') { gotoCase = 43; continue; }; + { gotoCase = 18; continue; }; +case 58: + yych = this._charAt(++cursor); + if (yych == '=') { gotoCase = 43; continue; }; + { gotoCase = 18; continue; }; +case 59: + yych = this._charAt(++cursor); + if (yych == '=') { gotoCase = 43; continue; }; + { gotoCase = 18; continue; }; +case 60: + yyaccept = 3; + YYMARKER = ++cursor; + yych = this._charAt(cursor); + if (yych <= '9') { + if (yych == '.') { gotoCase = 63; continue; }; + if (yych <= '/') { gotoCase = 35; continue; }; + { gotoCase = 60; continue; }; + } else { + if (yych <= 'E') { + if (yych <= 'D') { gotoCase = 35; continue; }; + } else { + if (yych != 'e') { gotoCase = 35; continue; }; + } + } +case 62: + yych = this._charAt(++cursor); + if (yych <= ',') { + if (yych == '+') { gotoCase = 69; continue; }; + { gotoCase = 45; continue; }; + } else { + if (yych <= '-') { gotoCase = 69; continue; }; + if (yych <= '/') { gotoCase = 45; continue; }; + if (yych <= '9') { gotoCase = 70; continue; }; + { gotoCase = 45; continue; }; + } +case 63: + yyaccept = 3; + YYMARKER = ++cursor; + yych = this._charAt(cursor); + if (yych <= 'D') { + if (yych <= '/') { gotoCase = 35; continue; }; + if (yych <= '9') { gotoCase = 63; continue; }; + { gotoCase = 35; continue; }; + } else { + if (yych <= 'E') { gotoCase = 65; continue; }; + if (yych != 'e') { gotoCase = 35; continue; }; + } +case 65: + yych = this._charAt(++cursor); + if (yych <= ',') { + if (yych != '+') { gotoCase = 45; continue; }; + } else { + if (yych <= '-') { gotoCase = 66; continue; }; + if (yych <= '/') { gotoCase = 45; continue; }; + if (yych <= '9') { gotoCase = 67; continue; }; + { gotoCase = 45; continue; }; + } +case 66: + yych = this._charAt(++cursor); + if (yych <= '/') { gotoCase = 45; continue; }; + if (yych >= ':') { gotoCase = 45; continue; }; +case 67: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '/') { gotoCase = 35; continue; }; + if (yych <= '9') { gotoCase = 67; continue; }; + { gotoCase = 35; continue; }; +case 69: + yych = this._charAt(++cursor); + if (yych <= '/') { gotoCase = 45; continue; }; + if (yych >= ':') { gotoCase = 45; continue; }; +case 70: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '/') { gotoCase = 35; continue; }; + if (yych <= '9') { gotoCase = 70; continue; }; + { gotoCase = 35; continue; }; +case 72: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '/') { gotoCase = 35; continue; }; + if (yych <= '7') { gotoCase = 72; continue; }; + { gotoCase = 35; continue; }; +case 74: + yych = this._charAt(++cursor); + if (yych <= '@') { + if (yych <= '/') { gotoCase = 45; continue; }; + if (yych >= ':') { gotoCase = 45; continue; }; + } else { + if (yych <= 'F') { gotoCase = 75; continue; }; + if (yych <= '`') { gotoCase = 45; continue; }; + if (yych >= 'g') { gotoCase = 45; continue; }; + } +case 75: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '@') { + if (yych <= '/') { gotoCase = 35; continue; }; + if (yych <= '9') { gotoCase = 75; continue; }; + { gotoCase = 35; continue; }; + } else { + if (yych <= 'F') { gotoCase = 75; continue; }; + if (yych <= '`') { gotoCase = 35; continue; }; + if (yych <= 'f') { gotoCase = 75; continue; }; + { gotoCase = 35; continue; }; + } +case 77: + yych = this._charAt(++cursor); + { gotoCase = 33; continue; }; +case 78: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '\f') { + if (yych == '\n') { gotoCase = 85; continue; }; + { gotoCase = 78; continue; }; + } else { + if (yych <= '\r') { gotoCase = 85; continue; }; + if (yych == '*') { gotoCase = 83; continue; }; + { gotoCase = 78; continue; }; + } +case 80: + ++cursor; + yych = this._charAt(cursor); + if (yych == '\n') { gotoCase = 82; continue; }; + if (yych != '\r') { gotoCase = 80; continue; }; +case 82: + { this.tokenType = "javascript-comment"; return cursor; } +case 83: + ++cursor; + yych = this._charAt(cursor); + if (yych == '*') { gotoCase = 83; continue; }; + if (yych == '/') { gotoCase = 87; continue; }; + { gotoCase = 78; continue; }; +case 85: + ++cursor; + this.setLexCondition(this._lexConditions.COMMENT); + { this.tokenType = "javascript-comment"; return cursor; } +case 87: + ++cursor; + { this.tokenType = "javascript-comment"; return cursor; } +case 89: + yyaccept = 3; + YYMARKER = ++cursor; + yych = this._charAt(cursor); + if (yych <= 'D') { + if (yych <= '/') { gotoCase = 35; continue; }; + if (yych <= '9') { gotoCase = 89; continue; }; + { gotoCase = 35; continue; }; + } else { + if (yych <= 'E') { gotoCase = 91; continue; }; + if (yych != 'e') { gotoCase = 35; continue; }; + } +case 91: + yych = this._charAt(++cursor); + if (yych <= ',') { + if (yych != '+') { gotoCase = 45; continue; }; + } else { + if (yych <= '-') { gotoCase = 92; continue; }; + if (yych <= '/') { gotoCase = 45; continue; }; + if (yych <= '9') { gotoCase = 93; continue; }; + { gotoCase = 45; continue; }; + } +case 92: + yych = this._charAt(++cursor); + if (yych <= '/') { gotoCase = 45; continue; }; + if (yych >= ':') { gotoCase = 45; continue; }; +case 93: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '/') { gotoCase = 35; continue; }; + if (yych <= '9') { gotoCase = 93; continue; }; + { gotoCase = 35; continue; }; +case 95: + ++cursor; + yych = this._charAt(cursor); +case 96: + if (yych <= '\r') { + if (yych == '\n') { gotoCase = 45; continue; }; + if (yych <= '\f') { gotoCase = 95; continue; }; + { gotoCase = 45; continue; }; + } else { + if (yych <= '\'') { + if (yych <= '&') { gotoCase = 95; continue; }; + { gotoCase = 98; continue; }; + } else { + if (yych != '\\') { gotoCase = 95; continue; }; + } + } + ++cursor; + yych = this._charAt(cursor); + if (yych <= 'a') { + if (yych <= '!') { + if (yych <= '\n') { + if (yych <= '\t') { gotoCase = 45; continue; }; + { gotoCase = 101; continue; }; + } else { + if (yych == '\r') { gotoCase = 101; continue; }; + { gotoCase = 45; continue; }; + } + } else { + if (yych <= '\'') { + if (yych <= '"') { gotoCase = 95; continue; }; + if (yych <= '&') { gotoCase = 45; continue; }; + { gotoCase = 95; continue; }; + } else { + if (yych == '\\') { gotoCase = 95; continue; }; + { gotoCase = 45; continue; }; + } + } + } else { + if (yych <= 'q') { + if (yych <= 'f') { + if (yych <= 'b') { gotoCase = 95; continue; }; + if (yych <= 'e') { gotoCase = 45; continue; }; + { gotoCase = 95; continue; }; + } else { + if (yych == 'n') { gotoCase = 95; continue; }; + { gotoCase = 45; continue; }; + } + } else { + if (yych <= 't') { + if (yych == 's') { gotoCase = 45; continue; }; + { gotoCase = 95; continue; }; + } else { + if (yych <= 'u') { gotoCase = 100; continue; }; + if (yych <= 'v') { gotoCase = 95; continue; }; + { gotoCase = 45; continue; }; + } + } + } +case 98: + ++cursor; + { this.tokenType = "javascript-string"; return cursor; } +case 100: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '@') { + if (yych <= '/') { gotoCase = 45; continue; }; + if (yych <= '9') { gotoCase = 103; continue; }; + { gotoCase = 45; continue; }; + } else { + if (yych <= 'F') { gotoCase = 103; continue; }; + if (yych <= '`') { gotoCase = 45; continue; }; + if (yych <= 'f') { gotoCase = 103; continue; }; + { gotoCase = 45; continue; }; + } +case 101: + ++cursor; + this.setLexCondition(this._lexConditions.SSTRING); + { this.tokenType = "javascript-string"; return cursor; } +case 103: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '@') { + if (yych <= '/') { gotoCase = 45; continue; }; + if (yych >= ':') { gotoCase = 45; continue; }; + } else { + if (yych <= 'F') { gotoCase = 104; continue; }; + if (yych <= '`') { gotoCase = 45; continue; }; + if (yych >= 'g') { gotoCase = 45; continue; }; + } +case 104: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '@') { + if (yych <= '/') { gotoCase = 45; continue; }; + if (yych >= ':') { gotoCase = 45; continue; }; + } else { + if (yych <= 'F') { gotoCase = 105; continue; }; + if (yych <= '`') { gotoCase = 45; continue; }; + if (yych >= 'g') { gotoCase = 45; continue; }; + } +case 105: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '@') { + if (yych <= '/') { gotoCase = 45; continue; }; + if (yych <= '9') { gotoCase = 95; continue; }; + { gotoCase = 45; continue; }; + } else { + if (yych <= 'F') { gotoCase = 95; continue; }; + if (yych <= '`') { gotoCase = 45; continue; }; + if (yych <= 'f') { gotoCase = 95; continue; }; + { gotoCase = 45; continue; }; + } +case 106: + ++cursor; + yych = this._charAt(cursor); +case 107: + if (yych <= '\r') { + if (yych == '\n') { gotoCase = 45; continue; }; + if (yych <= '\f') { gotoCase = 106; continue; }; + { gotoCase = 45; continue; }; + } else { + if (yych <= '"') { + if (yych <= '!') { gotoCase = 106; continue; }; + { gotoCase = 98; continue; }; + } else { + if (yych != '\\') { gotoCase = 106; continue; }; + } + } + ++cursor; + yych = this._charAt(cursor); + if (yych <= 'a') { + if (yych <= '!') { + if (yych <= '\n') { + if (yych <= '\t') { gotoCase = 45; continue; }; + { gotoCase = 110; continue; }; + } else { + if (yych == '\r') { gotoCase = 110; continue; }; + { gotoCase = 45; continue; }; + } + } else { + if (yych <= '\'') { + if (yych <= '"') { gotoCase = 106; continue; }; + if (yych <= '&') { gotoCase = 45; continue; }; + { gotoCase = 106; continue; }; + } else { + if (yych == '\\') { gotoCase = 106; continue; }; + { gotoCase = 45; continue; }; + } + } + } else { + if (yych <= 'q') { + if (yych <= 'f') { + if (yych <= 'b') { gotoCase = 106; continue; }; + if (yych <= 'e') { gotoCase = 45; continue; }; + { gotoCase = 106; continue; }; + } else { + if (yych == 'n') { gotoCase = 106; continue; }; + { gotoCase = 45; continue; }; + } + } else { + if (yych <= 't') { + if (yych == 's') { gotoCase = 45; continue; }; + { gotoCase = 106; continue; }; + } else { + if (yych <= 'u') { gotoCase = 109; continue; }; + if (yych <= 'v') { gotoCase = 106; continue; }; + { gotoCase = 45; continue; }; + } + } + } +case 109: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '@') { + if (yych <= '/') { gotoCase = 45; continue; }; + if (yych <= '9') { gotoCase = 112; continue; }; + { gotoCase = 45; continue; }; + } else { + if (yych <= 'F') { gotoCase = 112; continue; }; + if (yych <= '`') { gotoCase = 45; continue; }; + if (yych <= 'f') { gotoCase = 112; continue; }; + { gotoCase = 45; continue; }; + } +case 110: + ++cursor; + this.setLexCondition(this._lexConditions.DSTRING); + { this.tokenType = "javascript-string"; return cursor; } +case 112: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '@') { + if (yych <= '/') { gotoCase = 45; continue; }; + if (yych >= ':') { gotoCase = 45; continue; }; + } else { + if (yych <= 'F') { gotoCase = 113; continue; }; + if (yych <= '`') { gotoCase = 45; continue; }; + if (yych >= 'g') { gotoCase = 45; continue; }; + } +case 113: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '@') { + if (yych <= '/') { gotoCase = 45; continue; }; + if (yych >= ':') { gotoCase = 45; continue; }; + } else { + if (yych <= 'F') { gotoCase = 114; continue; }; + if (yych <= '`') { gotoCase = 45; continue; }; + if (yych >= 'g') { gotoCase = 45; continue; }; + } +case 114: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '@') { + if (yych <= '/') { gotoCase = 45; continue; }; + if (yych <= '9') { gotoCase = 106; continue; }; + { gotoCase = 45; continue; }; + } else { + if (yych <= 'F') { gotoCase = 106; continue; }; + if (yych <= '`') { gotoCase = 45; continue; }; + if (yych <= 'f') { gotoCase = 106; continue; }; + { gotoCase = 45; continue; }; + } +case 115: + ++cursor; + if ((yych = this._charAt(cursor)) == '=') { gotoCase = 43; continue; }; + { gotoCase = 18; continue; }; +/* *********************************** */ +case this.case_DSTRING: + yych = this._charAt(cursor); + if (yych <= '\r') { + if (yych == '\n') { gotoCase = 120; continue; }; + if (yych <= '\f') { gotoCase = 119; continue; }; + { gotoCase = 120; continue; }; + } else { + if (yych <= '"') { + if (yych <= '!') { gotoCase = 119; continue; }; + { gotoCase = 122; continue; }; + } else { + if (yych == '\\') { gotoCase = 124; continue; }; + { gotoCase = 119; continue; }; + } + } +case 118: + { this.tokenType = "javascript-string"; return cursor; } +case 119: + yyaccept = 0; + yych = this._charAt(YYMARKER = ++cursor); + { gotoCase = 126; continue; }; +case 120: + ++cursor; +case 121: + { this.tokenType = null; return cursor; } +case 122: + ++cursor; +case 123: + this.setLexCondition(this._lexConditions.NODIV); + { this.tokenType = "javascript-string"; return cursor; } +case 124: + yyaccept = 1; + yych = this._charAt(YYMARKER = ++cursor); + if (yych <= 'e') { + if (yych <= '\'') { + if (yych == '"') { gotoCase = 125; continue; }; + if (yych <= '&') { gotoCase = 121; continue; }; + } else { + if (yych <= '\\') { + if (yych <= '[') { gotoCase = 121; continue; }; + } else { + if (yych != 'b') { gotoCase = 121; continue; }; + } + } + } else { + if (yych <= 'r') { + if (yych <= 'm') { + if (yych >= 'g') { gotoCase = 121; continue; }; + } else { + if (yych <= 'n') { gotoCase = 125; continue; }; + if (yych <= 'q') { gotoCase = 121; continue; }; + } + } else { + if (yych <= 't') { + if (yych <= 's') { gotoCase = 121; continue; }; + } else { + if (yych <= 'u') { gotoCase = 127; continue; }; + if (yych >= 'w') { gotoCase = 121; continue; }; + } + } + } +case 125: + yyaccept = 0; + YYMARKER = ++cursor; + yych = this._charAt(cursor); +case 126: + if (yych <= '\r') { + if (yych == '\n') { gotoCase = 118; continue; }; + if (yych <= '\f') { gotoCase = 125; continue; }; + { gotoCase = 118; continue; }; + } else { + if (yych <= '"') { + if (yych <= '!') { gotoCase = 125; continue; }; + { gotoCase = 133; continue; }; + } else { + if (yych == '\\') { gotoCase = 132; continue; }; + { gotoCase = 125; continue; }; + } + } +case 127: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '@') { + if (yych <= '/') { gotoCase = 128; continue; }; + if (yych <= '9') { gotoCase = 129; continue; }; + } else { + if (yych <= 'F') { gotoCase = 129; continue; }; + if (yych <= '`') { gotoCase = 128; continue; }; + if (yych <= 'f') { gotoCase = 129; continue; }; + } +case 128: + cursor = YYMARKER; + if (yyaccept <= 0) { + { gotoCase = 118; continue; }; + } else { + { gotoCase = 121; continue; }; + } +case 129: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '@') { + if (yych <= '/') { gotoCase = 128; continue; }; + if (yych >= ':') { gotoCase = 128; continue; }; + } else { + if (yych <= 'F') { gotoCase = 130; continue; }; + if (yych <= '`') { gotoCase = 128; continue; }; + if (yych >= 'g') { gotoCase = 128; continue; }; + } +case 130: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '@') { + if (yych <= '/') { gotoCase = 128; continue; }; + if (yych >= ':') { gotoCase = 128; continue; }; + } else { + if (yych <= 'F') { gotoCase = 131; continue; }; + if (yych <= '`') { gotoCase = 128; continue; }; + if (yych >= 'g') { gotoCase = 128; continue; }; + } +case 131: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '@') { + if (yych <= '/') { gotoCase = 128; continue; }; + if (yych <= '9') { gotoCase = 125; continue; }; + { gotoCase = 128; continue; }; + } else { + if (yych <= 'F') { gotoCase = 125; continue; }; + if (yych <= '`') { gotoCase = 128; continue; }; + if (yych <= 'f') { gotoCase = 125; continue; }; + { gotoCase = 128; continue; }; + } +case 132: + ++cursor; + yych = this._charAt(cursor); + if (yych <= 'e') { + if (yych <= '\'') { + if (yych == '"') { gotoCase = 125; continue; }; + if (yych <= '&') { gotoCase = 128; continue; }; + { gotoCase = 125; continue; }; + } else { + if (yych <= '\\') { + if (yych <= '[') { gotoCase = 128; continue; }; + { gotoCase = 125; continue; }; + } else { + if (yych == 'b') { gotoCase = 125; continue; }; + { gotoCase = 128; continue; }; + } + } + } else { + if (yych <= 'r') { + if (yych <= 'm') { + if (yych <= 'f') { gotoCase = 125; continue; }; + { gotoCase = 128; continue; }; + } else { + if (yych <= 'n') { gotoCase = 125; continue; }; + if (yych <= 'q') { gotoCase = 128; continue; }; + { gotoCase = 125; continue; }; + } + } else { + if (yych <= 't') { + if (yych <= 's') { gotoCase = 128; continue; }; + { gotoCase = 125; continue; }; + } else { + if (yych <= 'u') { gotoCase = 127; continue; }; + if (yych <= 'v') { gotoCase = 125; continue; }; + { gotoCase = 128; continue; }; + } + } + } +case 133: + ++cursor; + yych = this._charAt(cursor); + { gotoCase = 123; continue; }; +/* *********************************** */ +case this.case_NODIV: + yych = this._charAt(cursor); + if (yych <= '9') { + if (yych <= '(') { + if (yych <= '#') { + if (yych <= ' ') { gotoCase = 136; continue; }; + if (yych <= '!') { gotoCase = 138; continue; }; + if (yych <= '"') { gotoCase = 140; continue; }; + } else { + if (yych <= '%') { + if (yych <= '$') { gotoCase = 141; continue; }; + { gotoCase = 143; continue; }; + } else { + if (yych <= '&') { gotoCase = 144; continue; }; + if (yych <= '\'') { gotoCase = 145; continue; }; + { gotoCase = 146; continue; }; + } + } + } else { + if (yych <= ',') { + if (yych <= ')') { gotoCase = 147; continue; }; + if (yych <= '*') { gotoCase = 149; continue; }; + if (yych <= '+') { gotoCase = 150; continue; }; + { gotoCase = 146; continue; }; + } else { + if (yych <= '.') { + if (yych <= '-') { gotoCase = 151; continue; }; + { gotoCase = 152; continue; }; + } else { + if (yych <= '/') { gotoCase = 153; continue; }; + if (yych <= '0') { gotoCase = 154; continue; }; + { gotoCase = 156; continue; }; + } + } + } + } else { + if (yych <= '\\') { + if (yych <= '>') { + if (yych <= ';') { gotoCase = 146; continue; }; + if (yych <= '<') { gotoCase = 157; continue; }; + if (yych <= '=') { gotoCase = 158; continue; }; + { gotoCase = 159; continue; }; + } else { + if (yych <= '@') { + if (yych <= '?') { gotoCase = 146; continue; }; + } else { + if (yych <= 'Z') { gotoCase = 141; continue; }; + if (yych <= '[') { gotoCase = 146; continue; }; + { gotoCase = 160; continue; }; + } + } + } else { + if (yych <= 'z') { + if (yych <= '^') { + if (yych <= ']') { gotoCase = 146; continue; }; + { gotoCase = 161; continue; }; + } else { + if (yych != '`') { gotoCase = 141; continue; }; + } + } else { + if (yych <= '|') { + if (yych <= '{') { gotoCase = 146; continue; }; + { gotoCase = 162; continue; }; + } else { + if (yych <= '~') { gotoCase = 146; continue; }; + if (yych >= 0x80) { gotoCase = 141; continue; }; + } + } + } + } +case 136: + ++cursor; +case 137: + { this.tokenType = null; return cursor; } +case 138: + ++cursor; + if ((yych = this._charAt(cursor)) == '=') { gotoCase = 260; continue; }; +case 139: + { this.tokenType = null; return cursor; } +case 140: + yyaccept = 0; + yych = this._charAt(YYMARKER = ++cursor); + if (yych == '\n') { gotoCase = 137; continue; }; + if (yych == '\r') { gotoCase = 137; continue; }; + { gotoCase = 252; continue; }; +case 141: + yyaccept = 1; + yych = this._charAt(YYMARKER = ++cursor); + { gotoCase = 170; continue; }; +case 142: + this.setLexCondition(this._lexConditions.DIV); + { + var token = this._line.substring(cursorOnEnter, cursor); + if (this._keywords[token] === true && token !== "__proto__") + this.tokenType = "javascript-keyword"; + else + this.tokenType = "javascript-ident"; + return cursor; + } +case 143: + yych = this._charAt(++cursor); + if (yych == '=') { gotoCase = 163; continue; }; + { gotoCase = 139; continue; }; +case 144: + yych = this._charAt(++cursor); + if (yych == '&') { gotoCase = 163; continue; }; + if (yych == '=') { gotoCase = 163; continue; }; + { gotoCase = 139; continue; }; +case 145: + yyaccept = 0; + yych = this._charAt(YYMARKER = ++cursor); + if (yych == '\n') { gotoCase = 137; continue; }; + if (yych == '\r') { gotoCase = 137; continue; }; + { gotoCase = 241; continue; }; +case 146: + yych = this._charAt(++cursor); + { gotoCase = 139; continue; }; +case 147: + ++cursor; + this.setLexCondition(this._lexConditions.DIV); + { this.tokenType = null; return cursor; } +case 149: + yych = this._charAt(++cursor); + if (yych == '=') { gotoCase = 163; continue; }; + { gotoCase = 139; continue; }; +case 150: + yych = this._charAt(++cursor); + if (yych == '+') { gotoCase = 163; continue; }; + if (yych == '=') { gotoCase = 163; continue; }; + { gotoCase = 139; continue; }; +case 151: + yych = this._charAt(++cursor); + if (yych == '-') { gotoCase = 163; continue; }; + if (yych == '=') { gotoCase = 163; continue; }; + { gotoCase = 139; continue; }; +case 152: + yych = this._charAt(++cursor); + if (yych <= '/') { gotoCase = 139; continue; }; + if (yych <= '9') { gotoCase = 234; continue; }; + { gotoCase = 139; continue; }; +case 153: + yyaccept = 0; + yych = this._charAt(YYMARKER = ++cursor); + if (yych <= '*') { + if (yych <= '\f') { + if (yych == '\n') { gotoCase = 137; continue; }; + { gotoCase = 197; continue; }; + } else { + if (yych <= '\r') { gotoCase = 137; continue; }; + if (yych <= ')') { gotoCase = 197; continue; }; + { gotoCase = 202; continue; }; + } + } else { + if (yych <= 'Z') { + if (yych == '/') { gotoCase = 204; continue; }; + { gotoCase = 197; continue; }; + } else { + if (yych <= '[') { gotoCase = 200; continue; }; + if (yych <= '\\') { gotoCase = 199; continue; }; + if (yych <= ']') { gotoCase = 137; continue; }; + { gotoCase = 197; continue; }; + } + } +case 154: + yyaccept = 2; + yych = this._charAt(YYMARKER = ++cursor); + if (yych <= 'E') { + if (yych <= '/') { + if (yych == '.') { gotoCase = 183; continue; }; + } else { + if (yych <= '7') { gotoCase = 192; continue; }; + if (yych >= 'E') { gotoCase = 182; continue; }; + } + } else { + if (yych <= 'd') { + if (yych == 'X') { gotoCase = 194; continue; }; + } else { + if (yych <= 'e') { gotoCase = 182; continue; }; + if (yych == 'x') { gotoCase = 194; continue; }; + } + } +case 155: + this.setLexCondition(this._lexConditions.DIV); + { this.tokenType = "javascript-number"; return cursor; } +case 156: + yyaccept = 2; + yych = this._charAt(YYMARKER = ++cursor); + if (yych <= '9') { + if (yych == '.') { gotoCase = 183; continue; }; + if (yych <= '/') { gotoCase = 155; continue; }; + { gotoCase = 180; continue; }; + } else { + if (yych <= 'E') { + if (yych <= 'D') { gotoCase = 155; continue; }; + { gotoCase = 182; continue; }; + } else { + if (yych == 'e') { gotoCase = 182; continue; }; + { gotoCase = 155; continue; }; + } + } +case 157: + yych = this._charAt(++cursor); + if (yych <= ';') { gotoCase = 139; continue; }; + if (yych <= '<') { gotoCase = 179; continue; }; + if (yych <= '=') { gotoCase = 163; continue; }; + { gotoCase = 139; continue; }; +case 158: + yych = this._charAt(++cursor); + if (yych == '=') { gotoCase = 178; continue; }; + { gotoCase = 139; continue; }; +case 159: + yych = this._charAt(++cursor); + if (yych <= '<') { gotoCase = 139; continue; }; + if (yych <= '=') { gotoCase = 163; continue; }; + if (yych <= '>') { gotoCase = 176; continue; }; + { gotoCase = 139; continue; }; +case 160: + yyaccept = 0; + yych = this._charAt(YYMARKER = ++cursor); + if (yych == 'u') { gotoCase = 164; continue; }; + { gotoCase = 137; continue; }; +case 161: + yych = this._charAt(++cursor); + if (yych == '=') { gotoCase = 163; continue; }; + { gotoCase = 139; continue; }; +case 162: + yych = this._charAt(++cursor); + if (yych == '=') { gotoCase = 163; continue; }; + if (yych != '|') { gotoCase = 139; continue; }; +case 163: + yych = this._charAt(++cursor); + { gotoCase = 139; continue; }; +case 164: + yych = this._charAt(++cursor); + if (yych <= '@') { + if (yych <= '/') { gotoCase = 165; continue; }; + if (yych <= '9') { gotoCase = 166; continue; }; + } else { + if (yych <= 'F') { gotoCase = 166; continue; }; + if (yych <= '`') { gotoCase = 165; continue; }; + if (yych <= 'f') { gotoCase = 166; continue; }; + } +case 165: + cursor = YYMARKER; + if (yyaccept <= 1) { + if (yyaccept <= 0) { + { gotoCase = 137; continue; }; + } else { + { gotoCase = 142; continue; }; + } + } else { + if (yyaccept <= 2) { + { gotoCase = 155; continue; }; + } else { + { gotoCase = 217; continue; }; + } + } +case 166: + yych = this._charAt(++cursor); + if (yych <= '@') { + if (yych <= '/') { gotoCase = 165; continue; }; + if (yych >= ':') { gotoCase = 165; continue; }; + } else { + if (yych <= 'F') { gotoCase = 167; continue; }; + if (yych <= '`') { gotoCase = 165; continue; }; + if (yych >= 'g') { gotoCase = 165; continue; }; + } +case 167: + yych = this._charAt(++cursor); + if (yych <= '@') { + if (yych <= '/') { gotoCase = 165; continue; }; + if (yych >= ':') { gotoCase = 165; continue; }; + } else { + if (yych <= 'F') { gotoCase = 168; continue; }; + if (yych <= '`') { gotoCase = 165; continue; }; + if (yych >= 'g') { gotoCase = 165; continue; }; + } +case 168: + yych = this._charAt(++cursor); + if (yych <= '@') { + if (yych <= '/') { gotoCase = 165; continue; }; + if (yych >= ':') { gotoCase = 165; continue; }; + } else { + if (yych <= 'F') { gotoCase = 169; continue; }; + if (yych <= '`') { gotoCase = 165; continue; }; + if (yych >= 'g') { gotoCase = 165; continue; }; + } +case 169: + yyaccept = 1; + YYMARKER = ++cursor; + yych = this._charAt(cursor); +case 170: + if (yych <= '[') { + if (yych <= '/') { + if (yych == '$') { gotoCase = 169; continue; }; + { gotoCase = 142; continue; }; + } else { + if (yych <= '9') { gotoCase = 169; continue; }; + if (yych <= '@') { gotoCase = 142; continue; }; + if (yych <= 'Z') { gotoCase = 169; continue; }; + { gotoCase = 142; continue; }; + } + } else { + if (yych <= '_') { + if (yych <= '\\') { gotoCase = 171; continue; }; + if (yych <= '^') { gotoCase = 142; continue; }; + { gotoCase = 169; continue; }; + } else { + if (yych <= '`') { gotoCase = 142; continue; }; + if (yych <= 'z') { gotoCase = 169; continue; }; + if (yych <= String.fromCharCode(0x7F)) { gotoCase = 142; continue; }; + { gotoCase = 169; continue; }; + } + } +case 171: + ++cursor; + yych = this._charAt(cursor); + if (yych != 'u') { gotoCase = 165; continue; }; + ++cursor; + yych = this._charAt(cursor); + if (yych <= '@') { + if (yych <= '/') { gotoCase = 165; continue; }; + if (yych >= ':') { gotoCase = 165; continue; }; + } else { + if (yych <= 'F') { gotoCase = 173; continue; }; + if (yych <= '`') { gotoCase = 165; continue; }; + if (yych >= 'g') { gotoCase = 165; continue; }; + } +case 173: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '@') { + if (yych <= '/') { gotoCase = 165; continue; }; + if (yych >= ':') { gotoCase = 165; continue; }; + } else { + if (yych <= 'F') { gotoCase = 174; continue; }; + if (yych <= '`') { gotoCase = 165; continue; }; + if (yych >= 'g') { gotoCase = 165; continue; }; + } +case 174: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '@') { + if (yych <= '/') { gotoCase = 165; continue; }; + if (yych >= ':') { gotoCase = 165; continue; }; + } else { + if (yych <= 'F') { gotoCase = 175; continue; }; + if (yych <= '`') { gotoCase = 165; continue; }; + if (yych >= 'g') { gotoCase = 165; continue; }; + } +case 175: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '@') { + if (yych <= '/') { gotoCase = 165; continue; }; + if (yych <= '9') { gotoCase = 169; continue; }; + { gotoCase = 165; continue; }; + } else { + if (yych <= 'F') { gotoCase = 169; continue; }; + if (yych <= '`') { gotoCase = 165; continue; }; + if (yych <= 'f') { gotoCase = 169; continue; }; + { gotoCase = 165; continue; }; + } +case 176: + yych = this._charAt(++cursor); + if (yych <= '<') { gotoCase = 139; continue; }; + if (yych <= '=') { gotoCase = 163; continue; }; + if (yych >= '?') { gotoCase = 139; continue; }; + yych = this._charAt(++cursor); + if (yych == '=') { gotoCase = 163; continue; }; + { gotoCase = 139; continue; }; +case 178: + yych = this._charAt(++cursor); + if (yych == '=') { gotoCase = 163; continue; }; + { gotoCase = 139; continue; }; +case 179: + yych = this._charAt(++cursor); + if (yych == '=') { gotoCase = 163; continue; }; + { gotoCase = 139; continue; }; +case 180: + yyaccept = 2; + YYMARKER = ++cursor; + yych = this._charAt(cursor); + if (yych <= '9') { + if (yych == '.') { gotoCase = 183; continue; }; + if (yych <= '/') { gotoCase = 155; continue; }; + { gotoCase = 180; continue; }; + } else { + if (yych <= 'E') { + if (yych <= 'D') { gotoCase = 155; continue; }; + } else { + if (yych != 'e') { gotoCase = 155; continue; }; + } + } +case 182: + yych = this._charAt(++cursor); + if (yych <= ',') { + if (yych == '+') { gotoCase = 189; continue; }; + { gotoCase = 165; continue; }; + } else { + if (yych <= '-') { gotoCase = 189; continue; }; + if (yych <= '/') { gotoCase = 165; continue; }; + if (yych <= '9') { gotoCase = 190; continue; }; + { gotoCase = 165; continue; }; + } +case 183: + yyaccept = 2; + YYMARKER = ++cursor; + yych = this._charAt(cursor); + if (yych <= 'D') { + if (yych <= '/') { gotoCase = 155; continue; }; + if (yych <= '9') { gotoCase = 183; continue; }; + { gotoCase = 155; continue; }; + } else { + if (yych <= 'E') { gotoCase = 185; continue; }; + if (yych != 'e') { gotoCase = 155; continue; }; + } +case 185: + yych = this._charAt(++cursor); + if (yych <= ',') { + if (yych != '+') { gotoCase = 165; continue; }; + } else { + if (yych <= '-') { gotoCase = 186; continue; }; + if (yych <= '/') { gotoCase = 165; continue; }; + if (yych <= '9') { gotoCase = 187; continue; }; + { gotoCase = 165; continue; }; + } +case 186: + yych = this._charAt(++cursor); + if (yych <= '/') { gotoCase = 165; continue; }; + if (yych >= ':') { gotoCase = 165; continue; }; +case 187: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '/') { gotoCase = 155; continue; }; + if (yych <= '9') { gotoCase = 187; continue; }; + { gotoCase = 155; continue; }; +case 189: + yych = this._charAt(++cursor); + if (yych <= '/') { gotoCase = 165; continue; }; + if (yych >= ':') { gotoCase = 165; continue; }; +case 190: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '/') { gotoCase = 155; continue; }; + if (yych <= '9') { gotoCase = 190; continue; }; + { gotoCase = 155; continue; }; +case 192: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '/') { gotoCase = 155; continue; }; + if (yych <= '7') { gotoCase = 192; continue; }; + { gotoCase = 155; continue; }; +case 194: + yych = this._charAt(++cursor); + if (yych <= '@') { + if (yych <= '/') { gotoCase = 165; continue; }; + if (yych >= ':') { gotoCase = 165; continue; }; + } else { + if (yych <= 'F') { gotoCase = 195; continue; }; + if (yych <= '`') { gotoCase = 165; continue; }; + if (yych >= 'g') { gotoCase = 165; continue; }; + } +case 195: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '@') { + if (yych <= '/') { gotoCase = 155; continue; }; + if (yych <= '9') { gotoCase = 195; continue; }; + { gotoCase = 155; continue; }; + } else { + if (yych <= 'F') { gotoCase = 195; continue; }; + if (yych <= '`') { gotoCase = 155; continue; }; + if (yych <= 'f') { gotoCase = 195; continue; }; + { gotoCase = 155; continue; }; + } +case 197: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '.') { + if (yych <= '\n') { + if (yych <= '\t') { gotoCase = 197; continue; }; + { gotoCase = 165; continue; }; + } else { + if (yych == '\r') { gotoCase = 165; continue; }; + { gotoCase = 197; continue; }; + } + } else { + if (yych <= '[') { + if (yych <= '/') { gotoCase = 220; continue; }; + if (yych <= 'Z') { gotoCase = 197; continue; }; + { gotoCase = 228; continue; }; + } else { + if (yych <= '\\') { gotoCase = 227; continue; }; + if (yych <= ']') { gotoCase = 165; continue; }; + { gotoCase = 197; continue; }; + } + } +case 199: + yych = this._charAt(++cursor); + if (yych == '\n') { gotoCase = 165; continue; }; + if (yych == '\r') { gotoCase = 165; continue; }; + { gotoCase = 197; continue; }; +case 200: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '*') { + if (yych <= '\f') { + if (yych == '\n') { gotoCase = 165; continue; }; + { gotoCase = 200; continue; }; + } else { + if (yych <= '\r') { gotoCase = 165; continue; }; + if (yych <= ')') { gotoCase = 200; continue; }; + { gotoCase = 165; continue; }; + } + } else { + if (yych <= '[') { + if (yych == '/') { gotoCase = 165; continue; }; + { gotoCase = 200; continue; }; + } else { + if (yych <= '\\') { gotoCase = 215; continue; }; + if (yych <= ']') { gotoCase = 213; continue; }; + { gotoCase = 200; continue; }; + } + } +case 202: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '\f') { + if (yych == '\n') { gotoCase = 209; continue; }; + { gotoCase = 202; continue; }; + } else { + if (yych <= '\r') { gotoCase = 209; continue; }; + if (yych == '*') { gotoCase = 207; continue; }; + { gotoCase = 202; continue; }; + } +case 204: + ++cursor; + yych = this._charAt(cursor); + if (yych == '\n') { gotoCase = 206; continue; }; + if (yych != '\r') { gotoCase = 204; continue; }; +case 206: + { this.tokenType = "javascript-comment"; return cursor; } +case 207: + ++cursor; + yych = this._charAt(cursor); + if (yych == '*') { gotoCase = 207; continue; }; + if (yych == '/') { gotoCase = 211; continue; }; + { gotoCase = 202; continue; }; +case 209: + ++cursor; + this.setLexCondition(this._lexConditions.COMMENT); + { this.tokenType = "javascript-comment"; return cursor; } +case 211: + ++cursor; + { this.tokenType = "javascript-comment"; return cursor; } +case 213: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '*') { + if (yych <= '\f') { + if (yych == '\n') { gotoCase = 165; continue; }; + { gotoCase = 213; continue; }; + } else { + if (yych <= '\r') { gotoCase = 165; continue; }; + if (yych <= ')') { gotoCase = 213; continue; }; + { gotoCase = 197; continue; }; + } + } else { + if (yych <= 'Z') { + if (yych == '/') { gotoCase = 220; continue; }; + { gotoCase = 213; continue; }; + } else { + if (yych <= '[') { gotoCase = 218; continue; }; + if (yych <= '\\') { gotoCase = 216; continue; }; + { gotoCase = 213; continue; }; + } + } +case 215: + ++cursor; + yych = this._charAt(cursor); + if (yych == '\n') { gotoCase = 165; continue; }; + if (yych == '\r') { gotoCase = 165; continue; }; + { gotoCase = 200; continue; }; +case 216: + yyaccept = 3; + YYMARKER = ++cursor; + yych = this._charAt(cursor); + if (yych == '\n') { gotoCase = 217; continue; }; + if (yych != '\r') { gotoCase = 213; continue; }; +case 217: + this.setLexCondition(this._lexConditions.REGEX); + { this.tokenType = "javascript-regexp"; return cursor; } +case 218: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '*') { + if (yych <= '\f') { + if (yych == '\n') { gotoCase = 165; continue; }; + { gotoCase = 218; continue; }; + } else { + if (yych <= '\r') { gotoCase = 165; continue; }; + if (yych <= ')') { gotoCase = 218; continue; }; + { gotoCase = 165; continue; }; + } + } else { + if (yych <= '[') { + if (yych == '/') { gotoCase = 165; continue; }; + { gotoCase = 218; continue; }; + } else { + if (yych <= '\\') { gotoCase = 225; continue; }; + if (yych <= ']') { gotoCase = 223; continue; }; + { gotoCase = 218; continue; }; + } + } +case 220: + ++cursor; + yych = this._charAt(cursor); + if (yych <= 'h') { + if (yych == 'g') { gotoCase = 220; continue; }; + } else { + if (yych <= 'i') { gotoCase = 220; continue; }; + if (yych == 'm') { gotoCase = 220; continue; }; + } + { this.tokenType = "javascript-regexp"; return cursor; } +case 223: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '*') { + if (yych <= '\f') { + if (yych == '\n') { gotoCase = 165; continue; }; + { gotoCase = 223; continue; }; + } else { + if (yych <= '\r') { gotoCase = 165; continue; }; + if (yych <= ')') { gotoCase = 223; continue; }; + { gotoCase = 197; continue; }; + } + } else { + if (yych <= 'Z') { + if (yych == '/') { gotoCase = 220; continue; }; + { gotoCase = 223; continue; }; + } else { + if (yych <= '[') { gotoCase = 218; continue; }; + if (yych <= '\\') { gotoCase = 226; continue; }; + { gotoCase = 223; continue; }; + } + } +case 225: + ++cursor; + yych = this._charAt(cursor); + if (yych == '\n') { gotoCase = 165; continue; }; + if (yych == '\r') { gotoCase = 165; continue; }; + { gotoCase = 218; continue; }; +case 226: + yyaccept = 3; + YYMARKER = ++cursor; + yych = this._charAt(cursor); + if (yych == '\n') { gotoCase = 217; continue; }; + if (yych == '\r') { gotoCase = 217; continue; }; + { gotoCase = 223; continue; }; +case 227: + yyaccept = 3; + YYMARKER = ++cursor; + yych = this._charAt(cursor); + if (yych == '\n') { gotoCase = 217; continue; }; + if (yych == '\r') { gotoCase = 217; continue; }; + { gotoCase = 197; continue; }; +case 228: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '*') { + if (yych <= '\f') { + if (yych == '\n') { gotoCase = 165; continue; }; + { gotoCase = 228; continue; }; + } else { + if (yych <= '\r') { gotoCase = 165; continue; }; + if (yych <= ')') { gotoCase = 228; continue; }; + { gotoCase = 165; continue; }; + } + } else { + if (yych <= '[') { + if (yych == '/') { gotoCase = 165; continue; }; + { gotoCase = 228; continue; }; + } else { + if (yych <= '\\') { gotoCase = 232; continue; }; + if (yych >= '^') { gotoCase = 228; continue; }; + } + } +case 230: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '*') { + if (yych <= '\f') { + if (yych == '\n') { gotoCase = 165; continue; }; + { gotoCase = 230; continue; }; + } else { + if (yych <= '\r') { gotoCase = 165; continue; }; + if (yych <= ')') { gotoCase = 230; continue; }; + { gotoCase = 197; continue; }; + } + } else { + if (yych <= 'Z') { + if (yych == '/') { gotoCase = 220; continue; }; + { gotoCase = 230; continue; }; + } else { + if (yych <= '[') { gotoCase = 228; continue; }; + if (yych <= '\\') { gotoCase = 233; continue; }; + { gotoCase = 230; continue; }; + } + } +case 232: + ++cursor; + yych = this._charAt(cursor); + if (yych == '\n') { gotoCase = 165; continue; }; + if (yych == '\r') { gotoCase = 165; continue; }; + { gotoCase = 228; continue; }; +case 233: + yyaccept = 3; + YYMARKER = ++cursor; + yych = this._charAt(cursor); + if (yych == '\n') { gotoCase = 217; continue; }; + if (yych == '\r') { gotoCase = 217; continue; }; + { gotoCase = 230; continue; }; +case 234: + yyaccept = 2; + YYMARKER = ++cursor; + yych = this._charAt(cursor); + if (yych <= 'D') { + if (yych <= '/') { gotoCase = 155; continue; }; + if (yych <= '9') { gotoCase = 234; continue; }; + { gotoCase = 155; continue; }; + } else { + if (yych <= 'E') { gotoCase = 236; continue; }; + if (yych != 'e') { gotoCase = 155; continue; }; + } +case 236: + yych = this._charAt(++cursor); + if (yych <= ',') { + if (yych != '+') { gotoCase = 165; continue; }; + } else { + if (yych <= '-') { gotoCase = 237; continue; }; + if (yych <= '/') { gotoCase = 165; continue; }; + if (yych <= '9') { gotoCase = 238; continue; }; + { gotoCase = 165; continue; }; + } +case 237: + yych = this._charAt(++cursor); + if (yych <= '/') { gotoCase = 165; continue; }; + if (yych >= ':') { gotoCase = 165; continue; }; +case 238: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '/') { gotoCase = 155; continue; }; + if (yych <= '9') { gotoCase = 238; continue; }; + { gotoCase = 155; continue; }; +case 240: + ++cursor; + yych = this._charAt(cursor); +case 241: + if (yych <= '\r') { + if (yych == '\n') { gotoCase = 165; continue; }; + if (yych <= '\f') { gotoCase = 240; continue; }; + { gotoCase = 165; continue; }; + } else { + if (yych <= '\'') { + if (yych <= '&') { gotoCase = 240; continue; }; + { gotoCase = 243; continue; }; + } else { + if (yych != '\\') { gotoCase = 240; continue; }; + } + } + ++cursor; + yych = this._charAt(cursor); + if (yych <= 'a') { + if (yych <= '!') { + if (yych <= '\n') { + if (yych <= '\t') { gotoCase = 165; continue; }; + { gotoCase = 246; continue; }; + } else { + if (yych == '\r') { gotoCase = 246; continue; }; + { gotoCase = 165; continue; }; + } + } else { + if (yych <= '\'') { + if (yych <= '"') { gotoCase = 240; continue; }; + if (yych <= '&') { gotoCase = 165; continue; }; + { gotoCase = 240; continue; }; + } else { + if (yych == '\\') { gotoCase = 240; continue; }; + { gotoCase = 165; continue; }; + } + } + } else { + if (yych <= 'q') { + if (yych <= 'f') { + if (yych <= 'b') { gotoCase = 240; continue; }; + if (yych <= 'e') { gotoCase = 165; continue; }; + { gotoCase = 240; continue; }; + } else { + if (yych == 'n') { gotoCase = 240; continue; }; + { gotoCase = 165; continue; }; + } + } else { + if (yych <= 't') { + if (yych == 's') { gotoCase = 165; continue; }; + { gotoCase = 240; continue; }; + } else { + if (yych <= 'u') { gotoCase = 245; continue; }; + if (yych <= 'v') { gotoCase = 240; continue; }; + { gotoCase = 165; continue; }; + } + } + } +case 243: + ++cursor; + { this.tokenType = "javascript-string"; return cursor; } +case 245: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '@') { + if (yych <= '/') { gotoCase = 165; continue; }; + if (yych <= '9') { gotoCase = 248; continue; }; + { gotoCase = 165; continue; }; + } else { + if (yych <= 'F') { gotoCase = 248; continue; }; + if (yych <= '`') { gotoCase = 165; continue; }; + if (yych <= 'f') { gotoCase = 248; continue; }; + { gotoCase = 165; continue; }; + } +case 246: + ++cursor; + this.setLexCondition(this._lexConditions.SSTRING); + { this.tokenType = "javascript-string"; return cursor; } +case 248: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '@') { + if (yych <= '/') { gotoCase = 165; continue; }; + if (yych >= ':') { gotoCase = 165; continue; }; + } else { + if (yych <= 'F') { gotoCase = 249; continue; }; + if (yych <= '`') { gotoCase = 165; continue; }; + if (yych >= 'g') { gotoCase = 165; continue; }; + } +case 249: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '@') { + if (yych <= '/') { gotoCase = 165; continue; }; + if (yych >= ':') { gotoCase = 165; continue; }; + } else { + if (yych <= 'F') { gotoCase = 250; continue; }; + if (yych <= '`') { gotoCase = 165; continue; }; + if (yych >= 'g') { gotoCase = 165; continue; }; + } +case 250: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '@') { + if (yych <= '/') { gotoCase = 165; continue; }; + if (yych <= '9') { gotoCase = 240; continue; }; + { gotoCase = 165; continue; }; + } else { + if (yych <= 'F') { gotoCase = 240; continue; }; + if (yych <= '`') { gotoCase = 165; continue; }; + if (yych <= 'f') { gotoCase = 240; continue; }; + { gotoCase = 165; continue; }; + } +case 251: + ++cursor; + yych = this._charAt(cursor); +case 252: + if (yych <= '\r') { + if (yych == '\n') { gotoCase = 165; continue; }; + if (yych <= '\f') { gotoCase = 251; continue; }; + { gotoCase = 165; continue; }; + } else { + if (yych <= '"') { + if (yych <= '!') { gotoCase = 251; continue; }; + { gotoCase = 243; continue; }; + } else { + if (yych != '\\') { gotoCase = 251; continue; }; + } + } + ++cursor; + yych = this._charAt(cursor); + if (yych <= 'a') { + if (yych <= '!') { + if (yych <= '\n') { + if (yych <= '\t') { gotoCase = 165; continue; }; + { gotoCase = 255; continue; }; + } else { + if (yych == '\r') { gotoCase = 255; continue; }; + { gotoCase = 165; continue; }; + } + } else { + if (yych <= '\'') { + if (yych <= '"') { gotoCase = 251; continue; }; + if (yych <= '&') { gotoCase = 165; continue; }; + { gotoCase = 251; continue; }; + } else { + if (yych == '\\') { gotoCase = 251; continue; }; + { gotoCase = 165; continue; }; + } + } + } else { + if (yych <= 'q') { + if (yych <= 'f') { + if (yych <= 'b') { gotoCase = 251; continue; }; + if (yych <= 'e') { gotoCase = 165; continue; }; + { gotoCase = 251; continue; }; + } else { + if (yych == 'n') { gotoCase = 251; continue; }; + { gotoCase = 165; continue; }; + } + } else { + if (yych <= 't') { + if (yych == 's') { gotoCase = 165; continue; }; + { gotoCase = 251; continue; }; + } else { + if (yych <= 'u') { gotoCase = 254; continue; }; + if (yych <= 'v') { gotoCase = 251; continue; }; + { gotoCase = 165; continue; }; + } + } + } +case 254: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '@') { + if (yych <= '/') { gotoCase = 165; continue; }; + if (yych <= '9') { gotoCase = 257; continue; }; + { gotoCase = 165; continue; }; + } else { + if (yych <= 'F') { gotoCase = 257; continue; }; + if (yych <= '`') { gotoCase = 165; continue; }; + if (yych <= 'f') { gotoCase = 257; continue; }; + { gotoCase = 165; continue; }; + } +case 255: + ++cursor; + this.setLexCondition(this._lexConditions.DSTRING); + { this.tokenType = "javascript-string"; return cursor; } +case 257: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '@') { + if (yych <= '/') { gotoCase = 165; continue; }; + if (yych >= ':') { gotoCase = 165; continue; }; + } else { + if (yych <= 'F') { gotoCase = 258; continue; }; + if (yych <= '`') { gotoCase = 165; continue; }; + if (yych >= 'g') { gotoCase = 165; continue; }; + } +case 258: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '@') { + if (yych <= '/') { gotoCase = 165; continue; }; + if (yych >= ':') { gotoCase = 165; continue; }; + } else { + if (yych <= 'F') { gotoCase = 259; continue; }; + if (yych <= '`') { gotoCase = 165; continue; }; + if (yych >= 'g') { gotoCase = 165; continue; }; + } +case 259: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '@') { + if (yych <= '/') { gotoCase = 165; continue; }; + if (yych <= '9') { gotoCase = 251; continue; }; + { gotoCase = 165; continue; }; + } else { + if (yych <= 'F') { gotoCase = 251; continue; }; + if (yych <= '`') { gotoCase = 165; continue; }; + if (yych <= 'f') { gotoCase = 251; continue; }; + { gotoCase = 165; continue; }; + } +case 260: + ++cursor; + if ((yych = this._charAt(cursor)) == '=') { gotoCase = 163; continue; }; + { gotoCase = 139; continue; }; +/* *********************************** */ +case this.case_REGEX: + yych = this._charAt(cursor); + if (yych <= '.') { + if (yych <= '\n') { + if (yych <= '\t') { gotoCase = 264; continue; }; + { gotoCase = 265; continue; }; + } else { + if (yych == '\r') { gotoCase = 265; continue; }; + { gotoCase = 264; continue; }; + } + } else { + if (yych <= '[') { + if (yych <= '/') { gotoCase = 267; continue; }; + if (yych <= 'Z') { gotoCase = 264; continue; }; + { gotoCase = 269; continue; }; + } else { + if (yych <= '\\') { gotoCase = 270; continue; }; + if (yych <= ']') { gotoCase = 265; continue; }; + { gotoCase = 264; continue; }; + } + } +case 263: + { this.tokenType = "javascript-regexp"; return cursor; } +case 264: + yyaccept = 0; + yych = this._charAt(YYMARKER = ++cursor); + { gotoCase = 272; continue; }; +case 265: + ++cursor; +case 266: + { this.tokenType = null; return cursor; } +case 267: + ++cursor; + yych = this._charAt(cursor); + { gotoCase = 278; continue; }; +case 268: + this.setLexCondition(this._lexConditions.NODIV); + { this.tokenType = "javascript-regexp"; return cursor; } +case 269: + yyaccept = 1; + yych = this._charAt(YYMARKER = ++cursor); + if (yych <= '\r') { + if (yych == '\n') { gotoCase = 266; continue; }; + if (yych <= '\f') { gotoCase = 276; continue; }; + { gotoCase = 266; continue; }; + } else { + if (yych <= '*') { + if (yych <= ')') { gotoCase = 276; continue; }; + { gotoCase = 266; continue; }; + } else { + if (yych == '/') { gotoCase = 266; continue; }; + { gotoCase = 276; continue; }; + } + } +case 270: + yych = this._charAt(++cursor); + if (yych == '\n') { gotoCase = 266; continue; }; + if (yych == '\r') { gotoCase = 266; continue; }; +case 271: + yyaccept = 0; + YYMARKER = ++cursor; + yych = this._charAt(cursor); +case 272: + if (yych <= '.') { + if (yych <= '\n') { + if (yych <= '\t') { gotoCase = 271; continue; }; + { gotoCase = 263; continue; }; + } else { + if (yych == '\r') { gotoCase = 263; continue; }; + { gotoCase = 271; continue; }; + } + } else { + if (yych <= '[') { + if (yych <= '/') { gotoCase = 277; continue; }; + if (yych <= 'Z') { gotoCase = 271; continue; }; + { gotoCase = 275; continue; }; + } else { + if (yych <= '\\') { gotoCase = 273; continue; }; + if (yych <= ']') { gotoCase = 263; continue; }; + { gotoCase = 271; continue; }; + } + } +case 273: + ++cursor; + yych = this._charAt(cursor); + if (yych == '\n') { gotoCase = 274; continue; }; + if (yych != '\r') { gotoCase = 271; continue; }; +case 274: + cursor = YYMARKER; + if (yyaccept <= 0) { + { gotoCase = 263; continue; }; + } else { + { gotoCase = 266; continue; }; + } +case 275: + ++cursor; + yych = this._charAt(cursor); +case 276: + if (yych <= '*') { + if (yych <= '\f') { + if (yych == '\n') { gotoCase = 274; continue; }; + { gotoCase = 275; continue; }; + } else { + if (yych <= '\r') { gotoCase = 274; continue; }; + if (yych <= ')') { gotoCase = 275; continue; }; + { gotoCase = 274; continue; }; + } + } else { + if (yych <= '[') { + if (yych == '/') { gotoCase = 274; continue; }; + { gotoCase = 275; continue; }; + } else { + if (yych <= '\\') { gotoCase = 281; continue; }; + if (yych <= ']') { gotoCase = 279; continue; }; + { gotoCase = 275; continue; }; + } + } +case 277: + ++cursor; + yych = this._charAt(cursor); +case 278: + if (yych <= 'h') { + if (yych == 'g') { gotoCase = 277; continue; }; + { gotoCase = 268; continue; }; + } else { + if (yych <= 'i') { gotoCase = 277; continue; }; + if (yych == 'm') { gotoCase = 277; continue; }; + { gotoCase = 268; continue; }; + } +case 279: + yyaccept = 0; + YYMARKER = ++cursor; + yych = this._charAt(cursor); + if (yych <= '*') { + if (yych <= '\f') { + if (yych == '\n') { gotoCase = 263; continue; }; + { gotoCase = 279; continue; }; + } else { + if (yych <= '\r') { gotoCase = 263; continue; }; + if (yych <= ')') { gotoCase = 279; continue; }; + { gotoCase = 271; continue; }; + } + } else { + if (yych <= 'Z') { + if (yych == '/') { gotoCase = 277; continue; }; + { gotoCase = 279; continue; }; + } else { + if (yych <= '[') { gotoCase = 275; continue; }; + if (yych <= '\\') { gotoCase = 282; continue; }; + { gotoCase = 279; continue; }; + } + } +case 281: + ++cursor; + yych = this._charAt(cursor); + if (yych == '\n') { gotoCase = 274; continue; }; + if (yych == '\r') { gotoCase = 274; continue; }; + { gotoCase = 275; continue; }; +case 282: + ++cursor; + yych = this._charAt(cursor); + if (yych == '\n') { gotoCase = 274; continue; }; + if (yych == '\r') { gotoCase = 274; continue; }; + { gotoCase = 279; continue; }; +/* *********************************** */ +case this.case_SSTRING: + yych = this._charAt(cursor); + if (yych <= '\r') { + if (yych == '\n') { gotoCase = 287; continue; }; + if (yych <= '\f') { gotoCase = 286; continue; }; + { gotoCase = 287; continue; }; + } else { + if (yych <= '\'') { + if (yych <= '&') { gotoCase = 286; continue; }; + { gotoCase = 289; continue; }; + } else { + if (yych == '\\') { gotoCase = 291; continue; }; + { gotoCase = 286; continue; }; + } + } +case 285: + { this.tokenType = "javascript-string"; return cursor; } +case 286: + yyaccept = 0; + yych = this._charAt(YYMARKER = ++cursor); + { gotoCase = 293; continue; }; +case 287: + ++cursor; +case 288: + { this.tokenType = null; return cursor; } +case 289: + ++cursor; +case 290: + this.setLexCondition(this._lexConditions.NODIV); + { this.tokenType = "javascript-string"; return cursor; } +case 291: + yyaccept = 1; + yych = this._charAt(YYMARKER = ++cursor); + if (yych <= 'e') { + if (yych <= '\'') { + if (yych == '"') { gotoCase = 292; continue; }; + if (yych <= '&') { gotoCase = 288; continue; }; + } else { + if (yych <= '\\') { + if (yych <= '[') { gotoCase = 288; continue; }; + } else { + if (yych != 'b') { gotoCase = 288; continue; }; + } + } + } else { + if (yych <= 'r') { + if (yych <= 'm') { + if (yych >= 'g') { gotoCase = 288; continue; }; + } else { + if (yych <= 'n') { gotoCase = 292; continue; }; + if (yych <= 'q') { gotoCase = 288; continue; }; + } + } else { + if (yych <= 't') { + if (yych <= 's') { gotoCase = 288; continue; }; + } else { + if (yych <= 'u') { gotoCase = 294; continue; }; + if (yych >= 'w') { gotoCase = 288; continue; }; + } + } + } +case 292: + yyaccept = 0; + YYMARKER = ++cursor; + yych = this._charAt(cursor); +case 293: + if (yych <= '\r') { + if (yych == '\n') { gotoCase = 285; continue; }; + if (yych <= '\f') { gotoCase = 292; continue; }; + { gotoCase = 285; continue; }; + } else { + if (yych <= '\'') { + if (yych <= '&') { gotoCase = 292; continue; }; + { gotoCase = 300; continue; }; + } else { + if (yych == '\\') { gotoCase = 299; continue; }; + { gotoCase = 292; continue; }; + } + } +case 294: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '@') { + if (yych <= '/') { gotoCase = 295; continue; }; + if (yych <= '9') { gotoCase = 296; continue; }; + } else { + if (yych <= 'F') { gotoCase = 296; continue; }; + if (yych <= '`') { gotoCase = 295; continue; }; + if (yych <= 'f') { gotoCase = 296; continue; }; + } +case 295: + cursor = YYMARKER; + if (yyaccept <= 0) { + { gotoCase = 285; continue; }; + } else { + { gotoCase = 288; continue; }; + } +case 296: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '@') { + if (yych <= '/') { gotoCase = 295; continue; }; + if (yych >= ':') { gotoCase = 295; continue; }; + } else { + if (yych <= 'F') { gotoCase = 297; continue; }; + if (yych <= '`') { gotoCase = 295; continue; }; + if (yych >= 'g') { gotoCase = 295; continue; }; + } +case 297: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '@') { + if (yych <= '/') { gotoCase = 295; continue; }; + if (yych >= ':') { gotoCase = 295; continue; }; + } else { + if (yych <= 'F') { gotoCase = 298; continue; }; + if (yych <= '`') { gotoCase = 295; continue; }; + if (yych >= 'g') { gotoCase = 295; continue; }; + } +case 298: + ++cursor; + yych = this._charAt(cursor); + if (yych <= '@') { + if (yych <= '/') { gotoCase = 295; continue; }; + if (yych <= '9') { gotoCase = 292; continue; }; + { gotoCase = 295; continue; }; + } else { + if (yych <= 'F') { gotoCase = 292; continue; }; + if (yych <= '`') { gotoCase = 295; continue; }; + if (yych <= 'f') { gotoCase = 292; continue; }; + { gotoCase = 295; continue; }; + } +case 299: + ++cursor; + yych = this._charAt(cursor); + if (yych <= 'e') { + if (yych <= '\'') { + if (yych == '"') { gotoCase = 292; continue; }; + if (yych <= '&') { gotoCase = 295; continue; }; + { gotoCase = 292; continue; }; + } else { + if (yych <= '\\') { + if (yych <= '[') { gotoCase = 295; continue; }; + { gotoCase = 292; continue; }; + } else { + if (yych == 'b') { gotoCase = 292; continue; }; + { gotoCase = 295; continue; }; + } + } + } else { + if (yych <= 'r') { + if (yych <= 'm') { + if (yych <= 'f') { gotoCase = 292; continue; }; + { gotoCase = 295; continue; }; + } else { + if (yych <= 'n') { gotoCase = 292; continue; }; + if (yych <= 'q') { gotoCase = 295; continue; }; + { gotoCase = 292; continue; }; + } + } else { + if (yych <= 't') { + if (yych <= 's') { gotoCase = 295; continue; }; + { gotoCase = 292; continue; }; + } else { + if (yych <= 'u') { gotoCase = 294; continue; }; + if (yych <= 'v') { gotoCase = 292; continue; }; + { gotoCase = 295; continue; }; + } + } + } +case 300: + ++cursor; + yych = this._charAt(cursor); + { gotoCase = 290; continue; }; + } + + } + } +} + +WebInspector.SourceJavaScriptTokenizer.prototype.__proto__ = WebInspector.SourceTokenizer.prototype; +/* FontView.js */ + +/* + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @extends {WebInspector.ResourceView} + * @constructor + */ +WebInspector.FontView = function(resource) +{ + WebInspector.ResourceView.call(this, resource); + + this.element.addStyleClass("font"); +} + +WebInspector.FontView._fontPreviewLines = [ "ABCDEFGHIJKLM", "NOPQRSTUVWXYZ", "abcdefghijklm", "nopqrstuvwxyz", "1234567890" ]; + +WebInspector.FontView._fontId = 0; + +WebInspector.FontView._measureFontSize = 50; + +WebInspector.FontView.prototype = { + hasContent: function() + { + return true; + }, + + _createContentIfNeeded: function() + { + if (this.fontPreviewElement) + return; + + var uniqueFontName = "WebInspectorFontPreview" + (++WebInspector.FontView._fontId); + + this.fontStyleElement = document.createElement("style"); + this.fontStyleElement.textContent = "@font-face { font-family: \"" + uniqueFontName + "\"; src: url(" + this.resource.url + "); }"; + document.head.appendChild(this.fontStyleElement); + + var fontPreview = document.createElement("div"); + for (var i = 0; i < WebInspector.FontView._fontPreviewLines.length; ++i) { + if (i > 0) + fontPreview.appendChild(document.createElement("br")); + fontPreview.appendChild(document.createTextNode(WebInspector.FontView._fontPreviewLines[i])); + } + this.fontPreviewElement = fontPreview.cloneNode(true); + this.fontPreviewElement.style.setProperty("font-family", uniqueFontName); + this.fontPreviewElement.style.setProperty("visibility", "hidden"); + + this._dummyElement = fontPreview; + this._dummyElement.style.visibility = "hidden"; + this._dummyElement.style.zIndex = "-1"; + this._dummyElement.style.display = "inline"; + this._dummyElement.style.position = "absolute"; + this._dummyElement.style.setProperty("font-family", uniqueFontName); + this._dummyElement.style.setProperty("font-size", WebInspector.FontView._measureFontSize + "px"); + + this.element.appendChild(this.fontPreviewElement); + }, + + wasShown: function() + { + this._createContentIfNeeded(); + + this.updateFontPreviewSize(); + }, + + onResize: function() + { + if (this._inResize) + return; + + this._inResize = true; + try { + this.updateFontPreviewSize(); + } finally { + delete this._inResize; + } + }, + + _measureElement: function() + { + this.element.appendChild(this._dummyElement); + var result = { width: this._dummyElement.offsetWidth, height: this._dummyElement.offsetHeight }; + this.element.removeChild(this._dummyElement); + + return result; + }, + + updateFontPreviewSize: function() + { + if (!this.fontPreviewElement || !this.isShowing()) + return; + + this.fontPreviewElement.style.removeProperty("visibility"); + var dimension = this._measureElement(); + + const height = dimension.height; + const width = dimension.width; + + // Subtract some padding. This should match the paddings in the CSS plus room for the scrollbar. + const containerWidth = this.element.offsetWidth - 50; + const containerHeight = this.element.offsetHeight - 30; + + if (!height || !width || !containerWidth || !containerHeight) { + this.fontPreviewElement.style.removeProperty("font-size"); + return; + } + + var widthRatio = containerWidth / width; + var heightRatio = containerHeight / height; + var finalFontSize = Math.floor(WebInspector.FontView._measureFontSize * Math.min(widthRatio, heightRatio)) - 2; + + this.fontPreviewElement.style.setProperty("font-size", finalFontSize + "px", null); + } +} + +WebInspector.FontView.prototype.__proto__ = WebInspector.ResourceView.prototype; +/* ImageView.js */ + +/* + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @extends {WebInspector.ResourceView} + * @constructor + */ +WebInspector.ImageView = function(resource) +{ + WebInspector.ResourceView.call(this, resource); + + this.element.addStyleClass("image"); +} + +WebInspector.ImageView.prototype = { + hasContent: function() + { + return true; + }, + + wasShown: function() + { + this._createContentIfNeeded(); + }, + + _createContentIfNeeded: function() + { + if (this._container) + return; + + var imageContainer = document.createElement("div"); + imageContainer.className = "image"; + this.element.appendChild(imageContainer); + + var imagePreviewElement = document.createElement("img"); + imagePreviewElement.addStyleClass("resource-image-view"); + imageContainer.appendChild(imagePreviewElement); + imagePreviewElement.addEventListener("contextmenu", this._contextMenu.bind(this), true); + + this._container = document.createElement("div"); + this._container.className = "info"; + this.element.appendChild(this._container); + + var imageNameElement = document.createElement("h1"); + imageNameElement.className = "title"; + imageNameElement.textContent = this.resource.displayName; + this._container.appendChild(imageNameElement); + + var infoListElement = document.createElement("dl"); + infoListElement.className = "infoList"; + + this.resource.populateImageSource(imagePreviewElement); + + function onImageLoad() + { + var content = this.resource.content; + if (content) + var resourceSize = this._base64ToSize(content); + else + var resourceSize = this.resource.resourceSize; + + var imageProperties = [ + { name: WebInspector.UIString("Dimensions"), value: WebInspector.UIString("%d × %d", imagePreviewElement.naturalWidth, imagePreviewElement.naturalHeight) }, + { name: WebInspector.UIString("File size"), value: Number.bytesToString(resourceSize) }, + { name: WebInspector.UIString("MIME type"), value: this.resource.mimeType } + ]; + + infoListElement.removeChildren(); + for (var i = 0; i < imageProperties.length; ++i) { + var dt = document.createElement("dt"); + dt.textContent = imageProperties[i].name; + infoListElement.appendChild(dt); + var dd = document.createElement("dd"); + dd.textContent = imageProperties[i].value; + infoListElement.appendChild(dd); + } + var dt = document.createElement("dt"); + dt.textContent = WebInspector.UIString("URL"); + infoListElement.appendChild(dt); + var dd = document.createElement("dd"); + var externalResource = true; + dd.appendChild(WebInspector.linkifyURLAsNode(this.resource.url, undefined, undefined, externalResource)); + infoListElement.appendChild(dd); + + this._container.appendChild(infoListElement); + } + imagePreviewElement.addEventListener("load", onImageLoad.bind(this), false); + }, + + _base64ToSize: function(content) + { + if (!content.length) + return 0; + var size = (content.length || 0) * 3 / 4; + if (content.length > 0 && content[content.length - 1] === "=") + size--; + if (content.length > 1 && content[content.length - 2] === "=") + size--; + return size; + }, + + _contextMenu: function(event) + { + var contextMenu = new WebInspector.ContextMenu(); + contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy image URL" : "Copy Image URL"), this._copyImageURL.bind(this)); + contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Open image in new tab" : "Open Image in New Tab"), this._openInNewTab.bind(this)); + contextMenu.show(event); + }, + + _copyImageURL: function(event) + { + InspectorFrontendHost.copyText(this.resource.url); + }, + + _openInNewTab: function(event) + { + InspectorFrontendHost.openInNewTab(this.resource.url); + } +} + +WebInspector.ImageView.prototype.__proto__ = WebInspector.ResourceView.prototype; +/* DatabaseTableView.js */ + +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.View} + */ +WebInspector.DatabaseTableView = function(database, tableName) +{ + WebInspector.View.call(this); + + this.database = database; + this.tableName = tableName; + + this.element.addStyleClass("storage-view"); + this.element.addStyleClass("table"); + + this.refreshButton = new WebInspector.StatusBarButton(WebInspector.UIString("Refresh"), "refresh-storage-status-bar-item"); + this.refreshButton.addEventListener("click", this._refreshButtonClicked, this); +} + +WebInspector.DatabaseTableView.prototype = { + wasShown: function() + { + this.update(); + }, + + get statusBarItems() + { + return [this.refreshButton.element]; + }, + + /** + * @param {string} tableName + * @return {string} + */ + _escapeTableName: function(tableName) + { + return tableName.replace(/\"/g, "\"\""); + }, + + update: function() + { + this.database.executeSql("SELECT * FROM \"" + this._escapeTableName(this.tableName) + "\"", this._queryFinished.bind(this), this._queryError.bind(this)); + }, + + _queryFinished: function(columnNames, values) + { + this.detachChildViews(); + this.element.removeChildren(); + + var dataGrid = WebInspector.DataGrid.createSortableDataGrid(columnNames, values); + if (!dataGrid) { + this._emptyView = new WebInspector.EmptyView(WebInspector.UIString("The “%sâ€\ntable is empty.", this.tableName)); + this._emptyView.show(this.element); + return; + } + dataGrid.show(this.element); + dataGrid.autoSizeColumns(5); + }, + + _queryError: function(error) + { + this.detachChildViews(); + this.element.removeChildren(); + + var errorMsgElement = document.createElement("div"); + errorMsgElement.className = "storage-table-error"; + errorMsgElement.textContent = WebInspector.UIString("An error occurred trying to\nread the “%s†table.", this.tableName); + this.element.appendChild(errorMsgElement); + }, + + _refreshButtonClicked: function(event) + { + this.update(); + } +} + +WebInspector.DatabaseTableView.prototype.__proto__ = WebInspector.View.prototype; +/* DatabaseQueryView.js */ + +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.View} + */ +WebInspector.DatabaseQueryView = function(database) +{ + WebInspector.View.call(this); + + this.database = database; + + this.element.addStyleClass("storage-view"); + this.element.addStyleClass("query"); + this.element.addStyleClass("monospace"); + this.element.addEventListener("selectstart", this._selectStart.bind(this), false); + + this._promptElement = document.createElement("div"); + this._promptElement.className = "database-query-prompt"; + this._promptElement.appendChild(document.createElement("br")); + this._promptElement.addEventListener("keydown", this._promptKeyDown.bind(this), true); + this.element.appendChild(this._promptElement); + + this.prompt = new WebInspector.TextPromptWithHistory(this.completions.bind(this), " "); + this.prompt.attach(this._promptElement); + + this.element.addEventListener("click", this._messagesClicked.bind(this), true); +} + +WebInspector.DatabaseQueryView.Events = { + SchemaUpdated: "SchemaUpdated" +} + +WebInspector.DatabaseQueryView.prototype = { + _messagesClicked: function() + { + if (!this.prompt.isCaretInsidePrompt() && window.getSelection().isCollapsed) + this.prompt.moveCaretToEndOfPrompt(); + }, + + completions: function(wordRange, force, completionsReadyCallback) + { + var prefix = wordRange.toString().toLowerCase(); + if (!prefix.length && !force) + return; + + var results = []; + + function accumulateMatches(textArray) + { + for (var i = 0; i < textArray.length; ++i) { + var text = textArray[i].toLowerCase(); + if (text.length < prefix.length) + continue; + if (text.indexOf(prefix) !== 0) + continue; + results.push(textArray[i]); + } + } + + function tableNamesCallback(tableNames) + { + accumulateMatches(tableNames.map(function(name) { return name + " " })); + accumulateMatches(["SELECT ", "FROM ", "WHERE ", "LIMIT ", "DELETE FROM ", "CREATE ", "DROP ", "TABLE ", "INDEX ", "UPDATE ", "INSERT INTO ", "VALUES ("]); + + completionsReadyCallback(results); + } + this.database.getTableNames(tableNamesCallback); + }, + + _selectStart: function(event) + { + if (this._selectionTimeout) + clearTimeout(this._selectionTimeout); + + this.prompt.clearAutoComplete(); + + function moveBackIfOutside() + { + delete this._selectionTimeout; + if (!this.prompt.isCaretInsidePrompt() && window.getSelection().isCollapsed) + this.prompt.moveCaretToEndOfPrompt(); + this.prompt.autoCompleteSoon(); + } + + this._selectionTimeout = setTimeout(moveBackIfOutside.bind(this), 100); + }, + + _promptKeyDown: function(event) + { + if (isEnterKey(event)) { + this._enterKeyPressed(event); + return; + } + }, + + _enterKeyPressed: function(event) + { + event.preventDefault(); + event.stopPropagation(); + + this.prompt.clearAutoComplete(true); + + var query = this.prompt.text; + if (!query.length) + return; + + this.prompt.pushHistoryItem(query); + this.prompt.text = ""; + + this.database.executeSql(query, this._queryFinished.bind(this, query), this._queryError.bind(this, query)); + }, + + _queryFinished: function(query, columnNames, values) + { + var dataGrid = WebInspector.DataGrid.createSortableDataGrid(columnNames, values); + var trimmedQuery = query.trim(); + + if (dataGrid) { + dataGrid.element.addStyleClass("inline"); + this._appendViewQueryResult(trimmedQuery, dataGrid); + dataGrid.autoSizeColumns(5); + } + + if (trimmedQuery.match(/^create /i) || trimmedQuery.match(/^drop table /i)) + this.dispatchEventToListeners(WebInspector.DatabaseQueryView.Events.SchemaUpdated, this.database); + }, + + _queryError: function(query, error) + { + if (error.message) + var message = error.message; + else if (error.code == 2) + var message = WebInspector.UIString("Database no longer has expected version."); + else + var message = WebInspector.UIString("An unexpected error %s occurred.", error.code); + + this._appendErrorQueryResult(query, message); + }, + + /** + * @param {string} query + * @param {WebInspector.View} view + */ + _appendViewQueryResult: function(query, view) + { + var resultElement = this._appendQueryResult(query); + view.show(resultElement); + + this._promptElement.scrollIntoView(false); + }, + + /** + * @param {string} query + * @param {string} errorText + */ + _appendErrorQueryResult: function(query, errorText) + { + var resultElement = this._appendQueryResult(query); + resultElement.addStyleClass("error") + resultElement.textContent = errorText; + + this._promptElement.scrollIntoView(false); + }, + + _appendQueryResult: function(query) + { + var element = document.createElement("div"); + element.className = "database-user-query"; + this.element.insertBefore(element, this.prompt.proxyElement); + + var commandTextElement = document.createElement("span"); + commandTextElement.className = "database-query-text"; + commandTextElement.textContent = query; + element.appendChild(commandTextElement); + + var resultElement = document.createElement("div"); + resultElement.className = "database-query-result"; + element.appendChild(resultElement); + return resultElement; + } +} + +WebInspector.DatabaseQueryView.prototype.__proto__ = WebInspector.View.prototype; +/* ProfileLauncherView.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.View} + */ +WebInspector.ProfileLauncherView = function(profilesPanel) +{ + WebInspector.View.call(this); + + this._panel = profilesPanel; + this._profileRunning = false; + this._optionIdPrefix = "profile-type-"; + + this.element.addStyleClass("profile-launcher-view"); + this.element.addStyleClass("panel-enabler-view"); + + this._contentElement = document.createElement("div"); + this._contentElement.className = "profile-launcher-view-content"; + this.element.appendChild(this._contentElement); + + var header = this._contentElement.createChild("h1"); + header.textContent = WebInspector.UIString("Select profiling type"); + + this._profileTypeSelectorForm = this._contentElement.createChild("form"); + this._contentElement.createChild("div", "flexible-space"); + + this._boundProfileTypeChangeListener = this._profileTypeChanged.bind(this); + + this._controlButton = this._contentElement.createChild("button", "control-profiling"); + this._controlButton.addEventListener("click", this._controlButtonClicked.bind(this), false); + this._updateControls(); +} + +WebInspector.ProfileLauncherView.EventTypes = { + ProfileTypeSelected: "profile-type-selected" +} + +WebInspector.ProfileLauncherView.prototype = { + setUpEventListeners: function() + { + this._panel.addEventListener(WebInspector.ProfilesPanel.EventTypes.ProfileStarted, this._onProfileStarted, this); + this._panel.addEventListener(WebInspector.ProfilesPanel.EventTypes.ProfileFinished, this._onProfileFinished, this); + }, + + addProfileType: function(profileType) + { + var checked = !this._profileTypeSelectorForm.children.length; + var labelElement = this._profileTypeSelectorForm.createChild("label"); + labelElement.textContent = profileType.name; + var optionElement = document.createElement("input"); + labelElement.insertBefore(optionElement, labelElement.firstChild); + optionElement.type = "radio"; + optionElement.name = "profile-type"; + var optionId = this._optionIdPrefix + profileType.id; + optionElement.id = optionId; + if (checked) { + optionElement.checked = checked; + this.dispatchEventToListeners(WebInspector.ProfileLauncherView.EventTypes.ProfileTypeSelected, profileType); + } + optionElement.addEventListener("change", this._boundProfileTypeChangeListener, false); + var descriptionElement = labelElement.createChild("p"); + descriptionElement.textContent = profileType.description; + }, + + _controlButtonClicked: function() + { + this._panel.toggleRecordButton(); + }, + + _updateControls: function() + { + if (this._isProfiling) { + this._profileTypeSelectorForm.disabled = true; + this._controlButton.addStyleClass("running"); + this._controlButton.textContent = WebInspector.UIString("Stop"); + } else { + this._profileTypeSelectorForm.disabled = false; + this._controlButton.removeStyleClass("running"); + this._controlButton.textContent = WebInspector.UIString("Start"); + } + }, + + _profileTypeChanged: function(event) + { + var selectedProfileType = this._panel.getProfileType(event.target.id.substring(this._optionIdPrefix.length)); + this.dispatchEventToListeners(WebInspector.ProfileLauncherView.EventTypes.ProfileTypeSelected, selectedProfileType); + }, + + _onProfileStarted: function(event) + { + this._isProfiling = true; + this._updateControls(); + }, + + _onProfileFinished: function(event) + { + this._isProfiling = false; + this._updateControls(); + } +} + +WebInspector.ProfileLauncherView.prototype.__proto__ = WebInspector.View.prototype; +/* ProfileDataGridTree.js */ + +/* + * Copyright (C) 2009 280 North Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.ProfileDataGridNode = function(profileView, profileNode, owningTree, hasChildren) +{ + this.profileView = profileView; + this.profileNode = profileNode; + + WebInspector.DataGridNode.call(this, null, hasChildren); + + this.addEventListener("populate", this._populate, this); + + this.tree = owningTree; + + this.childrenByCallUID = {}; + this.lastComparator = null; + + this.callUID = profileNode.callUID; + this.selfTime = profileNode.selfTime; + this.totalTime = profileNode.totalTime; + this.functionName = profileNode.functionName; + this.numberOfCalls = profileNode.numberOfCalls; + this.url = profileNode.url; +} + +WebInspector.ProfileDataGridNode.prototype = { + get data() + { + function formatMilliseconds(time) + { + return Number.secondsToString(time / 1000, !Capabilities.samplingCPUProfiler); + } + + var data = {}; + + data["function"] = this.functionName; + data["calls"] = this.numberOfCalls; + + if (this.profileView.showSelfTimeAsPercent.get()) + data["self"] = WebInspector.UIString("%.2f%%", this.selfPercent); + else + data["self"] = formatMilliseconds(this.selfTime); + + if (this.profileView.showTotalTimeAsPercent.get()) + data["total"] = WebInspector.UIString("%.2f%%", this.totalPercent); + else + data["total"] = formatMilliseconds(this.totalTime); + + if (this.profileView.showAverageTimeAsPercent.get()) + data["average"] = WebInspector.UIString("%.2f%%", this.averagePercent); + else + data["average"] = formatMilliseconds(this.averageTime); + + return data; + }, + + createCell: function(columnIdentifier) + { + var cell = WebInspector.DataGridNode.prototype.createCell.call(this, columnIdentifier); + + if (columnIdentifier === "self" && this._searchMatchedSelfColumn) + cell.addStyleClass("highlight"); + else if (columnIdentifier === "total" && this._searchMatchedTotalColumn) + cell.addStyleClass("highlight"); + else if (columnIdentifier === "average" && this._searchMatchedAverageColumn) + cell.addStyleClass("highlight"); + else if (columnIdentifier === "calls" && this._searchMatchedCallsColumn) + cell.addStyleClass("highlight"); + + if (columnIdentifier !== "function") + return cell; + + if (this.profileNode._searchMatchedFunctionColumn) + cell.addStyleClass("highlight"); + + if (this.profileNode.url) { + // FIXME(62725): profileNode should reference a debugger location. + var lineNumber = this.profileNode.lineNumber ? this.profileNode.lineNumber - 1 : 0; + var urlElement = this.profileView._linkifier.linkifyLocation(this.profileNode.url, lineNumber, 0, "profile-node-file"); + urlElement.style.maxWidth = "75%"; + cell.insertBefore(urlElement, cell.firstChild); + } + + return cell; + }, + + select: function(supressSelectedEvent) + { + WebInspector.DataGridNode.prototype.select.call(this, supressSelectedEvent); + this.profileView._dataGridNodeSelected(this); + }, + + deselect: function(supressDeselectedEvent) + { + WebInspector.DataGridNode.prototype.deselect.call(this, supressDeselectedEvent); + this.profileView._dataGridNodeDeselected(this); + }, + + sort: function(/*Function*/ comparator, /*Boolean*/ force) + { + var gridNodeGroups = [[this]]; + + for (var gridNodeGroupIndex = 0; gridNodeGroupIndex < gridNodeGroups.length; ++gridNodeGroupIndex) { + var gridNodes = gridNodeGroups[gridNodeGroupIndex]; + var count = gridNodes.length; + + for (var index = 0; index < count; ++index) { + var gridNode = gridNodes[index]; + + // If the grid node is collapsed, then don't sort children (save operation for later). + // If the grid node has the same sorting as previously, then there is no point in sorting it again. + if (!force && (!gridNode.expanded || gridNode.lastComparator === comparator)) { + if (gridNode.children.length) + gridNode.shouldRefreshChildren = true; + continue; + } + + gridNode.lastComparator = comparator; + + var children = gridNode.children; + var childCount = children.length; + + if (childCount) { + children.sort(comparator); + + for (var childIndex = 0; childIndex < childCount; ++childIndex) + children[childIndex]._recalculateSiblings(childIndex); + + gridNodeGroups.push(children); + } + } + } + }, + + insertChild: function(/*ProfileDataGridNode*/ profileDataGridNode, index) + { + WebInspector.DataGridNode.prototype.insertChild.call(this, profileDataGridNode, index); + + this.childrenByCallUID[profileDataGridNode.callUID] = profileDataGridNode; + }, + + removeChild: function(/*ProfileDataGridNode*/ profileDataGridNode) + { + WebInspector.DataGridNode.prototype.removeChild.call(this, profileDataGridNode); + + delete this.childrenByCallUID[profileDataGridNode.callUID]; + }, + + removeChildren: function(/*ProfileDataGridNode*/ profileDataGridNode) + { + WebInspector.DataGridNode.prototype.removeChildren.call(this); + + this.childrenByCallUID = {}; + }, + + findChild: function(/*Node*/ node) + { + if (!node) + return null; + return this.childrenByCallUID[node.callUID]; + }, + + get averageTime() + { + return this.selfTime / Math.max(1, this.numberOfCalls); + }, + + get averagePercent() + { + return this.averageTime / this.tree.totalTime * 100.0; + }, + + get selfPercent() + { + return this.selfTime / this.tree.totalTime * 100.0; + }, + + get totalPercent() + { + return this.totalTime / this.tree.totalTime * 100.0; + }, + + get _parent() + { + return this.parent !== this.dataGrid ? this.parent : this.tree; + }, + + _populate: function(event) + { + this._sharedPopulate(); + + if (this._parent) { + var currentComparator = this._parent.lastComparator; + + if (currentComparator) + this.sort(currentComparator, true); + } + + if (this.removeEventListener) + this.removeEventListener("populate", this._populate, this); + }, + + // When focusing and collapsing we modify lots of nodes in the tree. + // This allows us to restore them all to their original state when we revert. + _save: function() + { + if (this._savedChildren) + return; + + this._savedSelfTime = this.selfTime; + this._savedTotalTime = this.totalTime; + this._savedNumberOfCalls = this.numberOfCalls; + + this._savedChildren = this.children.slice(); + }, + + // When focusing and collapsing we modify lots of nodes in the tree. + // This allows us to restore them all to their original state when we revert. + _restore: function() + { + if (!this._savedChildren) + return; + + this.selfTime = this._savedSelfTime; + this.totalTime = this._savedTotalTime; + this.numberOfCalls = this._savedNumberOfCalls; + + this.removeChildren(); + + var children = this._savedChildren; + var count = children.length; + + for (var index = 0; index < count; ++index) { + children[index]._restore(); + this.appendChild(children[index]); + } + }, + + _merge: function(child, shouldAbsorb) + { + this.selfTime += child.selfTime; + + if (!shouldAbsorb) { + this.totalTime += child.totalTime; + this.numberOfCalls += child.numberOfCalls; + } + + var children = this.children.slice(); + + this.removeChildren(); + + var count = children.length; + + for (var index = 0; index < count; ++index) { + if (!shouldAbsorb || children[index] !== child) + this.appendChild(children[index]); + } + + children = child.children.slice(); + count = children.length; + + for (var index = 0; index < count; ++index) { + var orphanedChild = children[index], + existingChild = this.childrenByCallUID[orphanedChild.callUID]; + + if (existingChild) + existingChild._merge(orphanedChild, false); + else + this.appendChild(orphanedChild); + } + } +} + +WebInspector.ProfileDataGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype; + +WebInspector.ProfileDataGridTree = function(profileView, profileNode) +{ + this.tree = this; + this.children = []; + + this.profileView = profileView; + + this.totalTime = profileNode.totalTime; + this.lastComparator = null; + + this.childrenByCallUID = {}; +} + +WebInspector.ProfileDataGridTree.prototype = { + get expanded() + { + return true; + }, + + appendChild: function(child) + { + this.insertChild(child, this.children.length); + }, + + insertChild: function(child, index) + { + this.children.splice(index, 0, child); + this.childrenByCallUID[child.callUID] = child; + }, + + removeChildren: function() + { + this.children = []; + this.childrenByCallUID = {}; + }, + + findChild: WebInspector.ProfileDataGridNode.prototype.findChild, + sort: WebInspector.ProfileDataGridNode.prototype.sort, + + _save: function() + { + if (this._savedChildren) + return; + + this._savedTotalTime = this.totalTime; + this._savedChildren = this.children.slice(); + }, + + restore: function() + { + if (!this._savedChildren) + return; + + this.children = this._savedChildren; + this.totalTime = this._savedTotalTime; + + var children = this.children; + var count = children.length; + + for (var index = 0; index < count; ++index) + children[index]._restore(); + + this._savedChildren = null; + } +} + +WebInspector.ProfileDataGridTree.propertyComparators = [{}, {}]; + +WebInspector.ProfileDataGridTree.propertyComparator = function(/*String*/ property, /*Boolean*/ isAscending) +{ + var comparator = this.propertyComparators[(isAscending ? 1 : 0)][property]; + + if (!comparator) { + if (isAscending) { + comparator = function(lhs, rhs) + { + if (lhs[property] < rhs[property]) + return -1; + + if (lhs[property] > rhs[property]) + return 1; + + return 0; + } + } else { + comparator = function(lhs, rhs) + { + if (lhs[property] > rhs[property]) + return -1; + + if (lhs[property] < rhs[property]) + return 1; + + return 0; + } + } + + this.propertyComparators[(isAscending ? 1 : 0)][property] = comparator; + } + + return comparator; +} +/* BottomUpProfileDataGridTree.js */ + +/* + * Copyright (C) 2009 280 North Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// Bottom Up Profiling shows the entire callstack backwards: +// The root node is a representation of each individual function called, and each child of that node represents +// a reverse-callstack showing how many of those calls came from it. So, unlike top-down, the statistics in +// each child still represent the root node. We have to be particularly careful of recursion with this mode +// because a root node can represent itself AND an ancestor. + +WebInspector.BottomUpProfileDataGridNode = function(/*ProfileView*/ profileView, /*ProfileNode*/ profileNode, /*BottomUpProfileDataGridTree*/ owningTree) +{ + WebInspector.ProfileDataGridNode.call(this, profileView, profileNode, owningTree, this._willHaveChildren(profileNode)); + + this._remainingNodeInfos = []; +} + +WebInspector.BottomUpProfileDataGridNode.prototype = { + _takePropertiesFromProfileDataGridNode: function(/*ProfileDataGridNode*/ profileDataGridNode) + { + this._save(); + + this.selfTime = profileDataGridNode.selfTime; + this.totalTime = profileDataGridNode.totalTime; + this.numberOfCalls = profileDataGridNode.numberOfCalls; + }, + + // When focusing, we keep just the members of the callstack. + _keepOnlyChild: function(/*ProfileDataGridNode*/ child) + { + this._save(); + + this.removeChildren(); + this.appendChild(child); + }, + + _exclude: function(aCallUID) + { + if (this._remainingNodeInfos) + this._populate(); + + this._save(); + + var children = this.children; + var index = this.children.length; + + while (index--) + children[index]._exclude(aCallUID); + + var child = this.childrenByCallUID[aCallUID]; + + if (child) + this._merge(child, true); + }, + + _restore: function() + { + WebInspector.ProfileDataGridNode.prototype._restore(); + + if (!this.children.length) + this.hasChildren = this._willHaveChildren(); + }, + + _merge: function(/*ProfileDataGridNode*/ child, /*Boolean*/ shouldAbsorb) + { + this.selfTime -= child.selfTime; + + WebInspector.ProfileDataGridNode.prototype._merge.call(this, child, shouldAbsorb); + }, + + _sharedPopulate: function() + { + var remainingNodeInfos = this._remainingNodeInfos; + var count = remainingNodeInfos.length; + + for (var index = 0; index < count; ++index) { + var nodeInfo = remainingNodeInfos[index]; + var ancestor = nodeInfo.ancestor; + var focusNode = nodeInfo.focusNode; + var child = this.findChild(ancestor); + + // If we already have this child, then merge the data together. + if (child) { + var totalTimeAccountedFor = nodeInfo.totalTimeAccountedFor; + + child.selfTime += focusNode.selfTime; + child.numberOfCalls += focusNode.numberOfCalls; + + if (!totalTimeAccountedFor) + child.totalTime += focusNode.totalTime; + } else { + // If not, add it as a true ancestor. + // In heavy mode, we take our visual identity from ancestor node... + var child = new WebInspector.BottomUpProfileDataGridNode(this.profileView, ancestor, this.tree); + + if (ancestor !== focusNode) { + // but the actual statistics from the "root" node (bottom of the callstack). + child.selfTime = focusNode.selfTime; + child.totalTime = focusNode.totalTime; + child.numberOfCalls = focusNode.numberOfCalls; + } + + this.appendChild(child); + } + + var parent = ancestor.parent; + if (parent && parent.parent) { + nodeInfo.ancestor = parent; + child._remainingNodeInfos.push(nodeInfo); + } + } + + delete this._remainingNodeInfos; + }, + + _willHaveChildren: function(profileNode) + { + profileNode = profileNode || this.profileNode; + // In bottom up mode, our parents are our children since we display an inverted tree. + // However, we don't want to show the very top parent since it is redundant. + return !!(profileNode.parent && profileNode.parent.parent); + } +} + +WebInspector.BottomUpProfileDataGridNode.prototype.__proto__ = WebInspector.ProfileDataGridNode.prototype; + +WebInspector.BottomUpProfileDataGridTree = function(/*ProfileView*/ aProfileView, /*ProfileNode*/ aProfileNode) +{ + WebInspector.ProfileDataGridTree.call(this, aProfileView, aProfileNode); + + // Iterate each node in pre-order. + var profileNodeUIDs = 0; + var profileNodeGroups = [[], [aProfileNode]]; + var visitedProfileNodesForCallUID = {}; + + this._remainingNodeInfos = []; + + for (var profileNodeGroupIndex = 0; profileNodeGroupIndex < profileNodeGroups.length; ++profileNodeGroupIndex) { + var parentProfileNodes = profileNodeGroups[profileNodeGroupIndex]; + var profileNodes = profileNodeGroups[++profileNodeGroupIndex]; + var count = profileNodes.length; + + for (var index = 0; index < count; ++index) { + var profileNode = profileNodes[index]; + + if (!profileNode.UID) + profileNode.UID = ++profileNodeUIDs; + + if (profileNode.head && profileNode !== profileNode.head) { + // The total time of this ancestor is accounted for if we're in any form of recursive cycle. + var visitedNodes = visitedProfileNodesForCallUID[profileNode.callUID]; + var totalTimeAccountedFor = false; + + if (!visitedNodes) { + visitedNodes = {} + visitedProfileNodesForCallUID[profileNode.callUID] = visitedNodes; + } else { + // The total time for this node has already been accounted for iff one of it's parents has already been visited. + // We can do this check in this style because we are traversing the tree in pre-order. + var parentCount = parentProfileNodes.length; + for (var parentIndex = 0; parentIndex < parentCount; ++parentIndex) { + if (visitedNodes[parentProfileNodes[parentIndex].UID]) { + totalTimeAccountedFor = true; + break; + } + } + } + + visitedNodes[profileNode.UID] = true; + + this._remainingNodeInfos.push({ ancestor:profileNode, focusNode:profileNode, totalTimeAccountedFor:totalTimeAccountedFor }); + } + + var children = profileNode.children; + if (children.length) { + profileNodeGroups.push(parentProfileNodes.concat([profileNode])) + profileNodeGroups.push(children); + } + } + } + + // Populate the top level nodes. + WebInspector.BottomUpProfileDataGridNode.prototype._populate.call(this); + + return this; +} + +WebInspector.BottomUpProfileDataGridTree.prototype = { + // When focusing, we keep the entire callstack up to this ancestor. + focus: function(/*ProfileDataGridNode*/ profileDataGridNode) + { + if (!profileDataGridNode) + return; + + this._save(); + + var currentNode = profileDataGridNode; + var focusNode = profileDataGridNode; + + while (currentNode.parent && (currentNode instanceof WebInspector.ProfileDataGridNode)) { + currentNode._takePropertiesFromProfileDataGridNode(profileDataGridNode); + + focusNode = currentNode; + currentNode = currentNode.parent; + + if (currentNode instanceof WebInspector.ProfileDataGridNode) + currentNode._keepOnlyChild(focusNode); + } + + this.children = [focusNode]; + this.totalTime = profileDataGridNode.totalTime; + }, + + exclude: function(/*ProfileDataGridNode*/ profileDataGridNode) + { + if (!profileDataGridNode) + return; + + this._save(); + + var excludedCallUID = profileDataGridNode.callUID; + var excludedTopLevelChild = this.childrenByCallUID[excludedCallUID]; + + // If we have a top level node that is excluded, get rid of it completely (not keeping children), + // since bottom up data relies entirely on the root node. + if (excludedTopLevelChild) + this.children.remove(excludedTopLevelChild); + + var children = this.children; + var count = children.length; + + for (var index = 0; index < count; ++index) + children[index]._exclude(excludedCallUID); + + if (this.lastComparator) + this.sort(this.lastComparator, true); + }, + + _sharedPopulate: WebInspector.BottomUpProfileDataGridNode.prototype._sharedPopulate +} + +WebInspector.BottomUpProfileDataGridTree.prototype.__proto__ = WebInspector.ProfileDataGridTree.prototype; + +/* TopDownProfileDataGridTree.js */ + +/* + * Copyright (C) 2009 280 North Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.TopDownProfileDataGridNode = function(/*ProfileView*/ profileView, /*ProfileNode*/ profileNode, /*TopDownProfileDataGridTree*/ owningTree) +{ + var hasChildren = (profileNode.children && profileNode.children.length); + + WebInspector.ProfileDataGridNode.call(this, profileView, profileNode, owningTree, hasChildren); + + this._remainingChildren = profileNode.children; +} + +WebInspector.TopDownProfileDataGridNode.prototype = { + _sharedPopulate: function() + { + var children = this._remainingChildren; + var childrenLength = children.length; + + for (var i = 0; i < childrenLength; ++i) + this.appendChild(new WebInspector.TopDownProfileDataGridNode(this.profileView, children[i], this.tree)); + + this._remainingChildren = null; + }, + + _exclude: function(aCallUID) + { + if (this._remainingChildren) + this._populate(); + + this._save(); + + var children = this.children; + var index = this.children.length; + + while (index--) + children[index]._exclude(aCallUID); + + var child = this.childrenByCallUID[aCallUID]; + + if (child) + this._merge(child, true); + } +} + +WebInspector.TopDownProfileDataGridNode.prototype.__proto__ = WebInspector.ProfileDataGridNode.prototype; + +WebInspector.TopDownProfileDataGridTree = function(/*ProfileView*/ profileView, /*ProfileNode*/ profileNode) +{ + WebInspector.ProfileDataGridTree.call(this, profileView, profileNode); + + this._remainingChildren = profileNode.children; + + WebInspector.TopDownProfileDataGridNode.prototype._populate.call(this); +} + +WebInspector.TopDownProfileDataGridTree.prototype = { + focus: function(/*ProfileDataGridNode*/ profileDataGrideNode) + { + if (!profileDataGrideNode) + return; + + this._save(); + profileDataGrideNode.savePosition(); + + this.children = [profileDataGrideNode]; + this.totalTime = profileDataGrideNode.totalTime; + }, + + exclude: function(/*ProfileDataGridNode*/ profileDataGrideNode) + { + if (!profileDataGrideNode) + return; + + this._save(); + + var excludedCallUID = profileDataGrideNode.callUID; + + WebInspector.TopDownProfileDataGridNode.prototype._exclude.call(this, excludedCallUID); + + if (this.lastComparator) + this.sort(this.lastComparator, true); + }, + + restore: function() + { + if (!this._savedChildren) + return; + + this.children[0].restorePosition(); + + WebInspector.ProfileDataGridTree.prototype.restore.call(this); + }, + + _merge: WebInspector.TopDownProfileDataGridNode.prototype._merge, + + _sharedPopulate: WebInspector.TopDownProfileDataGridNode.prototype._sharedPopulate +} + +WebInspector.TopDownProfileDataGridTree.prototype.__proto__ = WebInspector.ProfileDataGridTree.prototype; +/* ProfileView.js */ + +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// FIXME: Rename the file. + +WebInspector.CPUProfileView = function(profile) +{ + WebInspector.View.call(this); + + this.element.addStyleClass("profile-view"); + + this.showSelfTimeAsPercent = WebInspector.settings.createSetting("cpuProfilerShowSelfTimeAsPercent", true); + this.showTotalTimeAsPercent = WebInspector.settings.createSetting("cpuProfilerShowTotalTimeAsPercent", true); + this.showAverageTimeAsPercent = WebInspector.settings.createSetting("cpuProfilerShowAverageTimeAsPercent", true); + this._viewType = WebInspector.settings.createSetting("cpuProfilerView", WebInspector.CPUProfileView._TypeHeavy); + + var columns = { "self": { title: WebInspector.UIString("Self"), width: "72px", sort: "descending", sortable: true }, + "total": { title: WebInspector.UIString("Total"), width: "72px", sortable: true }, + "average": { title: WebInspector.UIString("Average"), width: "72px", sortable: true }, + "calls": { title: WebInspector.UIString("Calls"), width: "54px", sortable: true }, + "function": { title: WebInspector.UIString("Function"), disclosure: true, sortable: true } }; + + if (Capabilities.samplingCPUProfiler) { + delete columns.average; + delete columns.calls; + } + + this.dataGrid = new WebInspector.DataGrid(columns); + this.dataGrid.addEventListener("sorting changed", this._sortData, this); + this.dataGrid.element.addEventListener("mousedown", this._mouseDownInDataGrid.bind(this), true); + this.dataGrid.show(this.element); + + this.viewSelectElement = document.createElement("select"); + this.viewSelectElement.className = "status-bar-item"; + this.viewSelectElement.addEventListener("change", this._changeView.bind(this), false); + + var heavyViewOption = document.createElement("option"); + heavyViewOption.label = WebInspector.UIString("Heavy (Bottom Up)"); + var treeViewOption = document.createElement("option"); + treeViewOption.label = WebInspector.UIString("Tree (Top Down)"); + this.viewSelectElement.appendChild(heavyViewOption); + this.viewSelectElement.appendChild(treeViewOption); + this.viewSelectElement.selectedIndex = this._viewType.get() === WebInspector.CPUProfileView._TypeHeavy ? 0 : 1; + + this.percentButton = new WebInspector.StatusBarButton("", "percent-time-status-bar-item"); + this.percentButton.addEventListener("click", this._percentClicked.bind(this), false); + + this.focusButton = new WebInspector.StatusBarButton(WebInspector.UIString("Focus selected function."), "focus-profile-node-status-bar-item"); + this.focusButton.disabled = true; + this.focusButton.addEventListener("click", this._focusClicked.bind(this), false); + + this.excludeButton = new WebInspector.StatusBarButton(WebInspector.UIString("Exclude selected function."), "exclude-profile-node-status-bar-item"); + this.excludeButton.disabled = true; + this.excludeButton.addEventListener("click", this._excludeClicked.bind(this), false); + + this.resetButton = new WebInspector.StatusBarButton(WebInspector.UIString("Restore all functions."), "reset-profile-status-bar-item"); + this.resetButton.visible = false; + this.resetButton.addEventListener("click", this._resetClicked.bind(this), false); + + this.profile = profile; + + function profileCallback(error, profile) + { + if (error) + return; + + if (!profile.head) { + // Profiling was tentatively terminated with the "Clear all profiles." button. + return; + } + this.profile.head = profile.head; + this._assignParentsInProfile(); + this._changeView(); + this._updatePercentButton(); + } + + this._linkifier = WebInspector.debuggerPresentationModel.createLinkifier(new WebInspector.DebuggerPresentationModel.DefaultLinkifierFormatter(30)); + + ProfilerAgent.getProfile(this.profile.typeId, this.profile.uid, profileCallback.bind(this)); +} + +WebInspector.CPUProfileView._TypeTree = "Tree"; +WebInspector.CPUProfileView._TypeHeavy = "Heavy"; + +WebInspector.CPUProfileView.prototype = { + get statusBarItems() + { + return [this.viewSelectElement, this.percentButton.element, this.focusButton.element, this.excludeButton.element, this.resetButton.element]; + }, + + get profile() + { + return this._profile; + }, + + set profile(profile) + { + this._profile = profile; + }, + + get bottomUpProfileDataGridTree() + { + if (!this._bottomUpProfileDataGridTree) { + if (this.profile.bottomUpHead) + this._bottomUpProfileDataGridTree = new WebInspector.TopDownProfileDataGridTree(this, this.profile.bottomUpHead); + else + this._bottomUpProfileDataGridTree = new WebInspector.BottomUpProfileDataGridTree(this, this.profile.head); + } + return this._bottomUpProfileDataGridTree; + }, + + get topDownProfileDataGridTree() + { + if (!this._topDownProfileDataGridTree) + this._topDownProfileDataGridTree = new WebInspector.TopDownProfileDataGridTree(this, this.profile.head); + return this._topDownProfileDataGridTree; + }, + + get currentTree() + { + return this._currentTree; + }, + + set currentTree(tree) + { + this._currentTree = tree; + this.refresh(); + }, + + get topDownTree() + { + if (!this._topDownTree) { + this._topDownTree = WebInspector.TopDownTreeFactory.create(this.profile.head); + this._sortProfile(this._topDownTree); + } + + return this._topDownTree; + }, + + get bottomUpTree() + { + if (!this._bottomUpTree) { + this._bottomUpTree = WebInspector.BottomUpTreeFactory.create(this.profile.head); + this._sortProfile(this._bottomUpTree); + } + + return this._bottomUpTree; + }, + + willHide: function() + { + this._currentSearchResultIndex = -1; + }, + + refresh: function() + { + var selectedProfileNode = this.dataGrid.selectedNode ? this.dataGrid.selectedNode.profileNode : null; + + this.dataGrid.removeChildren(); + + var children = this.profileDataGridTree.children; + var count = children.length; + + for (var index = 0; index < count; ++index) + this.dataGrid.appendChild(children[index]); + + if (selectedProfileNode) + selectedProfileNode.selected = true; + }, + + refreshVisibleData: function() + { + var child = this.dataGrid.children[0]; + while (child) { + child.refresh(); + child = child.traverseNextNode(false, null, true); + } + }, + + refreshShowAsPercents: function() + { + this._updatePercentButton(); + this.refreshVisibleData(); + }, + + searchCanceled: function() + { + if (this._searchResults) { + for (var i = 0; i < this._searchResults.length; ++i) { + var profileNode = this._searchResults[i].profileNode; + + delete profileNode._searchMatchedSelfColumn; + delete profileNode._searchMatchedTotalColumn; + delete profileNode._searchMatchedCallsColumn; + delete profileNode._searchMatchedFunctionColumn; + + profileNode.refresh(); + } + } + + delete this._searchFinishedCallback; + this._currentSearchResultIndex = -1; + this._searchResults = []; + }, + + performSearch: function(query, finishedCallback) + { + // Call searchCanceled since it will reset everything we need before doing a new search. + this.searchCanceled(); + + query = query.trim(); + + if (!query.length) + return; + + this._searchFinishedCallback = finishedCallback; + + var greaterThan = (query.indexOf(">") === 0); + var lessThan = (query.indexOf("<") === 0); + var equalTo = (query.indexOf("=") === 0 || ((greaterThan || lessThan) && query.indexOf("=") === 1)); + var percentUnits = (query.lastIndexOf("%") === (query.length - 1)); + var millisecondsUnits = (query.length > 2 && query.lastIndexOf("ms") === (query.length - 2)); + var secondsUnits = (!millisecondsUnits && query.lastIndexOf("s") === (query.length - 1)); + + var queryNumber = parseFloat(query); + if (greaterThan || lessThan || equalTo) { + if (equalTo && (greaterThan || lessThan)) + queryNumber = parseFloat(query.substring(2)); + else + queryNumber = parseFloat(query.substring(1)); + } + + var queryNumberMilliseconds = (secondsUnits ? (queryNumber * 1000) : queryNumber); + + // Make equalTo implicitly true if it wasn't specified there is no other operator. + if (!isNaN(queryNumber) && !(greaterThan || lessThan)) + equalTo = true; + + function matchesQuery(/*ProfileDataGridNode*/ profileDataGridNode) + { + delete profileDataGridNode._searchMatchedSelfColumn; + delete profileDataGridNode._searchMatchedTotalColumn; + delete profileDataGridNode._searchMatchedAverageColumn; + delete profileDataGridNode._searchMatchedCallsColumn; + delete profileDataGridNode._searchMatchedFunctionColumn; + + if (percentUnits) { + if (lessThan) { + if (profileDataGridNode.selfPercent < queryNumber) + profileDataGridNode._searchMatchedSelfColumn = true; + if (profileDataGridNode.totalPercent < queryNumber) + profileDataGridNode._searchMatchedTotalColumn = true; + if (profileDataGridNode.averagePercent < queryNumberMilliseconds) + profileDataGridNode._searchMatchedAverageColumn = true; + } else if (greaterThan) { + if (profileDataGridNode.selfPercent > queryNumber) + profileDataGridNode._searchMatchedSelfColumn = true; + if (profileDataGridNode.totalPercent > queryNumber) + profileDataGridNode._searchMatchedTotalColumn = true; + if (profileDataGridNode.averagePercent < queryNumberMilliseconds) + profileDataGridNode._searchMatchedAverageColumn = true; + } + + if (equalTo) { + if (profileDataGridNode.selfPercent == queryNumber) + profileDataGridNode._searchMatchedSelfColumn = true; + if (profileDataGridNode.totalPercent == queryNumber) + profileDataGridNode._searchMatchedTotalColumn = true; + if (profileDataGridNode.averagePercent < queryNumberMilliseconds) + profileDataGridNode._searchMatchedAverageColumn = true; + } + } else if (millisecondsUnits || secondsUnits) { + if (lessThan) { + if (profileDataGridNode.selfTime < queryNumberMilliseconds) + profileDataGridNode._searchMatchedSelfColumn = true; + if (profileDataGridNode.totalTime < queryNumberMilliseconds) + profileDataGridNode._searchMatchedTotalColumn = true; + if (profileDataGridNode.averageTime < queryNumberMilliseconds) + profileDataGridNode._searchMatchedAverageColumn = true; + } else if (greaterThan) { + if (profileDataGridNode.selfTime > queryNumberMilliseconds) + profileDataGridNode._searchMatchedSelfColumn = true; + if (profileDataGridNode.totalTime > queryNumberMilliseconds) + profileDataGridNode._searchMatchedTotalColumn = true; + if (profileDataGridNode.averageTime > queryNumberMilliseconds) + profileDataGridNode._searchMatchedAverageColumn = true; + } + + if (equalTo) { + if (profileDataGridNode.selfTime == queryNumberMilliseconds) + profileDataGridNode._searchMatchedSelfColumn = true; + if (profileDataGridNode.totalTime == queryNumberMilliseconds) + profileDataGridNode._searchMatchedTotalColumn = true; + if (profileDataGridNode.averageTime == queryNumberMilliseconds) + profileDataGridNode._searchMatchedAverageColumn = true; + } + } else { + if (equalTo && profileDataGridNode.numberOfCalls == queryNumber) + profileDataGridNode._searchMatchedCallsColumn = true; + if (greaterThan && profileDataGridNode.numberOfCalls > queryNumber) + profileDataGridNode._searchMatchedCallsColumn = true; + if (lessThan && profileDataGridNode.numberOfCalls < queryNumber) + profileDataGridNode._searchMatchedCallsColumn = true; + } + + if (profileDataGridNode.functionName.hasSubstring(query, true) || profileDataGridNode.url.hasSubstring(query, true)) + profileDataGridNode._searchMatchedFunctionColumn = true; + + if (profileDataGridNode._searchMatchedSelfColumn || + profileDataGridNode._searchMatchedTotalColumn || + profileDataGridNode._searchMatchedAverageColumn || + profileDataGridNode._searchMatchedCallsColumn || + profileDataGridNode._searchMatchedFunctionColumn) + { + profileDataGridNode.refresh(); + return true; + } + + return false; + } + + var current = this.profileDataGridTree.children[0]; + + while (current) { + if (matchesQuery(current)) { + this._searchResults.push({ profileNode: current }); + } + + current = current.traverseNextNode(false, null, false); + } + + finishedCallback(this, this._searchResults.length); + }, + + jumpToFirstSearchResult: function() + { + if (!this._searchResults || !this._searchResults.length) + return; + this._currentSearchResultIndex = 0; + this._jumpToSearchResult(this._currentSearchResultIndex); + }, + + jumpToLastSearchResult: function() + { + if (!this._searchResults || !this._searchResults.length) + return; + this._currentSearchResultIndex = (this._searchResults.length - 1); + this._jumpToSearchResult(this._currentSearchResultIndex); + }, + + jumpToNextSearchResult: function() + { + if (!this._searchResults || !this._searchResults.length) + return; + if (++this._currentSearchResultIndex >= this._searchResults.length) + this._currentSearchResultIndex = 0; + this._jumpToSearchResult(this._currentSearchResultIndex); + }, + + jumpToPreviousSearchResult: function() + { + if (!this._searchResults || !this._searchResults.length) + return; + if (--this._currentSearchResultIndex < 0) + this._currentSearchResultIndex = (this._searchResults.length - 1); + this._jumpToSearchResult(this._currentSearchResultIndex); + }, + + showingFirstSearchResult: function() + { + return (this._currentSearchResultIndex === 0); + }, + + showingLastSearchResult: function() + { + return (this._searchResults && this._currentSearchResultIndex === (this._searchResults.length - 1)); + }, + + _jumpToSearchResult: function(index) + { + var searchResult = this._searchResults[index]; + if (!searchResult) + return; + + var profileNode = searchResult.profileNode; + profileNode.revealAndSelect(); + }, + + _changeView: function() + { + if (!this.profile) + return; + + if (this.viewSelectElement.selectedIndex == 1) { + this.profileDataGridTree = this.topDownProfileDataGridTree; + this._sortProfile(); + this._viewType.set(WebInspector.CPUProfileView._TypeTree); + } else if (this.viewSelectElement.selectedIndex == 0) { + this.profileDataGridTree = this.bottomUpProfileDataGridTree; + this._sortProfile(); + this._viewType.set(WebInspector.CPUProfileView._TypeHeavy); + } + + if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults) + return; + + // The current search needs to be performed again. First negate out previous match + // count by calling the search finished callback with a negative number of matches. + // Then perform the search again the with same query and callback. + this._searchFinishedCallback(this, -this._searchResults.length); + this.performSearch(this.currentQuery, this._searchFinishedCallback); + }, + + _percentClicked: function(event) + { + var currentState = this.showSelfTimeAsPercent.get() && this.showTotalTimeAsPercent.get() && this.showAverageTimeAsPercent.get(); + this.showSelfTimeAsPercent.set(!currentState); + this.showTotalTimeAsPercent.set(!currentState); + this.showAverageTimeAsPercent.set(!currentState); + this.refreshShowAsPercents(); + }, + + _updatePercentButton: function() + { + if (this.showSelfTimeAsPercent.get() && this.showTotalTimeAsPercent.get() && this.showAverageTimeAsPercent.get()) { + this.percentButton.title = WebInspector.UIString("Show absolute total and self times."); + this.percentButton.toggled = true; + } else { + this.percentButton.title = WebInspector.UIString("Show total and self times as percentages."); + this.percentButton.toggled = false; + } + }, + + _focusClicked: function(event) + { + if (!this.dataGrid.selectedNode) + return; + + this.resetButton.visible = true; + this.profileDataGridTree.focus(this.dataGrid.selectedNode); + this.refresh(); + this.refreshVisibleData(); + }, + + _excludeClicked: function(event) + { + var selectedNode = this.dataGrid.selectedNode + + if (!selectedNode) + return; + + selectedNode.deselect(); + + this.resetButton.visible = true; + this.profileDataGridTree.exclude(selectedNode); + this.refresh(); + this.refreshVisibleData(); + }, + + _resetClicked: function(event) + { + this.resetButton.visible = false; + this.profileDataGridTree.restore(); + this._linkifier.reset(); + this.refresh(); + this.refreshVisibleData(); + }, + + _dataGridNodeSelected: function(node) + { + this.focusButton.disabled = false; + this.excludeButton.disabled = false; + }, + + _dataGridNodeDeselected: function(node) + { + this.focusButton.disabled = true; + this.excludeButton.disabled = true; + }, + + _sortData: function(event) + { + this._sortProfile(this.profile); + }, + + _sortProfile: function() + { + var sortAscending = this.dataGrid.sortOrder === "ascending"; + var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier; + var sortProperty = { + "average": "averageTime", + "self": "selfTime", + "total": "totalTime", + "calls": "numberOfCalls", + "function": "functionName" + }[sortColumnIdentifier]; + + this.profileDataGridTree.sort(WebInspector.ProfileDataGridTree.propertyComparator(sortProperty, sortAscending)); + + this.refresh(); + }, + + _mouseDownInDataGrid: function(event) + { + if (event.detail < 2) + return; + + var cell = event.target.enclosingNodeOrSelfWithNodeName("td"); + if (!cell || (!cell.hasStyleClass("total-column") && !cell.hasStyleClass("self-column") && !cell.hasStyleClass("average-column"))) + return; + + if (cell.hasStyleClass("total-column")) + this.showTotalTimeAsPercent.set(!this.showTotalTimeAsPercent.get()); + else if (cell.hasStyleClass("self-column")) + this.showSelfTimeAsPercent.set(!this.showSelfTimeAsPercent.get()); + else if (cell.hasStyleClass("average-column")) + this.showAverageTimeAsPercent.set(!this.showAverageTimeAsPercent.get()); + + this.refreshShowAsPercents(); + + event.preventDefault(); + event.stopPropagation(); + }, + + _assignParentsInProfile: function() + { + var head = this.profile.head; + head.parent = null; + head.head = null; + var nodesToTraverse = [ { parent: head, children: head.children } ]; + while (nodesToTraverse.length > 0) { + var pair = nodesToTraverse.shift(); + var parent = pair.parent; + var children = pair.children; + var length = children.length; + for (var i = 0; i < length; ++i) { + children[i].head = head; + children[i].parent = parent; + if (children[i].children.length > 0) + nodesToTraverse.push({ parent: children[i], children: children[i].children }); + } + } + } +} + +WebInspector.CPUProfileView.prototype.__proto__ = WebInspector.View.prototype; + +WebInspector.CPUProfileType = function() +{ + WebInspector.ProfileType.call(this, WebInspector.CPUProfileType.TypeId, WebInspector.UIString("Collect JavaScript CPU Profile")); + this._recording = false; + WebInspector.CPUProfileType.instance = this; +} + +WebInspector.CPUProfileType.TypeId = "CPU"; + +WebInspector.CPUProfileType.prototype = { + get buttonTooltip() + { + return this._recording ? WebInspector.UIString("Stop CPU profiling.") : WebInspector.UIString("Start CPU profiling."); + }, + + buttonClicked: function() + { + if (this._recording) { + this.stopRecordingProfile(); + WebInspector.networkManager.enableResourceTracking(); + } else { + WebInspector.networkManager.disableResourceTracking(); + this.startRecordingProfile(); + } + }, + + get treeItemTitle() + { + return WebInspector.UIString("CPU PROFILES"); + }, + + get description() + { + return WebInspector.UIString("CPU profiles show where the execution time is spent in your page's JavaScript functions."); + }, + + isRecordingProfile: function() + { + return this._recording; + }, + + startRecordingProfile: function() + { + this._recording = true; + ProfilerAgent.start(); + }, + + stopRecordingProfile: function() + { + this._recording = false; + ProfilerAgent.stop(); + }, + + setRecordingProfile: function(isProfiling) + { + this._recording = isProfiling; + }, + + createSidebarTreeElementForProfile: function(profile) + { + return new WebInspector.ProfileSidebarTreeElement(profile, WebInspector.UIString("Profile %d"), "profile-sidebar-tree-item"); + }, + + createView: function(profile) + { + return new WebInspector.CPUProfileView(profile); + } +} + +WebInspector.CPUProfileType.prototype.__proto__ = WebInspector.ProfileType.prototype; +/* CSSSelectorProfileView.js */ + +/* + * Copyright (C) 2011 Google Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends WebInspector.DataGridNode + * @param {WebInspector.CSSSelectorProfileView} profileView + */ +WebInspector.CSSSelectorDataGridNode = function(profileView, data) +{ + WebInspector.DataGridNode.call(this, data, false); + this._profileView = profileView; +} + +WebInspector.CSSSelectorDataGridNode.prototype = { + get data() + { + var data = {}; + data.selector = this._data.selector; + data.matches = this._data.matchCount; + + if (this._profileView.showTimeAsPercent.get()) + data.time = Number(this._data.timePercent).toFixed(1) + "%"; + else + data.time = Number.secondsToString(this._data.time / 1000, true); + + return data; + }, + + get rawData() + { + return this._data; + }, + + createCell: function(columnIdentifier) + { + var cell = WebInspector.DataGridNode.prototype.createCell.call(this, columnIdentifier); + if (columnIdentifier === "selector" && cell.firstChild) { + cell.firstChild.title = this.rawData.selector; + return cell; + } + + if (columnIdentifier !== "source") + return cell; + + cell.removeChildren(); + + if (this.rawData.url) { + var wrapperDiv = cell.createChild("div"); + wrapperDiv.appendChild(WebInspector.linkifyResourceAsNode(this.rawData.url, this.rawData.lineNumber)); + } + + return cell; + } +} + +WebInspector.CSSSelectorDataGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype; + +/** + * @constructor + * @extends WebInspector.View + * @param {CSSAgent.SelectorProfile} profile + */ +WebInspector.CSSSelectorProfileView = function(profile) +{ + WebInspector.View.call(this); + + this.element.addStyleClass("profile-view"); + + this.showTimeAsPercent = WebInspector.settings.createSetting("selectorProfilerShowTimeAsPercent", true); + + var columns = { "selector": { title: WebInspector.UIString("Selector"), width: "550px", sortable: true }, + "source": { title: WebInspector.UIString("Source"), width: "100px", sortable: true }, + "time": { title: WebInspector.UIString("Total"), width: "72px", sort: "descending", sortable: true }, + "matches": { title: WebInspector.UIString("Matches"), width: "72px", sortable: true } }; + + this.dataGrid = new WebInspector.DataGrid(columns); + this.dataGrid.element.addStyleClass("selector-profile-view"); + this.dataGrid.addEventListener("sorting changed", this._sortProfile, this); + this.dataGrid.element.addEventListener("mousedown", this._mouseDownInDataGrid.bind(this), true); + this.dataGrid.show(this.element); + + this.percentButton = new WebInspector.StatusBarButton("", "percent-time-status-bar-item"); + this.percentButton.addEventListener("click", this._percentClicked, this); + + this.profile = profile; + + this._createProfileNodes(); + this._sortProfile(); + this._updatePercentButton(); +} + +WebInspector.CSSSelectorProfileView.prototype = { + get statusBarItems() + { + return [this.percentButton.element]; + }, + + get profile() + { + return this._profile; + }, + + set profile(profile) + { + this._profile = profile; + }, + + _createProfileNodes: function() + { + var data = this.profile.data; + if (!data) { + // The profiler may have been terminated with the "Clear all profiles." button. + return; + } + + this.profile.children = []; + for (var i = 0; i < data.length; ++i) { + data[i].timePercent = data[i].time * 100 / this.profile.totalTime; + var node = new WebInspector.CSSSelectorDataGridNode(this, data[i]); + this.profile.children.push(node); + } + }, + + rebuildGridItems: function() + { + this.dataGrid.removeChildren(); + + var children = this.profile.children; + var count = children.length; + + for (var index = 0; index < count; ++index) + this.dataGrid.appendChild(children[index]); + }, + + refreshData: function() + { + var child = this.dataGrid.children[0]; + while (child) { + child.refresh(); + child = child.traverseNextNode(false, null, true); + } + }, + + refreshShowAsPercents: function() + { + this._updatePercentButton(); + this.refreshData(); + }, + + _percentClicked: function(event) + { + this.showTimeAsPercent.set(!this.showTimeAsPercent.get()); + this.refreshShowAsPercents(); + }, + + _updatePercentButton: function() + { + if (this.showTimeAsPercent.get()) { + this.percentButton.title = WebInspector.UIString("Show absolute times."); + this.percentButton.toggled = true; + } else { + this.percentButton.title = WebInspector.UIString("Show times as percentages."); + this.percentButton.toggled = false; + } + }, + + _sortProfile: function() + { + var sortAscending = this.dataGrid.sortOrder === "ascending"; + var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier; + + function selectorComparator(a, b) + { + var result = b.rawData.selector.localeCompare(a.rawData.selector); + return sortAscending ? -result : result; + } + + function sourceComparator(a, b) + { + var aRawData = a.rawData; + var bRawData = b.rawData; + var result = bRawData.url.localeCompare(aRawData.url); + if (!result) + result = bRawData.lineNumber - aRawData.lineNumber; + return sortAscending ? -result : result; + } + + function timeComparator(a, b) + { + const result = b.rawData.time - a.rawData.time; + return sortAscending ? -result : result; + } + + function matchesComparator(a, b) + { + const result = b.rawData.matchCount - a.rawData.matchCount; + return sortAscending ? -result : result; + } + + var comparator; + switch (sortColumnIdentifier) { + case "time": + comparator = timeComparator; + break; + case "matches": + comparator = matchesComparator; + break; + case "selector": + comparator = selectorComparator; + break; + case "source": + comparator = sourceComparator; + break; + } + + this.profile.children.sort(comparator); + + this.rebuildGridItems(); + }, + + _mouseDownInDataGrid: function(event) + { + if (event.detail < 2) + return; + + var cell = event.target.enclosingNodeOrSelfWithNodeName("td"); + if (!cell) + return; + + if (cell.hasStyleClass("time-column")) + this.showTimeAsPercent.set(!this.showTimeAsPercent.get()); + else + return; + + this.refreshShowAsPercents(); + + event.preventDefault(); + event.stopPropagation(); + } +} + +WebInspector.CSSSelectorProfileView.prototype.__proto__ = WebInspector.View.prototype; + +/** + * @constructor + */ +WebInspector.CSSSelectorProfileType = function() +{ + WebInspector.ProfileType.call(this, WebInspector.CSSSelectorProfileType.TypeId, WebInspector.UIString("Collect CSS Selector Profile")); + this._recording = false; + this._profileUid = 1; + WebInspector.CSSSelectorProfileType.instance = this; +} + +WebInspector.CSSSelectorProfileType.TypeId = "SELECTOR"; + +WebInspector.CSSSelectorProfileType.prototype = { + get buttonTooltip() + { + return this._recording ? WebInspector.UIString("Stop CSS selector profiling.") : WebInspector.UIString("Start CSS selector profiling."); + }, + + buttonClicked: function() + { + if (this._recording) + this.stopRecordingProfile(); + else + this.startRecordingProfile(); + }, + + get treeItemTitle() + { + return WebInspector.UIString("CSS SELECTOR PROFILES"); + }, + + get description() + { + return WebInspector.UIString("CSS selector profiles show how long the selector matching has taken in total and how many times a certain selector has matched DOM elements (the results are approximate due to matching algorithm optimizations.)"); + }, + + reset: function() + { + this._profileUid = 1; + }, + + isRecordingProfile: function() + { + return this._recording; + }, + + setRecordingProfile: function(isProfiling) + { + this._recording = isProfiling; + }, + + startRecordingProfile: function() + { + this._recording = true; + CSSAgent.startSelectorProfiler(); + WebInspector.panels.profiles.setRecordingProfile(WebInspector.CSSSelectorProfileType.TypeId, true); + }, + + stopRecordingProfile: function() + { + function callback(error, profile) + { + if (error) + return; + + profile.uid = this._profileUid++; + profile.title = WebInspector.UIString("Profile %d", profile.uid) + String.sprintf(" (%s)", Number.secondsToString(profile.totalTime / 1000)); + profile.typeId = WebInspector.CSSSelectorProfileType.TypeId; + WebInspector.panels.profiles.addProfileHeader(profile); + WebInspector.panels.profiles.setRecordingProfile(WebInspector.CSSSelectorProfileType.TypeId, false); + } + + this._recording = false; + CSSAgent.stopSelectorProfiler(callback.bind(this)); + }, + + createSidebarTreeElementForProfile: function(profile) + { + return new WebInspector.ProfileSidebarTreeElement(profile, profile.title, "profile-sidebar-tree-item"); + }, + + createView: function(profile) + { + return new WebInspector.CSSSelectorProfileView(profile); + } +} + +WebInspector.CSSSelectorProfileType.prototype.__proto__ = WebInspector.ProfileType.prototype; +/* PartialQuickSort.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +Object.defineProperty(Array.prototype, "sortRange", { value: +/** @this {Array} */ +function(comparator, leftBound, rightBound, k) +{ + function swap(array, i1, i2) + { + var temp = array[i1]; + array[i1] = array[i2]; + array[i2] = temp; + } + + function partition(array, comparator, left, right, pivotIndex) + { + var pivotValue = array[pivotIndex]; + swap(array, right, pivotIndex); + var storeIndex = left; + for (var i = left; i < right; ++i) { + if (comparator(array[i], pivotValue) < 0) { + swap(array, storeIndex, i); + ++storeIndex; + } + } + swap(array, right, storeIndex); + return storeIndex; + } + + function quickSortFirstK(array, comparator, left, right, k) + { + if (right <= left) + return; + var pivotIndex = Math.floor(Math.random() * (right - left)) + left; + var pivotNewIndex = partition(array, comparator, left, right, pivotIndex); + quickSortFirstK(array, comparator, left, pivotNewIndex - 1, k); + if (pivotNewIndex < left + k - 1) + quickSortFirstK(array, comparator, pivotNewIndex + 1, right, k); + } + + if (leftBound === 0 && rightBound === (this.length - 1) && k === this.length) + this.sort(comparator); + else + quickSortFirstK(this, comparator, leftBound, rightBound, k); + return this; +}}); +/* HeapSnapshot.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.HeapSnapshotLoader = function() +{ + this._json = ""; + this._state = "find-snapshot-info"; + this._snapshot = {}; +} + +WebInspector.HeapSnapshotLoader.prototype = { + _findBalancedCurlyBrackets: function() + { + var counter = 0; + var openingBracket = "{".charCodeAt(0), closingBracket = "}".charCodeAt(0); + for (var i = 0, l = this._json.length; i < l; ++i) { + var character = this._json.charCodeAt(i); + if (character === openingBracket) + ++counter; + else if (character === closingBracket) { + if (--counter === 0) + return i + 1; + } + } + return -1; + }, + + finishLoading: function() + { + if (!this._json) + return null; + this._parseStringsArray(); + this._json = ""; + var result = new WebInspector.HeapSnapshot(this._snapshot); + this._json = ""; + this._snapshot = {}; + return result; + }, + + _parseNodes: function() + { + var index = 0; + var char0 = "0".charCodeAt(0), char9 = "9".charCodeAt(0), closingBracket = "]".charCodeAt(0); + var length = this._json.length; + while (true) { + while (index < length) { + var code = this._json.charCodeAt(index); + if (char0 <= code && code <= char9) + break; + else if (code === closingBracket) { + this._json = this._json.slice(index + 1); + // Shave off provisionally allocated space. + this._snapshot.nodes = this._snapshot.nodes.slice(0); + return false; + } + ++index; + } + if (index === length) { + this._json = ""; + return true; + } + var startIndex = index; + while (index < length) { + var code = this._json.charCodeAt(index); + if (char0 > code || code > char9) + break; + ++index; + } + if (index === length) { + this._json = this._json.slice(startIndex); + return true; + } + this._snapshot.nodes.push(parseInt(this._json.slice(startIndex, index))); + } + }, + + _parseStringsArray: function() + { + var closingBracketIndex = this._json.lastIndexOf("]"); + if (closingBracketIndex === -1) + throw new Error("Incomplete JSON"); + this._json = this._json.slice(0, closingBracketIndex + 1); + this._snapshot.strings = JSON.parse(this._json); + }, + + pushJSONChunk: function(chunk) + { + this._json += chunk; + switch (this._state) { + case "find-snapshot-info": { + var snapshotToken = "\"snapshot\""; + var snapshotTokenIndex = this._json.indexOf(snapshotToken); + if (snapshotTokenIndex === -1) + throw new Error("Snapshot token not found"); + this._json = this._json.slice(snapshotTokenIndex + snapshotToken.length + 1); + this._state = "parse-snapshot-info"; + this.pushJSONChunk(""); + break; + } + case "parse-snapshot-info": { + var closingBracketIndex = this._findBalancedCurlyBrackets(); + if (closingBracketIndex === -1) + return; + this._snapshot.snapshot = JSON.parse(this._json.slice(0, closingBracketIndex)); + this._json = this._json.slice(closingBracketIndex); + this._state = "find-nodes"; + this.pushJSONChunk(""); + break; + } + case "find-nodes": { + var nodesToken = "\"nodes\""; + var nodesTokenIndex = this._json.indexOf(nodesToken); + if (nodesTokenIndex === -1) + return; + var bracketIndex = this._json.indexOf("[", nodesTokenIndex); + if (bracketIndex === -1) + return; + this._json = this._json.slice(bracketIndex + 1); + this._state = "parse-nodes-meta-info"; + this.pushJSONChunk(""); + break; + } + case "parse-nodes-meta-info": { + var closingBracketIndex = this._findBalancedCurlyBrackets(); + if (closingBracketIndex === -1) + return; + this._snapshot.nodes = [JSON.parse(this._json.slice(0, closingBracketIndex))]; + this._json = this._json.slice(closingBracketIndex); + this._state = "parse-nodes"; + this.pushJSONChunk(""); + break; + } + case "parse-nodes": { + if (this._parseNodes()) + return; + this._state = "find-strings"; + this.pushJSONChunk(""); + break; + } + case "find-strings": { + var stringsToken = "\"strings\""; + var stringsTokenIndex = this._json.indexOf(stringsToken); + if (stringsTokenIndex === -1) + return; + var bracketIndex = this._json.indexOf("[", stringsTokenIndex); + if (bracketIndex === -1) + return; + this._json = this._json.slice(bracketIndex); + this._state = "accumulate-strings"; + break; + } + case "accumulate-strings": + break; + } + } +}; + +WebInspector.HeapSnapshotArraySlice = function(snapshot, arrayName, start, end) +{ + // Note: we don't reference snapshot contents directly to avoid + // holding references to big chunks of data. + this._snapshot = snapshot; + this._arrayName = arrayName; + this._start = start; + this.length = end - start; +} + +WebInspector.HeapSnapshotArraySlice.prototype = { + item: function(index) + { + return this._snapshot[this._arrayName][this._start + index]; + }, + + slice: function(start, end) + { + if (typeof end === "undefined") + end = start + this._start + this.length; + return this._snapshot[this._arrayName].slice(this._start + start, end); + } +} + +WebInspector.HeapSnapshotEdge = function(snapshot, edges, edgeIndex) +{ + this._snapshot = snapshot; + this._edges = edges; + this.edgeIndex = edgeIndex || 0; +} + +WebInspector.HeapSnapshotEdge.prototype = { + clone: function() + { + return new WebInspector.HeapSnapshotEdge(this._snapshot, this._edges, this.edgeIndex); + }, + + get hasStringName() + { + if (!this.isShortcut) + return this._hasStringName; + return isNaN(parseInt(this._name, 10)); + }, + + get isElement() + { + return this._type() === this._snapshot._edgeElementType; + }, + + get isHidden() + { + return this._type() === this._snapshot._edgeHiddenType; + }, + + get isWeak() + { + return this._type() === this._snapshot._edgeWeakType; + }, + + get isInternal() + { + return this._type() === this._snapshot._edgeInternalType; + }, + + get isInvisible() + { + return this._type() === this._snapshot._edgeInvisibleType; + }, + + get isShortcut() + { + return this._type() === this._snapshot._edgeShortcutType; + }, + + get name() + { + if (!this.isShortcut) + return this._name; + var numName = parseInt(this._name, 10); + return isNaN(numName) ? this._name : numName; + }, + + get node() + { + return new WebInspector.HeapSnapshotNode(this._snapshot, this.nodeIndex); + }, + + get nodeIndex() + { + return this._edges.item(this.edgeIndex + this._snapshot._edgeToNodeOffset); + }, + + get rawEdges() + { + return this._edges; + }, + + toString: function() + { + switch (this.type) { + case "context": return "->" + this.name; + case "element": return "[" + this.name + "]"; + case "weak": return "[[" + this.name + "]]"; + case "property": + return this.name.indexOf(" ") === -1 ? "." + this.name : "[\"" + this.name + "\"]"; + case "shortcut": + var name = this.name; + if (typeof name === "string") + return this.name.indexOf(" ") === -1 ? "." + this.name : "[\"" + this.name + "\"]"; + else + return "[" + this.name + "]"; + case "internal": + case "hidden": + case "invisible": + return "{" + this.name + "}"; + }; + return "?" + this.name + "?"; + }, + + get type() + { + return this._snapshot._edgeTypes[this._type()]; + }, + + get _hasStringName() + { + return !this.isElement && !this.isHidden && !this.isWeak; + }, + + get _name() + { + return this._hasStringName ? this._snapshot._strings[this._nameOrIndex] : this._nameOrIndex; + }, + + get _nameOrIndex() + { + return this._edges.item(this.edgeIndex + this._snapshot._edgeNameOffset); + }, + + _type: function() + { + return this._edges.item(this.edgeIndex + this._snapshot._edgeTypeOffset); + } +}; + +WebInspector.HeapSnapshotEdgeIterator = function(edge) +{ + this.edge = edge; +} + +WebInspector.HeapSnapshotEdgeIterator.prototype = { + first: function() + { + this.edge.edgeIndex = 0; + }, + + hasNext: function() + { + return this.edge.edgeIndex < this.edge._edges.length; + }, + + get index() + { + return this.edge.edgeIndex; + }, + + set index(newIndex) + { + this.edge.edgeIndex = newIndex; + }, + + get item() + { + return this.edge; + }, + + next: function() + { + this.edge.edgeIndex += this.edge._snapshot._edgeFieldsCount; + } +}; + +WebInspector.HeapSnapshotRetainerEdge = function(snapshot, retainers, retainerIndex) +{ + this._snapshot = snapshot; + this._retainers = retainers; + this.retainerIndex = retainerIndex || 0; +} + +WebInspector.HeapSnapshotRetainerEdge.prototype = { + clone: function() + { + return new WebInspector.HeapSnapshotRetainerEdge(this._snapshot, this._retainers, this.retainerIndex); + }, + + get hasStringName() + { + return this._edge.hasStringName; + }, + + get isElement() + { + return this._edge.isElement; + }, + + get isHidden() + { + return this._edge.isHidden; + }, + + get isInternal() + { + return this._edge.isInternal; + }, + + get isInvisible() + { + return this._edge.isInvisible; + }, + + get isShortcut() + { + return this._edge.isShortcut; + }, + + get isWeak() + { + return this._edge.isWeak; + }, + + get name() + { + return this._edge.name; + }, + + get node() + { + return this._node; + }, + + get nodeIndex() + { + return this._nodeIndex; + }, + + get retainerIndex() + { + return this._retainerIndex; + }, + + set retainerIndex(newIndex) + { + if (newIndex !== this._retainerIndex) { + this._retainerIndex = newIndex; + this._setupEdge(); + } + }, + + _setupEdge: function() + { + var globalEdgeIndex = this._retainers.item(this._retainerIndex); + this._nodeIndex = this._snapshot._findNearestNodeIndex(globalEdgeIndex); + this._node = new WebInspector.HeapSnapshotNode(this._snapshot, this._nodeIndex); + var edgeIndex = globalEdgeIndex - this._nodeIndex - this._snapshot._firstEdgeOffset; + this._edge = new WebInspector.HeapSnapshotEdge(this._snapshot, this._node.rawEdges, edgeIndex); + }, + + toString: function() + { + return this._edge.toString(); + }, + + get type() + { + return this._edge.type; + } +} + +WebInspector.HeapSnapshotRetainerEdgeIterator = function(retainer) +{ + this.retainer = retainer; +} + +WebInspector.HeapSnapshotRetainerEdgeIterator.prototype = { + first: function() + { + this.retainer.retainerIndex = 0; + }, + + hasNext: function() + { + return this.retainer.retainerIndex < this.retainer._retainers.length; + }, + + get index() + { + return this.retainer.retainerIndex; + }, + + set index(newIndex) + { + this.retainer.retainerIndex = newIndex; + }, + + get item() + { + return this.retainer; + }, + + next: function() + { + ++this.retainer.retainerIndex; + } +}; + +WebInspector.HeapSnapshotNode = function(snapshot, nodeIndex) +{ + this._snapshot = snapshot; + this._firstNodeIndex = nodeIndex; + this.nodeIndex = nodeIndex; +} + +WebInspector.HeapSnapshotNode.prototype = { + get canBeQueried() + { + var flags = this._snapshot._flagsOfNode(this); + return !!(flags & this._snapshot._nodeFlags.canBeQueried); + }, + + get className() + { + switch (this.type) { + case "hidden": + return WebInspector.UIString("(system)"); + case "object": { + var commentPos = this.name.indexOf("/"); + return commentPos !== -1 ? this.name.substring(0, commentPos).trimRight() : this.name; + } + case "native": { + var entitiesCountPos = this.name.indexOf("/"); + return entitiesCountPos !== -1 ? this.name.substring(0, entitiesCountPos).trimRight() : this.name; + } + case "code": + return WebInspector.UIString("(compiled code)"); + default: + return "(" + this.type + ")"; + } + }, + + get dominatorIndex() + { + return this._nodes[this.nodeIndex + this._snapshot._dominatorOffset]; + }, + + get edges() + { + return new WebInspector.HeapSnapshotEdgeIterator(new WebInspector.HeapSnapshotEdge(this._snapshot, this.rawEdges)); + }, + + get edgesCount() + { + return this._nodes[this.nodeIndex + this._snapshot._edgesCountOffset]; + }, + + get flags() + { + return this._snapshot._flagsOfNode(this); + }, + + get id() + { + return this._nodes[this.nodeIndex + this._snapshot._nodeIdOffset]; + }, + + get instancesCount() + { + return this._nodes[this.nodeIndex + this._snapshot._nodeInstancesCountOffset]; + }, + + get isHidden() + { + return this._type() === this._snapshot._nodeHiddenType; + }, + + get isDOMWindow() + { + return this.name.substr(0, 9) === "DOMWindow"; + }, + + get isNativeRoot() + { + return this.name === "(Native objects)"; + }, + + get isDetachedDOMTree() + { + return this.className === "Detached DOM tree"; + }, + + get isRoot() + { + return this.nodeIndex === this._snapshot._rootNodeIndex; + }, + + get name() + { + return this._snapshot._strings[this._name()]; + }, + + get rawEdges() + { + var firstEdgeIndex = this._firstEdgeIndex(); + return new WebInspector.HeapSnapshotArraySlice(this._snapshot, "_nodes", firstEdgeIndex, firstEdgeIndex + this.edgesCount * this._snapshot._edgeFieldsCount); + }, + + get retainedSize() + { + return this._nodes[this.nodeIndex + this._snapshot._nodeRetainedSizeOffset]; + }, + + get retainers() + { + return new WebInspector.HeapSnapshotRetainerEdgeIterator(new WebInspector.HeapSnapshotRetainerEdge(this._snapshot, this._snapshot._retainersForNode(this))); + }, + + get selfSize() + { + return this._nodes[this.nodeIndex + this._snapshot._nodeSelfSizeOffset]; + }, + + get type() + { + return this._snapshot._nodeTypes[this._type()]; + }, + + _name: function() + { + return this._nodes[this.nodeIndex + this._snapshot._nodeNameOffset]; + }, + + get _nodes() + { + return this._snapshot._nodes; + }, + + _firstEdgeIndex: function() + { + return this.nodeIndex + this._snapshot._firstEdgeOffset; + }, + + get _nextNodeIndex() + { + return this._firstEdgeIndex() + this.edgesCount * this._snapshot._edgeFieldsCount; + }, + + _type: function() + { + return this._nodes[this.nodeIndex + this._snapshot._nodeTypeOffset]; + } +}; + +WebInspector.HeapSnapshotNodeIterator = function(node) +{ + this.node = node; +} + +WebInspector.HeapSnapshotNodeIterator.prototype = { + first: function() + { + this.node.nodeIndex = this.node._firstNodeIndex; + }, + + hasNext: function() + { + return this.node.nodeIndex < this.node._nodes.length; + }, + + get index() + { + return this.node.nodeIndex; + }, + + set index(newIndex) + { + this.node.nodeIndex = newIndex; + }, + + get item() + { + return this.node; + }, + + next: function() + { + this.node.nodeIndex = this.node._nextNodeIndex; + } +} + +WebInspector.HeapSnapshot = function(profile) +{ + this.uid = profile.snapshot.uid; + this._nodes = profile.nodes; + this._strings = profile.strings; + + this._init(); +} + +WebInspector.HeapSnapshot.prototype = { + _init: function() + { + this._metaNodeIndex = 0; + this._rootNodeIndex = 1; + var meta = this._nodes[this._metaNodeIndex]; + this._nodeTypeOffset = meta.fields.indexOf("type"); + this._nodeNameOffset = meta.fields.indexOf("name"); + this._nodeIdOffset = meta.fields.indexOf("id"); + this._nodeInstancesCountOffset = this._nodeIdOffset; + this._nodeSelfSizeOffset = meta.fields.indexOf("self_size"); + this._nodeRetainedSizeOffset = meta.fields.indexOf("retained_size"); + this._dominatorOffset = meta.fields.indexOf("dominator"); + this._edgesCountOffset = meta.fields.indexOf("children_count"); + this._firstEdgeOffset = meta.fields.indexOf("children"); + this._nodeTypes = meta.types[this._nodeTypeOffset]; + this._nodeHiddenType = this._nodeTypes.indexOf("hidden"); + var edgesMeta = meta.types[this._firstEdgeOffset]; + this._edgeFieldsCount = edgesMeta.fields.length; + this._edgeTypeOffset = edgesMeta.fields.indexOf("type"); + this._edgeNameOffset = edgesMeta.fields.indexOf("name_or_index"); + this._edgeToNodeOffset = edgesMeta.fields.indexOf("to_node"); + this._edgeTypes = edgesMeta.types[this._edgeTypeOffset]; + this._edgeElementType = this._edgeTypes.indexOf("element"); + this._edgeHiddenType = this._edgeTypes.indexOf("hidden"); + this._edgeInternalType = this._edgeTypes.indexOf("internal"); + this._edgeShortcutType = this._edgeTypes.indexOf("shortcut"); + this._edgeWeakType = this._edgeTypes.indexOf("weak"); + this._edgeInvisibleType = this._edgeTypes.length; + this._edgeTypes.push("invisible"); + + this._nodeFlags = { // bit flags + canBeQueried: 1, + detachedDOMTreeNode: 2, + }; + + this._markInvisibleEdges(); + }, + + dispose: function() + { + delete this._nodes; + delete this._strings; + delete this._retainers; + delete this._retainerIndex; + delete this._nodeIndex; + if (this._aggregates) { + delete this._aggregates; + delete this._aggregatesSortedFlags; + } + delete this._baseNodeIds; + delete this._dominatedNodes; + delete this._dominatedIndex; + delete this._flags; + }, + + get _allNodes() + { + return new WebInspector.HeapSnapshotNodeIterator(this.rootNode); + }, + + get nodeCount() + { + if (this._nodeCount) + return this._nodeCount; + + this._nodeCount = 0; + for (var iter = this._allNodes; iter.hasNext(); iter.next()) + ++this._nodeCount; + return this._nodeCount; + }, + + nodeFieldValuesByIndex: function(fieldName, indexes) + { + var node = new WebInspector.HeapSnapshotNode(this); + var result = new Array(indexes.length); + for (var i = 0, l = indexes.length; i < l; ++i) { + node.nodeIndex = indexes[i]; + result[i] = node[fieldName]; + } + return result; + }, + + get rootNode() + { + return new WebInspector.HeapSnapshotNode(this, this._rootNodeIndex); + }, + + get maxNodeId() + { + if (typeof this._maxNodeId === "number") + return this._maxNodeId; + this._maxNodeId = 0; + for (var iter = this._allNodes; iter.hasNext(); iter.next()) { + var id = iter.node.id; + if ((id % 2) && id > this._maxNodeId) + this._maxNodeId = id; + } + return this._maxNodeId; + }, + + get rootNodeIndex() + { + return this._rootNodeIndex; + }, + + get totalSize() + { + return this.rootNode.retainedSize; + }, + + _retainersForNode: function(node) + { + if (!this._retainers) + this._buildRetainers(); + + var retIndexFrom = this._getRetainerIndex(node.nodeIndex); + var retIndexTo = this._getRetainerIndex(node._nextNodeIndex); + return new WebInspector.HeapSnapshotArraySlice(this, "_retainers", retIndexFrom, retIndexTo); + }, + + _dominatedNodesOfNode: function(node) + { + if (!this._dominatedNodes) + this._buildDominatedNodes(); + + var dominatedIndexFrom = this._getDominatedIndex(node.nodeIndex); + var dominatedIndexTo = this._getDominatedIndex(node._nextNodeIndex); + return new WebInspector.HeapSnapshotArraySlice(this, "_dominatedNodes", dominatedIndexFrom, dominatedIndexTo); + }, + + _flagsOfNode: function(node) + { + if (!this._flags) + this._calculateFlags(); + return this._flags[node.nodeIndex]; + }, + + aggregates: function(sortedIndexes, key, filterString) + { + if (!this._aggregates) { + this._aggregates = {}; + this._aggregatesSortedFlags = {}; + } + + var aggregates = this._aggregates[key]; + if (aggregates) { + if (sortedIndexes && !this._aggregatesSortedFlags[key]) { + this._sortAggregateIndexes(aggregates); + this._aggregatesSortedFlags[key] = sortedIndexes; + } + return aggregates; + } + + var filter; + if (filterString) + filter = this._parseFilter(filterString); + + aggregates = this._buildAggregates(filter); + + if (sortedIndexes) + this._sortAggregateIndexes(aggregates); + + this._aggregatesSortedFlags[key] = sortedIndexes; + this._aggregates[key] = aggregates; + + return aggregates; + }, + + _buildReverseIndex: function(indexArrayName, backRefsArrayName, indexCallback, dataCallback) + { + if (!this._nodeIndex) + this._buildNodeIndex(); + + // Builds up two arrays: + // - "backRefsArray" is a continuous array, where each node owns an + // interval (can be empty) with corresponding back references. + // - "indexArray" is an array of indexes in the "backRefsArray" + // with the same positions as in the _nodeIndex. + var indexArray = this[indexArrayName] = new Array(this._nodeIndex.length); + for (var i = 0, l = indexArray.length; i < l; ++i) + indexArray[i] = 0; + for (var nodesIter = this._allNodes; nodesIter.hasNext(); nodesIter.next()) { + indexCallback(nodesIter.node, function (position) { ++indexArray[position]; }); + } + var backRefsCount = 0; + for (i = 0, l = indexArray.length; i < l; ++i) + backRefsCount += indexArray[i]; + var backRefsArray = this[backRefsArrayName] = new Array(backRefsCount + 1); + // Put in the first slot of each backRefsArray slice the count of entries + // that will be filled. + var backRefsPosition = 0; + for (i = 0, l = indexArray.length; i < l; ++i) { + backRefsCount = backRefsArray[backRefsPosition] = indexArray[i]; + indexArray[i] = backRefsPosition; + backRefsPosition += backRefsCount; + } + for (nodesIter = this._allNodes; nodesIter.hasNext(); nodesIter.next()) { + dataCallback(nodesIter.node, + function (backRefIndex) { return backRefIndex + (--backRefsArray[backRefIndex]); }, + function (backRefIndex, destIndex) { backRefsArray[backRefIndex] = destIndex; }); + } + }, + + _buildRetainers: function() + { + this._buildReverseIndex( + "_retainerIndex", + "_retainers", + (function (node, callback) + { + for (var edgesIter = node.edges; edgesIter.hasNext(); edgesIter.next()) + callback(this._findNodePositionInIndex(edgesIter.edge.nodeIndex)); + }).bind(this), + (function (node, indexCallback, dataCallback) + { + for (var edgesIter = node.edges; edgesIter.hasNext(); edgesIter.next()) { + var edge = edgesIter.edge; + var retIndex = this._getRetainerIndex(edge.nodeIndex); + dataCallback(indexCallback(retIndex), node.nodeIndex + this._firstEdgeOffset + edge.edgeIndex); + } + }).bind(this)); + }, + + _buildAggregates: function(filter) + { + var aggregates = {}; + for (var iter = this._allNodes; iter.hasNext(); iter.next()) { + var node = iter.node; + if (filter && !filter(node)) + continue; + if (node.type !== "native" && node.selfSize === 0) + continue; + var className = node.className; + if (className === "Document DOM tree") + continue; + if (className === "Detached DOM tree") + continue; + var nameMatters = node.type === "object" || node.type === "native"; + if (!aggregates.hasOwnProperty(className)) + aggregates[className] = { count: 0, self: 0, maxRet: 0, type: node.type, name: nameMatters ? node.name : null, idxs: [] }; + var clss = aggregates[className]; + ++clss.count; + clss.self += node.selfSize; + if (node.retainedSize > clss.maxRet) + clss.maxRet = node.retainedSize; + clss.idxs.push(node.nodeIndex); + } + // Shave off provisionally allocated space. + for (var className in aggregates) + aggregates[className].idxs = aggregates[className].idxs.slice(0); + return aggregates; + }, + + _sortAggregateIndexes: function(aggregates) + { + var nodeA = new WebInspector.HeapSnapshotNode(this); + var nodeB = new WebInspector.HeapSnapshotNode(this); + for (var clss in aggregates) + aggregates[clss].idxs.sort( + function(idxA, idxB) { + nodeA.nodeIndex = idxA; + nodeB.nodeIndex = idxB; + return nodeA.id < nodeB.id ? -1 : 1; + }); + }, + + _buildNodeIndex: function() + { + var count = this.nodeCount; + this._nodeIndex = new Array(count + 1); + count = 0; + for (var nodesIter = this._allNodes; nodesIter.hasNext(); nodesIter.next(), ++count) + this._nodeIndex[count] = nodesIter.index; + this._nodeIndex[count] = this._nodes.length; + }, + + _findNodePositionInIndex: function(index) + { + return binarySearch(index, this._nodeIndex, this._numbersComparator); + }, + + _findNearestNodeIndex: function(index) + { + var result = this._findNodePositionInIndex(index); + if (result < 0) { + result = -result - 1; + nodeIndex = this._nodeIndex[result]; + // Binary search can return either maximum lower value, or minimum higher value. + if (nodeIndex > index) + nodeIndex = this._nodeIndex[result - 1]; + } else + var nodeIndex = this._nodeIndex[result]; + return nodeIndex; + }, + + _getRetainerIndex: function(nodeIndex) + { + var nodePosition = this._findNodePositionInIndex(nodeIndex); + return this._retainerIndex[nodePosition]; + }, + + _buildDominatedNodes: function() + { + this._buildReverseIndex( + "_dominatedIndex", + "_dominatedNodes", + (function (node, callback) + { + var dominatorIndex = node.dominatorIndex; + if (dominatorIndex !== node.nodeIndex) + callback(this._findNodePositionInIndex(dominatorIndex)); + }).bind(this), + (function (node, indexCallback, dataCallback) + { + var dominatorIndex = node.dominatorIndex; + if (dominatorIndex !== node.nodeIndex) { + var dominatedIndex = this._getDominatedIndex(dominatorIndex); + dataCallback(indexCallback(dominatedIndex), node.nodeIndex); + } + }).bind(this)); + }, + + _getDominatedIndex: function(nodeIndex) + { + var nodePosition = this._findNodePositionInIndex(nodeIndex); + return this._dominatedIndex[nodePosition]; + }, + + _markInvisibleEdges: function() + { + // Mark hidden edges of global objects as invisible. + // FIXME: This is a temporary measure. Normally, we should + // really hide all hidden nodes. + for (var iter = this.rootNode.edges; iter.hasNext(); iter.next()) { + var edge = iter.edge; + if (!edge.isShortcut) + continue; + var node = edge.node; + var propNames = {}; + for (var innerIter = node.edges; innerIter.hasNext(); innerIter.next()) { + var globalObjEdge = innerIter.edge; + if (globalObjEdge.isShortcut) + propNames[globalObjEdge._nameOrIndex] = true; + } + for (innerIter.first(); innerIter.hasNext(); innerIter.next()) { + var globalObjEdge = innerIter.edge; + if (!globalObjEdge.isShortcut + && globalObjEdge.node.isHidden + && globalObjEdge._hasStringName + && (globalObjEdge._nameOrIndex in propNames)) + this._nodes[globalObjEdge._edges._start + globalObjEdge.edgeIndex + this._edgeTypeOffset] = this._edgeInvisibleType; + } + } + }, + + _numbersComparator: function(a, b) + { + return a < b ? -1 : (a > b ? 1 : 0); + }, + + _markDetachedDOMTreeNodes: function() + { + var flag = this._nodeFlags.detachedDOMTreeNode; + var nativeRoot; + for (var iter = this.rootNode.edges; iter.hasNext(); iter.next()) { + var node = iter.edge.node; + if (node.isNativeRoot) { + nativeRoot = node; + break; + } + } + + if (!nativeRoot) + return; + + for (var iter = nativeRoot.edges; iter.hasNext(); iter.next()) { + var node = iter.edge.node; + if (node.isDetachedDOMTree) { + for (var edgesIter = node.edges; edgesIter.hasNext(); edgesIter.next()) + this._flags[edgesIter.edge.node.nodeIndex] |= flag; + } + } + }, + + _markQueriableHeapObjects: function() + { + // Allow runtime properties query for objects accessible from DOMWindow objects + // via regular properties, and for DOM wrappers. Trying to access random objects + // can cause a crash due to insonsistent state of internal properties of wrappers. + var flag = this._nodeFlags.canBeQueried; + + var list = []; + for (var iter = this.rootNode.edges; iter.hasNext(); iter.next()) { + if (iter.edge.node.isDOMWindow) + list.push(iter.edge.node); + } + + while (list.length) { + var node = list.pop(); + if (this._flags[node.nodeIndex] & flag) + continue; + this._flags[node.nodeIndex] |= flag; + for (var iter = node.edges; iter.hasNext(); iter.next()) { + var edge = iter.edge; + var node = edge.node; + if (this._flags[node.nodeIndex]) + continue; + if (edge.isHidden || edge.isInvisible) + continue; + var name = edge.name; + if (!name) + continue; + if (edge.isInternal && name !== "native") + continue; + list.push(node); + } + } + }, + + _calculateFlags: function() + { + this._flags = new Array(this.nodeCount); + this._markDetachedDOMTreeNodes(); + this._markQueriableHeapObjects(); + }, + + baseSnapshotHasNode: function(baseSnapshotId, className, nodeId) + { + return this._baseNodeIds[baseSnapshotId][className].binaryIndexOf(nodeId, this._numbersComparator) !== -1; + }, + + pushBaseIds: function(baseSnapshotId, className, nodeIds) + { + if (!this._baseNodeIds) + this._baseNodeIds = []; + if (!this._baseNodeIds[baseSnapshotId]) + this._baseNodeIds[baseSnapshotId] = {}; + this._baseNodeIds[baseSnapshotId][className] = nodeIds; + }, + + createDiff: function(className) + { + return new WebInspector.HeapSnapshotsDiff(this, className); + }, + + _parseFilter: function(filter) + { + if (!filter) + return null; + var parsedFilter = eval("(function(){return " + filter + "})()"); + return parsedFilter.bind(this); + }, + + createEdgesProvider: function(nodeIndex, filter) + { + return new WebInspector.HeapSnapshotEdgesProvider(this, nodeIndex, this._parseFilter(filter)); + }, + + createRetainingEdgesProvider: function(nodeIndex, filter) + { + var node = new WebInspector.HeapSnapshotNode(this, nodeIndex); + return new WebInspector.HeapSnapshotEdgesProvider(this, nodeIndex, this._parseFilter(filter), node.retainers); + }, + + createNodesProvider: function(filter) + { + return new WebInspector.HeapSnapshotNodesProvider(this, this._parseFilter(filter)); + }, + + createNodesProviderForClass: function(className, aggregatesKey) + { + return new WebInspector.HeapSnapshotNodesProvider(this, null, this.aggregates(false, aggregatesKey)[className].idxs); + }, + + createNodesProviderForDominator: function(nodeIndex, filter) + { + var node = new WebInspector.HeapSnapshotNode(this, nodeIndex); + return new WebInspector.HeapSnapshotNodesProvider(this, this._parseFilter(filter), this._dominatedNodesOfNode(node)); + }, + + createPathFinder: function(targetNodeIndex, skipHidden) + { + return new WebInspector.HeapSnapshotPathFinder(this, targetNodeIndex, skipHidden); + }, + + updateStaticData: function() + { + return {nodeCount: this.nodeCount, rootNodeIndex: this._rootNodeIndex, totalSize: this.totalSize, uid: this.uid, nodeFlags: this._nodeFlags, maxNodeId: this.maxNodeId}; + } +}; + +WebInspector.HeapSnapshotFilteredOrderedIterator = function(iterator, filter, unfilteredIterationOrder) +{ + this._filter = filter; + this._iterator = iterator; + this._unfilteredIterationOrder = unfilteredIterationOrder; + this._iterationOrder = null; + this._position = 0; + this._currentComparator = null; + this._lastComparator = null; +} + +WebInspector.HeapSnapshotFilteredOrderedIterator.prototype = { + _createIterationOrder: function() + { + if (this._iterationOrder) + return; + if (this._unfilteredIterationOrder && !this._filter) { + this._iterationOrder = this._unfilteredIterationOrder.slice(0); + this._unfilteredIterationOrder = null; + return; + } + this._iterationOrder = []; + var iterator = this._iterator; + if (!this._unfilteredIterationOrder && !this._filter) { + for (iterator.first(); iterator.hasNext(); iterator.next()) + this._iterationOrder.push(iterator.index); + } else if (!this._unfilteredIterationOrder) { + for (iterator.first(); iterator.hasNext(); iterator.next()) { + if (this._filter(iterator.item)) + this._iterationOrder.push(iterator.index); + } + } else { + var order = this._unfilteredIterationOrder.constructor === Array ? + this._unfilteredIterationOrder : this._unfilteredIterationOrder.slice(0); + for (var i = 0, l = order.length; i < l; ++i) { + iterator.index = order[i]; + if (this._filter(iterator.item)) + this._iterationOrder.push(iterator.index); + } + this._unfilteredIterationOrder = null; + } + }, + + first: function() + { + this._position = 0; + }, + + hasNext: function() + { + return this._position < this._iterationOrder.length; + }, + + get isEmpty() + { + if (this._iterationOrder) + return !this._iterationOrder.length; + if (this._unfilteredIterationOrder && !this._filter) + return !this._unfilteredIterationOrder.length; + var iterator = this._iterator; + if (!this._unfilteredIterationOrder && !this._filter) { + iterator.first(); + return !iterator.hasNext(); + } else if (!this._unfilteredIterationOrder) { + for (iterator.first(); iterator.hasNext(); iterator.next()) + if (this._filter(iterator.item)) + return false; + } else { + var order = this._unfilteredIterationOrder.constructor === Array ? + this._unfilteredIterationOrder : this._unfilteredIterationOrder.slice(0); + for (var i = 0, l = order.length; i < l; ++i) { + iterator.index = order[i]; + if (this._filter(iterator.item)) + return false; + } + } + return true; + }, + + get item() + { + this._iterator.index = this._iterationOrder[this._position]; + return this._iterator.item; + }, + + get length() + { + this._createIterationOrder(); + return this._iterationOrder.length; + }, + + next: function() + { + ++this._position; + }, + + serializeNextItems: function(count) + { + this._createIterationOrder(); + var result = new Array(count); + if (this._lastComparator !== this._currentComparator) + this.sort(this._currentComparator, this._position, this._iterationOrder.length - 1, count); + for (var i = 0 ; i < count && this.hasNext(); ++i, this.next()) + result[i] = this._serialize(this.item); + result.length = i; + result.hasNext = this.hasNext(); + result.totalLength = this._iterationOrder.length; + return result; + }, + + sortAndRewind: function(comparator) + { + this._lastComparator = this._currentComparator; + this._currentComparator = comparator; + var result = this._lastComparator !== this._currentComparator; + if (result) + this.first(); + return result; + } +} + +WebInspector.HeapSnapshotFilteredOrderedIterator.prototype.createComparator = function(fieldNames) +{ + return {fieldName1:fieldNames[0], ascending1:fieldNames[1], fieldName2:fieldNames[2], ascending2:fieldNames[3]}; +} + +WebInspector.HeapSnapshotEdgesProvider = function(snapshot, nodeIndex, filter, iter) +{ + this.snapshot = snapshot; + var node = new WebInspector.HeapSnapshotNode(snapshot, nodeIndex); + var edgesIter = iter || new WebInspector.HeapSnapshotEdgeIterator(new WebInspector.HeapSnapshotEdge(snapshot, node.rawEdges)); + WebInspector.HeapSnapshotFilteredOrderedIterator.call(this, edgesIter, filter); +} + +WebInspector.HeapSnapshotEdgesProvider.prototype = { + _serialize: function(edge) + { + return {name: edge.name, node: WebInspector.HeapSnapshotNodesProvider.prototype._serialize(edge.node), nodeIndex: edge.nodeIndex, type: edge.type}; + }, + + sort: function(comparator, leftBound, rightBound, count) + { + var fieldName1 = comparator.fieldName1; + var fieldName2 = comparator.fieldName2; + var ascending1 = comparator.ascending1; + var ascending2 = comparator.ascending2; + + var edgeA = this._iterator.item.clone(); + var edgeB = edgeA.clone(); + var nodeA = new WebInspector.HeapSnapshotNode(this.snapshot); + var nodeB = new WebInspector.HeapSnapshotNode(this.snapshot); + + function sortByEdgeFieldName(ascending, indexA, indexB) + { + edgeA.edgeIndex = indexA; + edgeB.edgeIndex = indexB; + if (edgeB.name === "__proto__") return -1; + if (edgeA.name === "__proto__") return 1; + var result = + edgeA.hasStringName === edgeB.hasStringName ? + (edgeA.name < edgeB.name ? -1 : (edgeA.name > edgeB.name ? 1 : 0)) : + (edgeA.hasStringName ? -1 : 1); + return ascending ? result : -result; + } + + function sortByNodeField(fieldName, ascending, indexA, indexB) + { + edgeA.edgeIndex = indexA; + edgeB.edgeIndex = indexB; + nodeA.nodeIndex = edgeA.nodeIndex; + nodeB.nodeIndex = edgeB.nodeIndex; + var valueA = nodeA[fieldName]; + var valueB = nodeB[fieldName]; + var result = valueA < valueB ? -1 : (valueA > valueB ? 1 : 0); + return ascending ? result : -result; + } + + function sortByEdgeAndNode(indexA, indexB) { + var result = sortByEdgeFieldName(ascending1, indexA, indexB); + if (result === 0) + result = sortByNodeField(fieldName2, ascending2, indexA, indexB); + return result; + } + + function sortByNodeAndEdge(indexA, indexB) { + var result = sortByNodeField(fieldName1, ascending1, indexA, indexB); + if (result === 0) + result = sortByEdgeFieldName(ascending2, indexA, indexB); + return result; + } + + function sortByNodeAndNode(indexA, indexB) { + var result = sortByNodeField(fieldName1, ascending1, indexA, indexB); + if (result === 0) + result = sortByNodeField(fieldName2, ascending2, indexA, indexB); + return result; + } + + if (fieldName1 === "!edgeName") + this._iterationOrder.sortRange(sortByEdgeAndNode, leftBound, rightBound, count); + else if (fieldName2 === "!edgeName") + this._iterationOrder.sortRange(sortByNodeAndEdge, leftBound, rightBound, count); + else + this._iterationOrder.sortRange(sortByNodeAndNode, leftBound, rightBound, count); + } +}; + +WebInspector.HeapSnapshotEdgesProvider.prototype.__proto__ = WebInspector.HeapSnapshotFilteredOrderedIterator.prototype; + +WebInspector.HeapSnapshotNodesProvider = function(snapshot, filter, nodeIndexes) +{ + this.snapshot = snapshot; + WebInspector.HeapSnapshotFilteredOrderedIterator.call(this, snapshot._allNodes, filter, nodeIndexes); +} + +WebInspector.HeapSnapshotNodesProvider.prototype = { + _serialize: function(node) + { + return {id: node.id, name: node.name, nodeIndex: node.nodeIndex, retainedSize: node.retainedSize, selfSize: node.selfSize, type: node.type, flags: node.flags}; + }, + + sort: function(comparator, leftBound, rightBound, count) + { + var fieldName1 = comparator.fieldName1; + var fieldName2 = comparator.fieldName2; + var ascending1 = comparator.ascending1; + var ascending2 = comparator.ascending2; + + var nodeA = new WebInspector.HeapSnapshotNode(this.snapshot); + var nodeB = new WebInspector.HeapSnapshotNode(this.snapshot); + + function sortByNodeField(fieldName, ascending, indexA, indexB) + { + nodeA.nodeIndex = indexA; + nodeB.nodeIndex = indexB; + var valueA = nodeA[fieldName]; + var valueB = nodeB[fieldName]; + var result = valueA < valueB ? -1 : (valueA > valueB ? 1 : 0); + return ascending ? result : -result; + } + + function sortByComparator(indexA, indexB) { + var result = sortByNodeField(fieldName1, ascending1, indexA, indexB); + if (result === 0) + result = sortByNodeField(fieldName2, ascending2, indexA, indexB); + return result; + } + + this._iterationOrder.sortRange(sortByComparator, leftBound, rightBound, count); + } +}; + +WebInspector.HeapSnapshotNodesProvider.prototype.__proto__ = WebInspector.HeapSnapshotFilteredOrderedIterator.prototype; + +WebInspector.HeapSnapshotPathFinder = function(snapshot, targetNodeIndex, skipHidden) +{ + this._snapshot = snapshot; + this._maxLength = 1; + this._lengthLimit = 15; + this._targetNodeIndex = targetNodeIndex; + this._currentPath = null; + this._skipHidden = skipHidden; + this._rootChildren = this._fillRootChildren(); +} + +WebInspector.HeapSnapshotPathFinder.prototype = { + findNext: function() + { + for (var i = 0; i < 100000; ++i) { + if (!this._buildNextPath()) { + if (++this._maxLength >= this._lengthLimit) + return null; + this._currentPath = null; + if (!this._buildNextPath()) + return null; + } + if (this._isPathFound()) + return {path:this._pathToString(this._currentPath), route:this._pathToRoute(this._currentPath), len:this._currentPath.length}; + } + + return false; + }, + + updateRoots: function(filter) + { + if (filter) + filter = eval("(function(){return " + filter + "})()"); + this._rootChildren = this._fillRootChildren(filter); + this._reset(); + }, + + _reset: function() + { + this._maxLength = 1; + this._currentPath = null; + }, + + _fillRootChildren: function(filter) + { + var result = []; + for (var iter = this._snapshot.rootNode.edges; iter.hasNext(); iter.next()) { + if (!filter) { + if (!iter.edge.isShortcut) + result[iter.edge.nodeIndex] = true; + } else if (filter(iter.edge.node)) { + result[iter.edge.nodeIndex] = true; + } + } + return result; + }, + + _appendToCurrentPath: function(iter) + { + this._currentPath._cache[this._lastEdge.nodeIndex] = true; + this._currentPath.push(iter); + }, + + _removeLastFromCurrentPath: function() + { + this._currentPath.pop(); + delete this._currentPath._cache[this._lastEdge.nodeIndex]; + }, + + _hasInPath: function(nodeIndex) + { + return this._targetNodeIndex === nodeIndex + || !!this._currentPath._cache[nodeIndex]; + }, + + _isPathFound: function() + { + return this._currentPath.length === this._maxLength + && this._lastEdge.nodeIndex in this._rootChildren; + }, + + get _lastEdgeIter() + { + return this._currentPath[this._currentPath.length - 1]; + }, + + get _lastEdge() + { + return this._lastEdgeIter.item; + }, + + _skipEdge: function(edge) + { + return edge.isInvisible + || (this._skipHidden && (edge.isHidden || edge.node.isHidden)) + || edge.isWeak + || this._hasInPath(edge.nodeIndex); + }, + + _nextEdgeIter: function() + { + var iter = this._lastEdgeIter; + while (iter.hasNext() && this._skipEdge(iter.item)) + iter.next(); + return iter; + }, + + _buildNextPath: function() + { + if (this._currentPath !== null) { + var iter = this._lastEdgeIter; + while (true) { + iter.next(); + iter = this._nextEdgeIter(); + if (iter.hasNext()) + return true; + while (true) { + if (this._currentPath.length > 1) { + this._removeLastFromCurrentPath(); + iter = this._lastEdgeIter; + iter.next(); + iter = this._nextEdgeIter(); + if (iter.hasNext()) { + while (this._currentPath.length < this._maxLength) { + iter = this._nextEdgeIter(); + if (iter.hasNext()) + this._appendToCurrentPath(iter.item.node.retainers); + else + return true; + } + return true; + } + } else + return false; + } + } + } else { + var node = new WebInspector.HeapSnapshotNode(this._snapshot, this._targetNodeIndex); + this._currentPath = [node.retainers]; + this._currentPath._cache = {}; + while (this._currentPath.length < this._maxLength) { + var iter = this._nextEdgeIter(); + if (iter.hasNext()) + this._appendToCurrentPath(iter.item.node.retainers); + else + break; + } + return true; + } + }, + + _nodeToString: function(node) + { + if (node.id === 1) + return node.name; + else + return node.name + "@" + node.id; + }, + + _pathToString: function(path) + { + if (!path) + return ""; + var sPath = []; + for (var j = 0; j < path.length; ++j) + sPath.push(path[j].item.toString()); + sPath.push(this._nodeToString(path[path.length - 1].item.node)); + sPath.reverse(); + return sPath.join(""); + }, + + _pathToRoute: function(path) + { + if (!path) + return []; + var route = []; + route.push(this._targetNodeIndex); + for (var i = 0; i < path.length; ++i) + route.push(path[i].item.nodeIndex); + route.reverse(); + return route; + } +}; + +WebInspector.HeapSnapshotsDiff = function(snapshot, className) +{ + this._snapshot = snapshot; + this._className = className; +}; + +WebInspector.HeapSnapshotsDiff.prototype = { + calculate: function() + { + var aggregates = this._snapshot.aggregates(true)[this._className]; + var indexes = aggregates ? aggregates.idxs : []; + var i = 0, l = this._baseIds.length; + var j = 0, m = indexes.length; + var diff = { addedCount: 0, removedCount: 0, addedSize: 0, removedSize: 0 }; + + var nodeB = new WebInspector.HeapSnapshotNode(this._snapshot, indexes[j]); + while (i < l && j < m) { + var nodeAId = this._baseIds[i]; + if (nodeAId < nodeB.id) { + diff.removedCount++; + diff.removedSize += this._baseSelfSizes[i]; + ++i; + } else if (nodeAId > nodeB.id) { + diff.addedCount++; + diff.addedSize += nodeB.selfSize; + nodeB.nodeIndex = indexes[++j]; + } else { + ++i; + nodeB.nodeIndex = indexes[++j]; + } + } + while (i < l) { + diff.removedCount++; + diff.removedSize += this._baseSelfSizes[i]; + ++i; + } + while (j < m) { + diff.addedCount++; + diff.addedSize += nodeB.selfSize; + nodeB.nodeIndex = indexes[++j]; + } + diff.countDelta = diff.addedCount - diff.removedCount; + diff.sizeDelta = diff.addedSize - diff.removedSize; + return diff; + }, + + pushBaseIds: function(baseIds) + { + this._baseIds = baseIds; + }, + + pushBaseSelfSizes: function(baseSelfSizes) + { + this._baseSelfSizes = baseSelfSizes; + } +}; +/* HeapSnapshotProxy.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyrightdd + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.HeapSnapshotRealWorker = function() +{ + this._worker = new Worker("HeapSnapshotWorker.js"); + this._worker.addEventListener("message", this._messageReceived.bind(this), false); +} + +WebInspector.HeapSnapshotRealWorker.prototype = { + _messageReceived: function(event) + { + this.dispatchEventToListeners("message", event.data); + }, + + postMessage: function(message) + { + this._worker.postMessage(message); + }, + + terminate: function() + { + this._worker.terminate(); + } +}; + +WebInspector.HeapSnapshotRealWorker.prototype.__proto__ = WebInspector.Object.prototype; + +WebInspector.HeapSnapshotFakeWorker = function() +{ + this._dispatcher = new WebInspector.HeapSnapshotWorkerDispatcher(window, this._postMessageFromWorker.bind(this)); +} + +WebInspector.HeapSnapshotFakeWorker.prototype = { + postMessage: function(message) + { + function dispatch() + { + if (this._dispatcher) + this._dispatcher.dispatchMessage({data: message}); + } + setTimeout(dispatch.bind(this), 0); + }, + + terminate: function() + { + this._dispatcher = null; + }, + + _postMessageFromWorker: function(message) + { + function send() + { + this.dispatchEventToListeners("message", message); + } + setTimeout(send.bind(this), 0); + } +}; + +WebInspector.HeapSnapshotFakeWorker.prototype.__proto__ = WebInspector.Object.prototype; + +WebInspector.HeapSnapshotWorker = function() +{ + this._nextObjectId = 1; + this._nextCallId = 1; + this._callbacks = []; + this._previousCallbacks = []; + // There is no support for workers in Chromium DRT. + this._worker = typeof InspectorTest === "undefined" ? new WebInspector.HeapSnapshotRealWorker() : new WebInspector.HeapSnapshotFakeWorker(); + this._worker.addEventListener("message", this._messageReceived.bind(this), false); +} + +WebInspector.HeapSnapshotWorker.prototype = { + createObject: function(constructorName) + { + var proxyConstructorFunction = this._findFunction(constructorName + "Proxy"); + var objectId = this._nextObjectId++; + var proxy = new proxyConstructorFunction(this, objectId); + this._postMessage({callId: this._nextCallId++, disposition: "create", objectId: objectId, methodName: constructorName}); + return proxy; + }, + + dispose: function() + { + this._worker.terminate(); + if (this._interval) + clearInterval(this._interval); + }, + + disposeObject: function(objectId) + { + this._postMessage({callId: this._nextCallId++, disposition: "dispose", objectId: objectId}); + }, + + callGetter: function(callback, objectId, getterName) + { + var callId = this._nextCallId++; + this._callbacks[callId] = callback; + this._postMessage({callId: callId, disposition: "getter", objectId: objectId, methodName: getterName}); + }, + + callFactoryMethod: function(callback, objectId, methodName, proxyConstructorName) + { + var callId = this._nextCallId++; + var methodArguments = Array.prototype.slice.call(arguments, 4); + var newObjectId = this._nextObjectId++; + var proxyConstructorFunction = this._findFunction(proxyConstructorName); + if (callback) { + function wrapCallback(remoteResult) + { + callback(remoteResult ? new proxyConstructorFunction(this, newObjectId) : null); + } + this._callbacks[callId] = wrapCallback.bind(this); + this._postMessage({callId: callId, disposition: "factory", objectId: objectId, methodName: methodName, methodArguments: methodArguments, newObjectId: newObjectId}); + return null; + } else { + this._postMessage({callId: callId, disposition: "factory", objectId: objectId, methodName: methodName, methodArguments: methodArguments, newObjectId: newObjectId}); + return new proxyConstructorFunction(this, newObjectId); + } + }, + + callMethod: function(callback, objectId, methodName) + { + var callId = this._nextCallId++; + var methodArguments = Array.prototype.slice.call(arguments, 3); + if (callback) + this._callbacks[callId] = callback; + this._postMessage({callId: callId, disposition: "method", objectId: objectId, methodName: methodName, methodArguments: methodArguments}); + }, + + startCheckingForLongRunningCalls: function() + { + this._checkLongRunningCalls(); + this._interval = setInterval(this._checkLongRunningCalls.bind(this), 300); + }, + + _checkLongRunningCalls: function() + { + for (var callId in this._previousCallbacks) + if (!(callId in this._callbacks)) + delete this._previousCallbacks[callId]; + var hasLongRunningCalls = false; + for (callId in this._previousCallbacks) { + hasLongRunningCalls = true; + break; + } + this.dispatchEventToListeners("wait", hasLongRunningCalls); + for (callId in this._callbacks) + this._previousCallbacks[callId] = true; + }, + + _findFunction: function(name) + { + var path = name.split("."); + var result = window; + for (var i = 0; i < path.length; ++i) + result = result[path[i]]; + return result; + }, + + _messageReceived: function(event) + { + var data = event.data; + if (!this._callbacks[data.callId]) + return; + var callback = this._callbacks[data.callId]; + delete this._callbacks[data.callId]; + callback(data.result); + }, + + _postMessage: function(message) + { + this._worker.postMessage(message); + } +}; + +WebInspector.HeapSnapshotWorker.prototype.__proto__ = WebInspector.Object.prototype; + +WebInspector.HeapSnapshotProxyObject = function(worker, objectId) +{ + this._worker = worker; + this._objectId = objectId; +} + +WebInspector.HeapSnapshotProxyObject.prototype = { + _callWorker: function(workerMethodName, args) + { + args.splice(1, 0, this._objectId); + return this._worker[workerMethodName].apply(this._worker, args); + }, + + dispose: function() + { + this._worker.disposeObject(this._objectId); + }, + + disposeWorker: function() + { + this._worker.dispose(); + }, + + callFactoryMethod: function(callback, methodName, proxyConstructorName) + { + return this._callWorker("callFactoryMethod", Array.prototype.slice.call(arguments, 0)); + }, + + callGetter: function(callback, getterName) + { + return this._callWorker("callGetter", Array.prototype.slice.call(arguments, 0)); + }, + + callMethod: function(callback, methodName) + { + return this._callWorker("callMethod", Array.prototype.slice.call(arguments, 0)); + }, + + get worker() { + return this._worker; + } +}; + +WebInspector.HeapSnapshotLoaderProxy = function(worker, objectId) +{ + WebInspector.HeapSnapshotProxyObject.call(this, worker, objectId); + this._loading = false; + this._loaded = false; +} + +WebInspector.HeapSnapshotLoaderProxy.prototype = { + finishLoading: function(callback) + { + if (!this._loading) + return false; + var loadCallbacks = this._onLoadCallbacks; + loadCallbacks.splice(0, 0, callback); + delete this._onLoadCallbacks; + this._loading = false; + this._loaded = true; + function callLoadCallbacks(snapshotProxy) + { + for (var i = 0; i < loadCallbacks.length; ++i) + loadCallbacks[i](snapshotProxy); + } + function updateStaticData(snapshotProxy) + { + this.dispose(); + snapshotProxy.updateStaticData(callLoadCallbacks); + } + this.callFactoryMethod(updateStaticData.bind(this), "finishLoading", "WebInspector.HeapSnapshotProxy"); + return true; + }, + + get loaded() + { + return this._loaded; + }, + + startLoading: function(callback) + { + if (!this._loading) { + this._onLoadCallbacks = [callback]; + this._loading = true; + return true; + } else { + this._onLoadCallbacks.push(callback); + return false; + } + }, + + pushJSONChunk: function(chunk) + { + if (!this._loading) + return; + this.callMethod(null, "pushJSONChunk", chunk); + } +}; + +WebInspector.HeapSnapshotLoaderProxy.prototype.__proto__ = WebInspector.HeapSnapshotProxyObject.prototype; + +WebInspector.HeapSnapshotProxy = function(worker, objectId) +{ + WebInspector.HeapSnapshotProxyObject.call(this, worker, objectId); +} + +WebInspector.HeapSnapshotProxy.prototype = { + aggregates: function(sortedIndexes, key, filter, callback) + { + this.callMethod(callback, "aggregates", sortedIndexes, key, filter); + }, + + createDiff: function(className) + { + return this.callFactoryMethod(null, "createDiff", "WebInspector.HeapSnapshotsDiffProxy", className); + }, + + createEdgesProvider: function(nodeIndex, filter) + { + return this.callFactoryMethod(null, "createEdgesProvider", "WebInspector.HeapSnapshotProviderProxy", nodeIndex, filter); + }, + + createRetainingEdgesProvider: function(nodeIndex, filter) + { + return this.callFactoryMethod(null, "createRetainingEdgesProvider", "WebInspector.HeapSnapshotProviderProxy", nodeIndex, filter); + }, + + createNodesProvider: function(filter) + { + return this.callFactoryMethod(null, "createNodesProvider", "WebInspector.HeapSnapshotProviderProxy", filter); + }, + + createNodesProviderForClass: function(className, aggregatesKey) + { + return this.callFactoryMethod(null, "createNodesProviderForClass", "WebInspector.HeapSnapshotProviderProxy", className, aggregatesKey); + }, + + createNodesProviderForDominator: function(nodeIndex, filter) + { + return this.callFactoryMethod(null, "createNodesProviderForDominator", "WebInspector.HeapSnapshotProviderProxy", nodeIndex, filter); + }, + + createPathFinder: function(targetNodeIndex, skipHidden) + { + return this.callFactoryMethod(null, "createPathFinder", "WebInspector.HeapSnapshotPathFinderProxy", targetNodeIndex, skipHidden); + }, + + dispose: function() + { + this.disposeWorker(); + }, + + finishLoading: function() + { + return false; + }, + + get loaded() + { + return !!this._objectId; + }, + + get maxNodeId() + { + return this._staticData.maxNodeId; + }, + + get nodeCount() + { + return this._staticData.nodeCount; + }, + + nodeFieldValuesByIndex: function(fieldName, indexes, callback) + { + this.callMethod(callback, "nodeFieldValuesByIndex", fieldName, indexes); + }, + + get nodeFlags() + { + return this._staticData.nodeFlags; + }, + + pushBaseIds: function(snapshotId, className, nodeIds) + { + this.callMethod(null, "pushBaseIds", snapshotId, className, nodeIds); + }, + + get rootNodeIndex() + { + return this._staticData.rootNodeIndex; + }, + + updateStaticData: function(callback) + { + function dataReceived(staticData) + { + this._staticData = staticData; + callback(this); + } + this.callMethod(dataReceived.bind(this), "updateStaticData"); + }, + + startLoading: function(callback) + { + setTimeout(callback.bind(null, this), 0); + return false; + }, + + get totalSize() + { + return this._staticData.totalSize; + }, + + get uid() + { + return this._staticData.uid; + } +}; + +WebInspector.HeapSnapshotProxy.prototype.__proto__ = WebInspector.HeapSnapshotProxyObject.prototype; + +WebInspector.HeapSnapshotProviderProxy = function(worker, objectId) +{ + WebInspector.HeapSnapshotProxyObject.call(this, worker, objectId); +} + +WebInspector.HeapSnapshotProviderProxy.prototype = { + isEmpty: function(callback) + { + this.callGetter(callback, "isEmpty"); + }, + + serializeNextItems: function(count, callback) + { + this.callMethod(callback, "serializeNextItems", count); + }, + + sortAndRewind: function(comparator, callback) + { + this.callMethod(callback, "sortAndRewind", comparator); + } +}; + +WebInspector.HeapSnapshotProviderProxy.prototype.__proto__ = WebInspector.HeapSnapshotProxyObject.prototype; + +WebInspector.HeapSnapshotPathFinderProxy = function(worker, objectId) +{ + WebInspector.HeapSnapshotProxyObject.call(this, worker, objectId); +} + +WebInspector.HeapSnapshotPathFinderProxy.prototype = { + findNext: function(callback) + { + this.callMethod(callback, "findNext"); + }, + + updateRoots: function(filter) + { + this.callMethod(null, "updateRoots", filter); + } +}; + +WebInspector.HeapSnapshotPathFinderProxy.prototype.__proto__ = WebInspector.HeapSnapshotProxyObject.prototype; + +WebInspector.HeapSnapshotsDiffProxy = function(worker, objectId) +{ + WebInspector.HeapSnapshotProxyObject.call(this, worker, objectId); +} + +WebInspector.HeapSnapshotsDiffProxy.prototype = { + calculate: function(callback) + { + this.callMethod(callback, "calculate"); + }, + + pushBaseIds: function(baseIds) + { + this.callMethod(null, "pushBaseIds", baseIds); + }, + + pushBaseSelfSizes: function(baseSelfSizes) + { + this.callMethod(null, "pushBaseSelfSizes", baseSelfSizes); + } +}; + +WebInspector.HeapSnapshotsDiffProxy.prototype.__proto__ = WebInspector.HeapSnapshotProxyObject.prototype; +/* HeapSnapshotWorkerDispatcher.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.HeapSnapshotWorkerDispatcher = function(globalObject, postMessage) +{ + this._objects = []; + this._global = globalObject; + this._postMessage = postMessage; +} + +WebInspector.HeapSnapshotWorkerDispatcher.prototype = { + _findFunction: function(name) + { + var path = name.split("."); + var result = this._global; + for (var i = 0; i < path.length; ++i) + result = result[path[i]]; + return result; + }, + + dispatchMessage: function(event) + { + var data = event.data; + switch (data.disposition) { + case "create": { + var constructorFunction = this._findFunction(data.methodName); + this._objects[data.objectId] = new constructorFunction(); + this._postMessage({callId: data.callId}); + break; + } + case "dispose": { + delete this._objects[data.objectId]; + this._postMessage({callId: data.callId}); + break; + } + case "getter": { + var object = this._objects[data.objectId]; + var result = object[data.methodName]; + this._postMessage({callId: data.callId, result: result}); + break; + } + case "factory": { + var object = this._objects[data.objectId]; + var result = object[data.methodName].apply(object, data.methodArguments); + if (result) + this._objects[data.newObjectId] = result; + this._postMessage({callId: data.callId, result: !!result}); + break; + } + case "method": { + var object = this._objects[data.objectId]; + var result = object[data.methodName].apply(object, data.methodArguments); + this._postMessage({callId: data.callId, result: result}); + break; + } + } + } +}; +/* DetailedHeapshotGridNodes.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.HeapSnapshotGridNode = function(tree, hasChildren) +{ + WebInspector.DataGridNode.call(this, null, hasChildren); + this._defaultPopulateCount = tree._defaultPopulateCount; + this._provider = null; + this.addEventListener("populate", this._populate, this); +} + +WebInspector.HeapSnapshotGridNode.prototype = { + createCell: function(columnIdentifier) + { + var cell = WebInspector.DataGridNode.prototype.createCell.call(this, columnIdentifier); + if (this._searchMatched) + cell.addStyleClass("highlight"); + return cell; + }, + + dispose: function() + { + if (this._provider) + this._provider.dispose(); + for (var node = this.children[0]; node; node = node.traverseNextNode(true, this, true)) + if (node.dispose) + node.dispose(); + }, + + hasHoverMessage: false, + + queryObjectContent: function(callback) + { + }, + + _populate: function(event) + { + this.removeEventListener("populate", this._populate, this); + function sorted(ignored) + { + this.populateChildren(); + } + this._provider.sortAndRewind(this.comparator(), sorted.bind(this)); + }, + + populateChildren: function(provider, howMany, atIndex, afterPopulate, suppressNotifyAboutCompletion) + { + if (!howMany && provider) { + howMany = provider.instanceCount; + provider.instanceCount = 0; + } + provider = provider || this._provider; + if (!("instanceCount" in provider)) + provider.instanceCount = 0; + howMany = howMany || this._defaultPopulateCount; + atIndex = atIndex || this.children.length; + var haveSavedChildren = !!this._savedChildren; + if (haveSavedChildren) { + haveSavedChildren = false; + for (var c in this._savedChildren) { + haveSavedChildren = true; + break; + } + } + + var part = 0; + function callSerialize() + { + if (part >= howMany) + return; + part += this._defaultPopulateCount; + provider.serializeNextItems(this._defaultPopulateCount, childrenRetrieved.bind(this)); + } + function childrenRetrieved(items) + { + var length = items.totalLength; + for (var i = 0, l = items.length; i < l; ++i) { + var item = items[i]; + if (haveSavedChildren) { + var hash = this._childHashForEntity(item); + if (hash in this._savedChildren) { + this.insertChild(this._savedChildren[hash], atIndex++); + continue; + } + } + this.insertChild(this._createChildNode(item, provider, this), atIndex++); + } + provider.instanceCount += items.length; + if (part < howMany) { + setTimeout(callSerialize.bind(this), 0); + return; + } + + if (items.hasNext) + this.insertChild(new WebInspector.ShowMoreDataGridNode(this.populateChildren.bind(this, provider), this._defaultPopulateCount, length), atIndex++); + if (afterPopulate) + afterPopulate(); + if (!suppressNotifyAboutCompletion) { + function notify() + { + this.dispatchEventToListeners("populate complete"); + } + setTimeout(notify.bind(this), 0); + } + } + setTimeout(callSerialize.bind(this), 0); + }, + + _saveChildren: function() + { + this._savedChildren = {}; + for (var i = 0, childrenCount = this.children.length; i < childrenCount; ++i) { + var child = this.children[i]; + if (child.expanded) + this._savedChildren[this._childHashForNode(child)] = child; + } + }, + + sort: function() + { + this.dataGrid.recursiveSortingEnter(); + function afterSort(sorted) + { + if (!sorted) { + this.dataGrid.recursiveSortingLeave(); + return; + } + this._saveChildren(); + this.removeChildren(); + + function afterPopulate() + { + for (var i = 0, l = this.children.length; i < l; ++i) { + var child = this.children[i]; + if (child.expanded) + child.sort(); + } + this.dataGrid.recursiveSortingLeave(); + } + this.populateChildren(this._provider, null, null, afterPopulate.bind(this)); + } + this._provider.sortAndRewind(this.comparator(), afterSort.bind(this)); + } +}; + +WebInspector.HeapSnapshotGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype; + +WebInspector.HeapSnapshotGenericObjectNode = function(tree, node) +{ + WebInspector.HeapSnapshotGridNode.call(this, tree, false); + this._name = node.name; + this._type = node.type; + this._shallowSize = node.selfSize; + this._retainedSize = node.retainedSize; + this.snapshotNodeId = node.id; + this.snapshotNodeIndex = node.nodeIndex; + if (this._type === "string") + this.hasHoverMessage = true; + else if (this._type === "object" && this.isDOMWindow(this._name)) { + this._name = this.shortenWindowURL(this._name, false); + this.hasHoverMessage = true; + } else if (node.flags & tree.snapshot.nodeFlags.canBeQueried) + this.hasHoverMessage = true; + if (node.flags & tree.snapshot.nodeFlags.detachedDOMTreeNode) + this.detachedDOMTreeNode = true; +}; + +WebInspector.HeapSnapshotGenericObjectNode.prototype = { + createCell: function(columnIdentifier) + { + var cell = columnIdentifier !== "object" ? WebInspector.DataGridNode.prototype.createCell.call(this, columnIdentifier) : this._createObjectCell(); + if (this._searchMatched) + cell.addStyleClass("highlight"); + return cell; + }, + + _createObjectCell: function() + { + var cell = document.createElement("td"); + cell.className = "object-column"; + var div = document.createElement("div"); + div.className = "source-code event-properties"; + div.style.overflow = "hidden"; + var data = this.data["object"]; + if (this._prefixObjectCell) + this._prefixObjectCell(div, data); + var valueSpan = document.createElement("span"); + valueSpan.className = "value console-formatted-" + data.valueStyle; + valueSpan.textContent = data.value; + div.appendChild(valueSpan); + cell.appendChild(div); + cell.addStyleClass("disclosure"); + if (this.depth) + cell.style.setProperty("padding-left", (this.depth * this.dataGrid.indentWidth) + "px"); + return cell; + }, + + get _countPercent() + { + return this._count / this.dataGrid.snapshot.nodeCount * 100.0; + }, + + get data() + { + var data = this._emptyData(); + + var value = this._name; + var valueStyle = "object"; + switch (this._type) { + case "string": + value = "\"" + value + "\""; + valueStyle = "string"; + break; + case "regexp": + value = "/" + value + "/"; + valueStyle = "string"; + break; + case "closure": + value = "function " + value + "()"; + valueStyle = "function"; + break; + case "number": + valueStyle = "number"; + break; + case "hidden": + valueStyle = "null"; + break; + case "array": + if (!value) + value = "[]"; + else + value += " []"; + break; + }; + if (this.hasHoverMessage) + valueStyle += " highlight"; + if (this.detachedDOMTreeNode) + valueStyle += " detached-dom-tree-node"; + data["object"] = { valueStyle: valueStyle, value: value + " @" + this.snapshotNodeId }; + + var view = this.dataGrid.snapshotView; + data["shallowSize"] = view.showShallowSizeAsPercent ? WebInspector.UIString("%.2f%%", this._shallowSizePercent) : Number.bytesToString(this._shallowSize); + data["retainedSize"] = view.showRetainedSizeAsPercent ? WebInspector.UIString("%.2f%%", this._retainedSizePercent) : Number.bytesToString(this._retainedSize); + + return this._enhanceData ? this._enhanceData(data) : data; + }, + + queryObjectContent: function(callback) + { + if (this._type === "string") + callback(WebInspector.RemoteObject.fromPrimitiveValue(this._name)); + else { + function formatResult(error, object) + { + if (!error && object.type) + callback(WebInspector.RemoteObject.fromPayload(object), !!error); + else + callback(WebInspector.RemoteObject.fromPrimitiveValue(WebInspector.UIString("Not available"))); + } + ProfilerAgent.getObjectByHeapObjectId(this.snapshotNodeId, formatResult); + } + }, + + get _retainedSizePercent() + { + return this._retainedSize / this.dataGrid.snapshot.totalSize * 100.0; + }, + + get _shallowSizePercent() + { + return this._shallowSize / this.dataGrid.snapshot.totalSize * 100.0; + }, + + _updateHasChildren: function() + { + function isEmptyCallback(isEmpty) + { + this.hasChildren = !isEmpty; + } + this._provider.isEmpty(isEmptyCallback.bind(this)); + }, + + isDOMWindow: function(fullName) + { + return fullName.substr(0, 9) === "DOMWindow"; + }, + + shortenWindowURL: function(fullName, hasObjectId) + { + var startPos = fullName.indexOf("/"); + var endPos = hasObjectId ? fullName.indexOf("@") : fullName.length; + if (startPos !== -1 && endPos !== -1) { + var fullURL = fullName.substring(startPos + 1, endPos).trimLeft(); + var url = fullURL.trimURL(); + if (url.length > 40) + url = url.trimMiddle(40); + return fullName.substr(0, startPos + 2) + url + fullName.substr(endPos); + } else + return fullName; + } +} + +WebInspector.HeapSnapshotGenericObjectNode.prototype.__proto__ = WebInspector.HeapSnapshotGridNode.prototype; + +WebInspector.HeapSnapshotObjectNode = function(tree, isFromBaseSnapshot, edge) +{ + WebInspector.HeapSnapshotGenericObjectNode.call(this, tree, edge.node); + this._referenceName = edge.name; + this._referenceType = edge.type; + this._isFromBaseSnapshot = isFromBaseSnapshot; + this._provider = this._createProvider(!isFromBaseSnapshot ? tree.snapshot : tree.baseSnapshot, edge.nodeIndex, tree); + this._updateHasChildren(); +} + +WebInspector.HeapSnapshotObjectNode.prototype = { + _createChildNode: function(item) + { + return new WebInspector.HeapSnapshotObjectNode(this.dataGrid, this._isFromBaseSnapshot, item); + }, + + _createProvider: function(snapshot, nodeIndex, tree) + { + var showHiddenData = WebInspector.settings.showHeapSnapshotObjectsHiddenProperties.get(); + var filter = "function(edge) {" + + " return !edge.isInvisible" + + " && (" + showHiddenData + " || (!edge.isHidden && !edge.node.isHidden));" + + "}"; + if (tree.showRetainingEdges) + return snapshot.createRetainingEdgesProvider(nodeIndex, filter); + else + return snapshot.createEdgesProvider(nodeIndex, filter); + }, + + _childHashForEntity: function(edge) + { + return edge.type + "#" + edge.name; + }, + + _childHashForNode: function(childNode) + { + return childNode._referenceType + "#" + childNode._referenceName; + }, + + comparator: function() + { + var sortAscending = this.dataGrid.sortOrder === "ascending"; + var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier; + var sortFields = { + object: ["!edgeName", sortAscending, "retainedSize", false], + count: ["!edgeName", true, "retainedSize", false], + shallowSize: ["selfSize", sortAscending, "!edgeName", true], + retainedSize: ["retainedSize", sortAscending, "!edgeName", true] + }[sortColumnIdentifier] || ["!edgeName", true, "retainedSize", false]; + return WebInspector.HeapSnapshotFilteredOrderedIterator.prototype.createComparator(sortFields); + }, + + _emptyData: function() + { + return {count:"", addedCount: "", removedCount: "", countDelta:"", addedSize: "", removedSize: "", sizeDelta: ""}; + }, + + _enhanceData: function(data) + { + var name = this._referenceName; + if (name === "") name = "(empty)"; + var nameClass = "name"; + switch (this._referenceType) { + case "context": + nameClass = "console-formatted-number"; + break; + case "internal": + case "hidden": + nameClass = "console-formatted-null"; + break; + } + data["object"].nameClass = nameClass; + data["object"].name = name; + return data; + }, + + _prefixObjectCell: function(div, data) + { + var nameSpan = document.createElement("span"); + nameSpan.className = data.nameClass; + nameSpan.textContent = data.name; + var separatorSpan = document.createElement("span"); + separatorSpan.className = "separator"; + separatorSpan.textContent = ": "; + div.appendChild(nameSpan); + div.appendChild(separatorSpan); + } +} + +WebInspector.HeapSnapshotObjectNode.prototype.__proto__ = WebInspector.HeapSnapshotGenericObjectNode.prototype; + +WebInspector.HeapSnapshotInstanceNode = function(tree, baseSnapshot, snapshot, node) +{ + WebInspector.HeapSnapshotGenericObjectNode.call(this, tree, node); + this._isDeletedNode = !!baseSnapshot; + this._provider = this._createProvider(baseSnapshot || snapshot, node.nodeIndex); + this._updateHasChildren(); +}; + +WebInspector.HeapSnapshotInstanceNode.prototype = { + _createChildNode: function(item) + { + return new WebInspector.HeapSnapshotObjectNode(this.dataGrid, this._isDeletedNode, item); + }, + + _createProvider: function(snapshot, nodeIndex) + { + var showHiddenData = WebInspector.DetailedHeapshotView.prototype.showHiddenData; + return snapshot.createEdgesProvider( + nodeIndex, + "function(edge) {" + + " return !edge.isInvisible" + + " && (" + showHiddenData + " || (!edge.isHidden && !edge.node.isHidden));" + + "}"); + }, + + _childHashForEntity: function(edge) + { + return edge.type + "#" + edge.name; + }, + + _childHashForNode: function(childNode) + { + return childNode._referenceType + "#" + childNode._referenceName; + }, + + comparator: function() + { + var sortAscending = this.dataGrid.sortOrder === "ascending"; + var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier; + var sortFields = { + object: ["!edgeName", sortAscending, "retainedSize", false], + count: ["!edgeName", true, "retainedSize", false], + addedSize: ["selfSize", sortAscending, "!edgeName", true], + removedSize: ["selfSize", sortAscending, "!edgeName", true], + shallowSize: ["selfSize", sortAscending, "!edgeName", true], + retainedSize: ["retainedSize", sortAscending, "!edgeName", true] + }[sortColumnIdentifier] || ["!edgeName", true, "retainedSize", false]; + return WebInspector.HeapSnapshotFilteredOrderedIterator.prototype.createComparator(sortFields); + }, + + _emptyData: function() + { + return {count:"", countDelta:"", sizeDelta: ""}; + }, + + _enhanceData: function(data) + { + if (this._isDeletedNode) { + data["addedCount"] = ""; + data["addedSize"] = ""; + data["removedCount"] = "\u2022"; + data["removedSize"] = Number.bytesToString(this._shallowSize); + } else { + data["addedCount"] = "\u2022"; + data["addedSize"] = Number.bytesToString(this._shallowSize); + data["removedCount"] = ""; + data["removedSize"] = ""; + } + return data; + }, + + get isDeletedNode() + { + return this._isDeletedNode; + } +} + +WebInspector.HeapSnapshotInstanceNode.prototype.__proto__ = WebInspector.HeapSnapshotGenericObjectNode.prototype; + +WebInspector.HeapSnapshotConstructorNode = function(tree, className, aggregate, aggregatesKey) +{ + WebInspector.HeapSnapshotGridNode.call(this, tree, aggregate.count > 0); + this._name = className; + this._count = aggregate.count; + this._shallowSize = aggregate.self; + this._retainedSize = aggregate.maxRet; + this._provider = this._createNodesProvider(tree.snapshot, className, aggregatesKey); +} + +WebInspector.HeapSnapshotConstructorNode.prototype = { + _createChildNode: function(item) + { + return new WebInspector.HeapSnapshotInstanceNode(this.dataGrid, null, this.dataGrid.snapshot, item); + }, + + _createNodesProvider: function(snapshot, className, aggregatesKey) + { + return snapshot.createNodesProviderForClass(className, aggregatesKey); + }, + + comparator: function() + { + var sortAscending = this.dataGrid.sortOrder === "ascending"; + var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier; + var sortFields = { + object: ["id", sortAscending, "retainedSize", false], + count: ["id", true, "retainedSize", false], + shallowSize: ["selfSize", sortAscending, "id", true], + retainedSize: ["retainedSize", sortAscending, "id", true] + }[sortColumnIdentifier]; + return WebInspector.HeapSnapshotFilteredOrderedIterator.prototype.createComparator(sortFields); + }, + + _childHashForEntity: function(node) + { + return node.id; + }, + + _childHashForNode: function(childNode) + { + return childNode.snapshotNodeId; + }, + + get data() + { + var data = {object: this._name, count: this._count}; + var view = this.dataGrid.snapshotView; + data["count"] = view.showCountAsPercent ? WebInspector.UIString("%.2f%%", this._countPercent) : this._count; + data["shallowSize"] = view.showShallowSizeAsPercent ? WebInspector.UIString("%.2f%%", this._shallowSizePercent) : Number.bytesToString(this._shallowSize); + data["retainedSize"] = "> " + (view.showRetainedSizeAsPercent ? WebInspector.UIString("%.2f%%", this._retainedSizePercent) : Number.bytesToString(this._retainedSize)); + return data; + }, + + get _countPercent() + { + return this._count / this.dataGrid.snapshot.nodeCount * 100.0; + }, + + get _retainedSizePercent() + { + return this._retainedSize / this.dataGrid.snapshot.totalSize * 100.0; + }, + + get _shallowSizePercent() + { + return this._shallowSize / this.dataGrid.snapshot.totalSize * 100.0; + } +}; + +WebInspector.HeapSnapshotConstructorNode.prototype.__proto__ = WebInspector.HeapSnapshotGridNode.prototype; + +WebInspector.HeapSnapshotIteratorsTuple = function(it1, it2) +{ + this._it1 = it1; + this._it2 = it2; +} + +WebInspector.HeapSnapshotIteratorsTuple.prototype = { + dispose: function() + { + this._it1.dispose(); + this._it2.dispose(); + }, + + sortAndRewind: function(comparator, callback) + { + function afterSort(ignored) + { + this._it2.sortAndRewind(comparator, callback); + } + this._it1.sortAndRewind(comparator, afterSort.bind(this)); + } +}; + +WebInspector.HeapSnapshotDiffNode = function(tree, className, baseAggregate, aggregate) +{ + WebInspector.HeapSnapshotGridNode.call(this, tree, true); + this._name = className; + this._baseIndexes = baseAggregate ? baseAggregate.idxs : []; + this._indexes = aggregate ? aggregate.idxs : []; + this._provider = this._createNodesProvider(tree.baseSnapshot, tree.snapshot, aggregate ? aggregate.type : baseAggregate.type, className); +} + +WebInspector.HeapSnapshotDiffNode.prototype = { + calculateDiff: function(dataGrid, callback) + { + var diff = dataGrid.snapshot.createDiff(this._name); + + function diffCalculated(diffResult) + { + diff.dispose(); + this._addedCount = diffResult.addedCount; + this._removedCount = diffResult.removedCount; + this._countDelta = diffResult.countDelta; + this._addedSize = diffResult.addedSize; + this._removedSize = diffResult.removedSize; + this._sizeDelta = diffResult.sizeDelta; + this._baseIndexes = null; + this._indexes = null; + callback(this._addedSize === 0 && this._removedSize === 0); + } + function baseSelfSizesReceived(baseSelfSizes) + { + diff.pushBaseSelfSizes(baseSelfSizes); + diff.calculate(diffCalculated.bind(this)); + } + function baseIdsReceived(baseIds) + { + diff.pushBaseIds(baseIds); + dataGrid.snapshot.pushBaseIds(dataGrid.baseSnapshot.uid, this._name, baseIds); + dataGrid.baseSnapshot.nodeFieldValuesByIndex("selfSize", this._baseIndexes, baseSelfSizesReceived.bind(this)); + } + function idsReceived(ids) + { + dataGrid.baseSnapshot.pushBaseIds(dataGrid.snapshot.uid, this._name, ids); + } + dataGrid.baseSnapshot.nodeFieldValuesByIndex("id", this._baseIndexes, baseIdsReceived.bind(this)); + dataGrid.snapshot.nodeFieldValuesByIndex("id", this._indexes, idsReceived.bind(this)); + }, + + _createChildNode: function(item, provider) + { + if (provider === this._provider._it1) + return new WebInspector.HeapSnapshotInstanceNode(this.dataGrid, null, provider.snapshot, item); + else + return new WebInspector.HeapSnapshotInstanceNode(this.dataGrid, provider.snapshot, null, item); + }, + + _createNodesProvider: function(baseSnapshot, snapshot, nodeType, nodeClassName) + { + var className = this._name; + return new WebInspector.HeapSnapshotIteratorsTuple( + createProvider(snapshot, baseSnapshot), createProvider(baseSnapshot, snapshot)); + + function createProvider(snapshot, otherSnapshot) + { + var otherSnapshotId = otherSnapshot.uid; + var provider = snapshot.createNodesProvider( + "function (node) {" + + " return node.type === \"" + nodeType + "\" " + + (nodeClassName !== null ? "&& node.className === \"" + nodeClassName + "\"" : "") + + " && !this.baseSnapshotHasNode(" + otherSnapshotId + ", \"" + className + "\", node.id);" + + "}"); + provider.snapshot = snapshot; + return provider; + } + }, + + _childHashForEntity: function(node) + { + return node.id; + }, + + _childHashForNode: function(childNode) + { + return childNode.snapshotNodeId; + }, + + comparator: function() + { + var sortAscending = this.dataGrid.sortOrder === "ascending"; + var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier; + var sortFields = { + object: ["id", sortAscending, "selfSize", false], + addedCount: ["selfSize", sortAscending, "id", true], + removedCount: ["selfSize", sortAscending, "id", true], + countDelta: ["selfSize", sortAscending, "id", true], + addedSize: ["selfSize", sortAscending, "id", true], + removedSize: ["selfSize", sortAscending, "id", true], + sizeDelta: ["selfSize", sortAscending, "id", true] + }[sortColumnIdentifier]; + return WebInspector.HeapSnapshotFilteredOrderedIterator.prototype.createComparator(sortFields); + }, + + populateChildren: function(provider, howMany, atIndex, afterPopulate) + { + if (!provider && !howMany) { + var firstProviderPopulated = function() + { + WebInspector.HeapSnapshotGridNode.prototype.populateChildren.call(this, this._provider._it2, this._defaultPopulateCount, atIndex, afterPopulate); + }; + WebInspector.HeapSnapshotGridNode.prototype.populateChildren.call(this, this._provider._it1, this._defaultPopulateCount, atIndex, firstProviderPopulated.bind(this), true); + } else if (!howMany) { + var firstProviderPopulated = function() + { + WebInspector.HeapSnapshotGridNode.prototype.populateChildren.call(this, this._provider._it2, null, atIndex, afterPopulate); + }; + WebInspector.HeapSnapshotGridNode.prototype.populateChildren.call(this, this._provider._it1, null, atIndex, firstProviderPopulated.bind(this), true); + } else + WebInspector.HeapSnapshotGridNode.prototype.populateChildren.call(this, provider, howMany, atIndex, afterPopulate); + }, + + _signForDelta: function(delta) + { + if (delta === 0) + return ""; + if (delta > 0) + return "+"; + else + return "\u2212"; // Math minus sign, same width as plus. + }, + + get data() + { + var data = {object: this._name}; + + data["addedCount"] = this._addedCount; + data["removedCount"] = this._removedCount; + data["countDelta"] = WebInspector.UIString("%s%d", this._signForDelta(this._countDelta), Math.abs(this._countDelta)); + data["addedSize"] = Number.bytesToString(this._addedSize); + data["removedSize"] = Number.bytesToString(this._removedSize); + data["sizeDelta"] = WebInspector.UIString("%s%s", this._signForDelta(this._sizeDelta), Number.bytesToString(Math.abs(this._sizeDelta))); + + return data; + } +}; + +WebInspector.HeapSnapshotDiffNode.prototype.__proto__ = WebInspector.HeapSnapshotGridNode.prototype; + +WebInspector.HeapSnapshotDominatorObjectNode = function(tree, node) +{ + WebInspector.HeapSnapshotGenericObjectNode.call(this, tree, node); + this._provider = this._createProvider(tree.snapshot, node.nodeIndex); + this._updateHasChildren(); +}; + +WebInspector.HeapSnapshotDominatorObjectNode.prototype = { + _createChildNode: function(item) + { + return new WebInspector.HeapSnapshotDominatorObjectNode(this.dataGrid, item); + }, + + _createProvider: function(snapshot, nodeIndex) + { + var showHiddenData = WebInspector.DetailedHeapshotView.prototype.showHiddenData; + return snapshot.createNodesProviderForDominator(nodeIndex, + "function (node) {" + + " return " + showHiddenData + " || !node.isHidden;" + + "}"); + }, + + _childHashForEntity: function(node) + { + return node.id; + }, + + _childHashForNode: function(childNode) + { + return childNode.snapshotNodeId; + }, + + comparator: function() + { + var sortAscending = this.dataGrid.sortOrder === "ascending"; + var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier; + var sortFields = { + object: ["id", sortAscending, "retainedSize", false], + shallowSize: ["selfSize", sortAscending, "id", true], + retainedSize: ["retainedSize", sortAscending, "id", true] + }[sortColumnIdentifier]; + return WebInspector.HeapSnapshotFilteredOrderedIterator.prototype.createComparator(sortFields); + }, + + _emptyData: function() + { + return {}; + } +}; + +WebInspector.HeapSnapshotDominatorObjectNode.prototype.__proto__ = WebInspector.HeapSnapshotGenericObjectNode.prototype; + +function MixInSnapshotNodeFunctions(sourcePrototype, targetPrototype) +{ + targetPrototype._childHashForEntity = sourcePrototype._childHashForEntity; + targetPrototype._childHashForNode = sourcePrototype._childHashForNode; + targetPrototype.comparator = sourcePrototype.comparator; + targetPrototype._createChildNode = sourcePrototype._createChildNode; + targetPrototype._createProvider = sourcePrototype._createProvider; + targetPrototype.dispose = sourcePrototype.dispose; + targetPrototype.populateChildren = sourcePrototype.populateChildren; + targetPrototype._saveChildren = sourcePrototype._saveChildren; + targetPrototype.sort = sourcePrototype.sort; +} +/* DetailedHeapshotView.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.HeapSnapshotSortableDataGrid = function(columns) +{ + WebInspector.DataGrid.call(this, columns); + this.addEventListener("sorting changed", this.sortingChanged, this); +} + +WebInspector.HeapSnapshotSortableDataGrid.prototype = { + dispose: function() + { + for (var i = 0, l = this.children.length; i < l; ++i) + this.children[i].dispose(); + }, + + resetSortingCache: function() + { + delete this._lastSortColumnIdentifier; + delete this._lastSortAscending; + }, + + sortingChanged: function() + { + var sortAscending = this.sortOrder === "ascending"; + var sortColumnIdentifier = this.sortColumnIdentifier; + if (this._lastSortColumnIdentifier === sortColumnIdentifier && this._lastSortAscending === sortAscending) + return; + this._lastSortColumnIdentifier = sortColumnIdentifier; + this._lastSortAscending = sortAscending; + var sortFields = this._sortFields(sortColumnIdentifier, sortAscending); + + function SortByTwoFields(nodeA, nodeB) + { + var field1 = nodeA[sortFields[0]]; + var field2 = nodeB[sortFields[0]]; + var result = field1 < field2 ? -1 : (field1 > field2 ? 1 : 0); + if (!sortFields[1]) + result = -result; + if (result !== 0) + return result; + field1 = nodeA[sortFields[2]]; + field2 = nodeB[sortFields[2]]; + result = field1 < field2 ? -1 : (field1 > field2 ? 1 : 0); + if (!sortFields[3]) + result = -result; + return result; + } + this._performSorting(SortByTwoFields); + }, + + _performSorting: function(sortFunction) + { + this.recursiveSortingEnter(); + var children = this.children; + this.removeChildren(); + children.sort(sortFunction); + for (var i = 0, l = children.length; i < l; ++i) { + var child = children[i]; + this.appendChild(child); + if (child.expanded) + child.sort(); + } + this.recursiveSortingLeave(); + }, + + recursiveSortingEnter: function() + { + if (!("_recursiveSortingDepth" in this)) + this._recursiveSortingDepth = 1; + else + ++this._recursiveSortingDepth; + }, + + recursiveSortingLeave: function() + { + if (!("_recursiveSortingDepth" in this)) + return; + if (!--this._recursiveSortingDepth) { + delete this._recursiveSortingDepth; + this.dispatchEventToListeners("sorting complete"); + } + } +}; + +WebInspector.HeapSnapshotSortableDataGrid.prototype.__proto__ = WebInspector.DataGrid.prototype; + +WebInspector.HeapSnapshotContainmentDataGrid = function() +{ + var columns = { + object: { title: WebInspector.UIString("Object"), disclosure: true, sortable: true, sort: "ascending" }, + shallowSize: { title: WebInspector.UIString("Shallow Size"), width: "90px", sortable: true }, + retainedSize: { title: WebInspector.UIString("Retained Size"), width: "90px", sortable: true } + }; + WebInspector.HeapSnapshotSortableDataGrid.call(this, columns); +} + +WebInspector.HeapSnapshotContainmentDataGrid.prototype = { + _defaultPopulateCount: 100, + + expandRoute: function(route) + { + function nextStep(parent, hopIndex) + { + if (hopIndex >= route.length) { + parent.element.scrollIntoViewIfNeeded(true); + parent.select(); + return; + } + var nodeIndex = route[hopIndex]; + for (var i = 0, l = parent.children.length; i < l; ++i) { + var child = parent.children[i]; + if (child.snapshotNodeIndex === nodeIndex) { + if (child.expanded) + nextStep(child, hopIndex + 1); + else { + function afterExpand() + { + child.removeEventListener("populate complete", afterExpand, null); + var lastChild = child.children[child.children.length - 1]; + if (!lastChild.showAll) + nextStep(child, hopIndex + 1); + else { + child.addEventListener("populate complete", afterExpand, null); + lastChild.showAll.click(); + } + } + child.addEventListener("populate complete", afterExpand, null); + child.expand(); + } + break; + } + } + } + nextStep(this, 0); + }, + + setDataSource: function(snapshotView, snapshot, nodeIndex) + { + this.snapshotView = snapshotView; + this.snapshot = snapshot; + this.snapshotNodeIndex = nodeIndex || this.snapshot.rootNodeIndex; + this._provider = this._createProvider(snapshot, this.snapshotNodeIndex, this); + this.sort(); + }, + + sortingChanged: function() + { + this.sort(); + } +}; + +MixInSnapshotNodeFunctions(WebInspector.HeapSnapshotObjectNode.prototype, WebInspector.HeapSnapshotContainmentDataGrid.prototype); +WebInspector.HeapSnapshotContainmentDataGrid.prototype.__proto__ = WebInspector.HeapSnapshotSortableDataGrid.prototype; + +WebInspector.HeapSnapshotRetainmentDataGrid = function() +{ + this.showRetainingEdges = true; + WebInspector.HeapSnapshotContainmentDataGrid.call(this); +} + +WebInspector.HeapSnapshotRetainmentDataGrid.prototype = { + reset: function() + { + this.removeChildren(); + this.resetSortingCache(); + }, +} + +WebInspector.HeapSnapshotRetainmentDataGrid.prototype.__proto__ = WebInspector.HeapSnapshotContainmentDataGrid.prototype; + +WebInspector.HeapSnapshotConstructorsDataGrid = function() +{ + var columns = { + object: { title: WebInspector.UIString("Constructor"), disclosure: true, sortable: true }, + count: { title: WebInspector.UIString("#"), width: "45px", sortable: true }, + shallowSize: { title: WebInspector.UIString("Shallow Size"), width: "90px", sortable: true }, + retainedSize: { title: WebInspector.UIString("Retained Size"), width: "90px", sort: "descending", sortable: true } + }; + WebInspector.HeapSnapshotSortableDataGrid.call(this, columns); + this._filterProfileIndex = -1; +} + +WebInspector.HeapSnapshotConstructorsDataGrid.prototype = { + _defaultPopulateCount: 100, + + _sortFields: function(sortColumn, sortAscending) + { + return { + object: ["_name", sortAscending, "_count", false], + count: ["_count", sortAscending, "_name", true], + shallowSize: ["_shallowSize", sortAscending, "_name", true], + retainedSize: ["_retainedSize", sortAscending, "_name", true] + }[sortColumn]; + }, + + setDataSource: function(snapshotView, snapshot) + { + this.snapshotView = snapshotView; + this.snapshot = snapshot; + if (this._filterProfileIndex === -1) + this.populateChildren(); + }, + + populateChildren: function() + { + function aggregatesReceived(key, aggregates) + { + for (var constructor in aggregates) + this.appendChild(new WebInspector.HeapSnapshotConstructorNode(this, constructor, aggregates[constructor], key)); + this.sortingChanged(); + } + + if (this._filterProfileIndex === -1) { + this.snapshot.aggregates(false, "allObjects", null, aggregatesReceived.bind(this, "allObjects")); + return; + } + + this.dispose(); + this.removeChildren(); + this.resetSortingCache(); + + var key = this._minNodeId + ".." + this._maxNodeId; + var filter = "function(node) { var id = node.id; return id > " + this._minNodeId + " && id <= " + this._maxNodeId + "; }"; + this.snapshot.aggregates(false, key, filter, aggregatesReceived.bind(this, key)); + }, + + _filterSelectIndexChanged: function(loader, profileIndex) + { + this._filterProfileIndex = profileIndex; + + delete this._maxNodeId; + delete this._minNodeId; + + if (this._filterProfileIndex === -1) { + this.populateChildren(); + return; + } + + function firstSnapshotLoaded(snapshot) + { + this._maxNodeId = snapshot.maxNodeId; + if (profileIndex > 0) + loader(profileIndex - 1, secondSnapshotLoaded.bind(this)); + else { + this._minNodeId = 0; + this.populateChildren(); + } + } + + function secondSnapshotLoaded(snapshot) + { + this._minNodeId = snapshot.maxNodeId; + this.populateChildren(); + } + + loader(profileIndex, firstSnapshotLoaded.bind(this)); + }, +}; + +WebInspector.HeapSnapshotConstructorsDataGrid.prototype.__proto__ = WebInspector.HeapSnapshotSortableDataGrid.prototype; + +WebInspector.HeapSnapshotDiffDataGrid = function() +{ + var columns = { + object: { title: WebInspector.UIString("Constructor"), disclosure: true, sortable: true }, + addedCount: { title: WebInspector.UIString("# New"), width: "72px", sortable: true, sort: "descending" }, + removedCount: { title: WebInspector.UIString("# Deleted"), width: "72px", sortable: true }, + // \u0394 is a Greek delta letter. + countDelta: { title: "\u0394", width: "40px", sortable: true }, + addedSize: { title: WebInspector.UIString("Alloc. Size"), width: "72px", sortable: true }, + removedSize: { title: WebInspector.UIString("Freed Size"), width: "72px", sortable: true }, + sizeDelta: { title: "\u0394", width: "72px", sortable: true } + }; + WebInspector.HeapSnapshotSortableDataGrid.call(this, columns); +} + +WebInspector.HeapSnapshotDiffDataGrid.prototype = { + _defaultPopulateCount: 50, + + _sortFields: function(sortColumn, sortAscending) + { + return { + object: ["_name", sortAscending, "_count", false], + addedCount: ["_addedCount", sortAscending, "_name", true], + removedCount: ["_removedCount", sortAscending, "_name", true], + countDelta: ["_countDelta", sortAscending, "_name", true], + addedSize: ["_addedSize", sortAscending, "_name", true], + removedSize: ["_removedSize", sortAscending, "_name", true], + sizeDelta: ["_sizeDelta", sortAscending, "_name", true] + }[sortColumn]; + }, + + setDataSource: function(snapshotView, snapshot) + { + this.snapshotView = snapshotView; + this.snapshot = snapshot; + }, + + _baseProfileIndexChanged: function(loader, profileIndex) + { + loader(profileIndex, this.setBaseDataSource.bind(this)); + }, + + setBaseDataSource: function(baseSnapshot) + { + this.baseSnapshot = baseSnapshot; + this.dispose(); + this.removeChildren(); + this.resetSortingCache(); + if (this.baseSnapshot === this.snapshot) { + this.dispatchEventToListeners("sorting complete"); + return; + } + this.populateChildren(); + }, + + populateChildren: function() + { + function baseAggregatesReceived(baseClasses) + { + function aggregatesReceived(classes) + { + var nodeCount = 0; + var nodes = []; + for (var clss in baseClasses) + nodes.push(new WebInspector.HeapSnapshotDiffNode(this, clss, baseClasses[clss], classes[clss])); + for (clss in classes) { + if (!(clss in baseClasses)) + nodes.push(new WebInspector.HeapSnapshotDiffNode(this, clss, null, classes[clss])); + } + nodeCount = nodes.length; + function addNodeIfNonZeroDiff(boundNode, zeroDiff) + { + if (!zeroDiff) + this.appendChild(boundNode); + if (!--nodeCount) + this.sortingChanged(); + } + for (var i = 0, l = nodes.length; i < l; ++i) { + var node = nodes[i]; + node.calculateDiff(this, addNodeIfNonZeroDiff.bind(this, node)); + } + } + this.snapshot.aggregates(true, "allObjects", null, aggregatesReceived.bind(this)); + } + this.baseSnapshot.aggregates(true, "allObjects", null, baseAggregatesReceived.bind(this)); + } +}; + +WebInspector.HeapSnapshotDiffDataGrid.prototype.__proto__ = WebInspector.HeapSnapshotSortableDataGrid.prototype; + +WebInspector.HeapSnapshotDominatorsDataGrid = function() +{ + var columns = { + object: { title: WebInspector.UIString("Object"), disclosure: true, sortable: true }, + shallowSize: { title: WebInspector.UIString("Shallow Size"), width: "90px", sortable: true }, + retainedSize: { title: WebInspector.UIString("Retained Size"), width: "90px", sort: "descending", sortable: true } + }; + WebInspector.HeapSnapshotSortableDataGrid.call(this, columns); +} + +WebInspector.HeapSnapshotDominatorsDataGrid.prototype = { + _defaultPopulateCount: 25, + + setDataSource: function(snapshotView, snapshot) + { + this.snapshotView = snapshotView; + this.snapshot = snapshot; + this.snapshotNodeIndex = this.snapshot.rootNodeIndex; + this._provider = this._createProvider(snapshot, this.snapshotNodeIndex); + this.sort(); + }, + + sortingChanged: function() + { + this.sort(); + } +}; + +MixInSnapshotNodeFunctions(WebInspector.HeapSnapshotDominatorObjectNode.prototype, WebInspector.HeapSnapshotDominatorsDataGrid.prototype); +WebInspector.HeapSnapshotDominatorsDataGrid.prototype.__proto__ = WebInspector.HeapSnapshotSortableDataGrid.prototype; + +WebInspector.DetailedHeapshotView = function(parent, profile) +{ + WebInspector.View.call(this); + + this.element.addStyleClass("detailed-heapshot-view"); + + this.parent = parent; + this.parent.addEventListener("profile added", this._updateBaseOptions, this); + this.parent.addEventListener("profile added", this._updateFilterOptions, this); + + this.showCountAsPercent = false; + this.showShallowSizeAsPercent = false; + this.showRetainedSizeAsPercent = false; + + this.viewsContainer = document.createElement("div"); + this.viewsContainer.addStyleClass("views-container"); + this.element.appendChild(this.viewsContainer); + + this.containmentView = new WebInspector.View(); + this.containmentView.element.addStyleClass("view"); + this.containmentDataGrid = new WebInspector.HeapSnapshotContainmentDataGrid(); + this.containmentDataGrid.element.addEventListener("click", this._mouseClickInContentsGrid.bind(this), true); + this.containmentDataGrid.element.addEventListener("mousedown", this._mouseDownInContentsGrid.bind(this), true); + this.containmentDataGrid.show(this.containmentView.element); + + this.constructorsView = new WebInspector.View(); + this.constructorsView.element.addStyleClass("view"); + this.constructorsDataGrid = new WebInspector.HeapSnapshotConstructorsDataGrid(); + this.constructorsDataGrid.element.addEventListener("click", this._mouseClickInContentsGrid.bind(this), true); + this.constructorsDataGrid.element.addEventListener("mousedown", this._mouseDownInContentsGrid.bind(this), true); + this.constructorsDataGrid.show(this.constructorsView.element); + + this.diffView = new WebInspector.View(); + this.diffView.element.addStyleClass("view"); + this.diffDataGrid = new WebInspector.HeapSnapshotDiffDataGrid(); + this.diffDataGrid.element.addEventListener("click", this._mouseClickInContentsGrid.bind(this), true); + this.diffDataGrid.show(this.diffView.element); + + this.dominatorView = new WebInspector.View(); + this.dominatorView.element.addStyleClass("view"); + this.dominatorDataGrid = new WebInspector.HeapSnapshotDominatorsDataGrid(); + this.dominatorDataGrid.element.addEventListener("click", this._mouseClickInContentsGrid.bind(this), true); + this.dominatorDataGrid.element.addEventListener("mousedown", this._mouseDownInContentsGrid.bind(this), true); + this.dominatorDataGrid.show(this.dominatorView.element); + + this.retainmentViewHeader = document.createElement("div"); + this.retainmentViewHeader.addStyleClass("retainers-view-header"); + this.retainmentViewHeader.addEventListener("mousedown", this._startRetainersHeaderDragging.bind(this), true); + var retainingPathsTitleDiv = document.createElement("div"); + retainingPathsTitleDiv.className = "title"; + var retainingPathsTitle = document.createElement("span"); + retainingPathsTitle.textContent = WebInspector.UIString("Object's retaining tree"); + retainingPathsTitleDiv.appendChild(retainingPathsTitle); + this.retainmentViewHeader.appendChild(retainingPathsTitleDiv); + this.element.appendChild(this.retainmentViewHeader); + + this.retainmentView = new WebInspector.View(); + this.retainmentView.element.addStyleClass("view"); + this.retainmentView.element.addStyleClass("retaining-paths-view"); + this.retainmentDataGrid = new WebInspector.HeapSnapshotRetainmentDataGrid(); + this.retainmentDataGrid.element.addEventListener("click", this._mouseClickInRetainmentGrid.bind(this), true); + this.retainmentDataGrid.show(this.retainmentView.element); + this.retainmentView.show(this.element); + this.retainmentDataGrid.reset(); + + this.dataGrid = this.constructorsDataGrid; + this.currentView = this.constructorsView; + + this.viewSelectElement = document.createElement("select"); + this.viewSelectElement.className = "status-bar-item"; + this.viewSelectElement.addEventListener("change", this._changeView.bind(this), false); + + this.views = [{title: "Summary", view: this.constructorsView, grid: this.constructorsDataGrid}, + {title: "Comparison", view: this.diffView, grid: this.diffDataGrid}, + {title: "Containment", view: this.containmentView, grid: this.containmentDataGrid}, + {title: "Dominators", view: this.dominatorView, grid: this.dominatorDataGrid}]; + this.views.current = 0; + for (var i = 0; i < this.views.length; ++i) { + var view = this.views[i]; + var option = document.createElement("option"); + option.label = WebInspector.UIString(view.title); + this.viewSelectElement.appendChild(option); + } + + this._profileUid = profile.uid; + + this.baseSelectElement = document.createElement("select"); + this.baseSelectElement.className = "status-bar-item hidden"; + this.baseSelectElement.addEventListener("change", this._changeBase.bind(this), false); + this._updateBaseOptions(); + + this.filterSelectElement = document.createElement("select"); + this.filterSelectElement.className = "status-bar-item"; + this.filterSelectElement.addEventListener("change", this._changeFilter.bind(this), false); + this._updateFilterOptions(); + + this.percentButton = new WebInspector.StatusBarButton("", "percent-time-status-bar-item status-bar-item"); + this.percentButton.addEventListener("click", this._percentClicked.bind(this), false); + this.helpButton = new WebInspector.StatusBarButton("", "heapshot-help-status-bar-item status-bar-item"); + this.helpButton.addEventListener("click", this._helpClicked.bind(this), false); + + var popoverHelper = new WebInspector.ObjectPopoverHelper(this.element, this._getHoverAnchor.bind(this), this._showObjectPopover.bind(this), null, true); + + this._loadProfile(this._profileUid, profileCallback.bind(this)); + + function profileCallback() + { + var list = this._profiles(); + var profileIndex; + for (var i = 0; i < list.length; ++i) { + if (list[i].uid === this._profileUid) { + profileIndex = i; + break; + } + } + + if (profileIndex > 0) + this.baseSelectElement.selectedIndex = profileIndex - 1; + else + this.baseSelectElement.selectedIndex = profileIndex; + this.dataGrid.setDataSource(this, this.profileWrapper); + this._updatePercentButton(); + } +} + +WebInspector.DetailedHeapshotView.prototype = { + dispose: function() + { + this.profileWrapper.dispose(); + if (this.baseProfile) + this.baseProfileWrapper.dispose(); + this.containmentDataGrid.dispose(); + this.constructorsDataGrid.dispose(); + this.diffDataGrid.dispose(); + this.dominatorDataGrid.dispose(); + this.retainmentDataGrid.dispose(); + }, + + get statusBarItems() + { + return [this.viewSelectElement, this.baseSelectElement, this.filterSelectElement, this.percentButton.element, this.helpButton.element]; + }, + + get profile() + { + return this.parent.getProfile(WebInspector.DetailedHeapshotProfileType.TypeId, this._profileUid); + }, + + get profileWrapper() + { + return this.profile.proxy; + }, + + get baseProfile() + { + return this.parent.getProfile(WebInspector.DetailedHeapshotProfileType.TypeId, this._baseProfileUid); + }, + + get baseProfileWrapper() + { + return this.baseProfile.proxy; + }, + + wasShown: function() + { + if (!this.profileWrapper.loaded) + this._loadProfile(this._profileUid, profileCallback1.bind(this)); + else + profileCallback1.call(this); + + function profileCallback1() { + if (this.baseProfile && !this.baseProfileWrapper.loaded) + this._loadProfile(this._baseProfileUid, profileCallback2.bind(this)); + else + profileCallback2.call(this); + } + + function profileCallback2() { + this.currentView.show(this.viewsContainer); + } + }, + + willHide: function() + { + this._currentSearchResultIndex = -1; + }, + + onResize: function() + { + var height = this.retainmentView.element.clientHeight; + this._updateRetainmentViewHeight(height); + }, + + refreshShowAsPercents: function() + { + this._updatePercentButton(); + this.refreshVisibleData(); + }, + + searchCanceled: function() + { + if (this._searchResults) { + for (var i = 0; i < this._searchResults.length; ++i) { + var node = this._searchResults[i].node; + delete node._searchMatched; + node.refresh(); + } + } + + delete this._searchFinishedCallback; + this._currentSearchResultIndex = -1; + this._searchResults = []; + }, + + performSearch: function(query, finishedCallback) + { + // Call searchCanceled since it will reset everything we need before doing a new search. + this.searchCanceled(); + + query = query.trim(); + + if (!query.length) + return; + if (this.currentView !== this.constructorsView && this.currentView !== this.diffView) + return; + + this._searchFinishedCallback = finishedCallback; + + function matchesByName(gridNode) { + return ("name" in gridNode) && gridNode.name.hasSubstring(query, true); + } + + function matchesById(gridNode) { + return ("snapshotNodeId" in gridNode) && gridNode.snapshotNodeId === query; + } + + var matchPredicate; + if (query.charAt(0) !== "@") + matchPredicate = matchesByName; + else { + query = parseInt(query.substring(1), 10); + matchPredicate = matchesById; + } + + function matchesQuery(gridNode) + { + delete gridNode._searchMatched; + if (matchPredicate(gridNode)) { + gridNode._searchMatched = true; + gridNode.refresh(); + return true; + } + return false; + } + + var current = this.dataGrid.children[0]; + var depth = 0; + var info = {}; + + // Restrict to type nodes and instances. + const maxDepth = 1; + + while (current) { + if (matchesQuery(current)) + this._searchResults.push({ node: current }); + current = current.traverseNextNode(false, null, (depth >= maxDepth), info); + depth += info.depthChange; + } + + finishedCallback(this, this._searchResults.length); + }, + + jumpToFirstSearchResult: function() + { + if (!this._searchResults || !this._searchResults.length) + return; + this._currentSearchResultIndex = 0; + this._jumpToSearchResult(this._currentSearchResultIndex); + }, + + jumpToLastSearchResult: function() + { + if (!this._searchResults || !this._searchResults.length) + return; + this._currentSearchResultIndex = (this._searchResults.length - 1); + this._jumpToSearchResult(this._currentSearchResultIndex); + }, + + jumpToNextSearchResult: function() + { + if (!this._searchResults || !this._searchResults.length) + return; + if (++this._currentSearchResultIndex >= this._searchResults.length) + this._currentSearchResultIndex = 0; + this._jumpToSearchResult(this._currentSearchResultIndex); + }, + + jumpToPreviousSearchResult: function() + { + if (!this._searchResults || !this._searchResults.length) + return; + if (--this._currentSearchResultIndex < 0) + this._currentSearchResultIndex = (this._searchResults.length - 1); + this._jumpToSearchResult(this._currentSearchResultIndex); + }, + + showingFirstSearchResult: function() + { + return (this._currentSearchResultIndex === 0); + }, + + showingLastSearchResult: function() + { + return (this._searchResults && this._currentSearchResultIndex === (this._searchResults.length - 1)); + }, + + _jumpToSearchResult: function(index) + { + var searchResult = this._searchResults[index]; + if (!searchResult) + return; + + var node = searchResult.node; + node.revealAndSelect(); + }, + + refreshVisibleData: function() + { + var child = this.dataGrid.children[0]; + while (child) { + child.refresh(); + child = child.traverseNextNode(false, null, true); + } + }, + + _changeBase: function() + { + if (this._baseProfileUid === this._profiles()[this.baseSelectElement.selectedIndex].uid) + return; + + this._baseProfileUid = this._profiles()[this.baseSelectElement.selectedIndex].uid; + this.dataGrid._baseProfileIndexChanged(this._loadProfileByIndex.bind(this), this.baseSelectElement.selectedIndex); + + if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults) + return; + + // The current search needs to be performed again. First negate out previous match + // count by calling the search finished callback with a negative number of matches. + // Then perform the search again with the same query and callback. + this._searchFinishedCallback(this, -this._searchResults.length); + this.performSearch(this.currentQuery, this._searchFinishedCallback); + }, + + _changeFilter: function() + { + var profileIndex = this.filterSelectElement.selectedIndex - 1; + this.dataGrid._filterSelectIndexChanged(this._loadProfileByIndex.bind(this), profileIndex); + + if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults) + return; + + // The current search needs to be performed again. First negate out previous match + // count by calling the search finished callback with a negative number of matches. + // Then perform the search again with the same query and callback. + this._searchFinishedCallback(this, -this._searchResults.length); + this.performSearch(this.currentQuery, this._searchFinishedCallback); + }, + + _profiles: function() + { + return WebInspector.panels.profiles.getProfiles(WebInspector.DetailedHeapshotProfileType.TypeId); + }, + + _loadProfile: function(profileUid, callback) + { + WebInspector.panels.profiles.loadHeapSnapshot(profileUid, callback); + }, + + _loadProfileByIndex: function(profileIndex, callback) + { + var profileUid = this._profiles()[profileIndex].uid; + WebInspector.panels.profiles.loadHeapSnapshot(profileUid, callback); + }, + + isDetailedSnapshot: function(snapshot) + { + var s = new WebInspector.HeapSnapshot(snapshot); + for (var iter = s.rootNode.edges; iter.hasNext(); iter.next()) + if (iter.edge.node.name === "(GC roots)") + return true; + return false; + }, + + processLoadedSnapshot: function(profile, snapshot) + { + profile.nodes = snapshot.nodes; + profile.strings = snapshot.strings; + var s = new WebInspector.HeapSnapshot(profile); + profile.sidebarElement.subtitle = Number.bytesToString(s.totalSize); + }, + + _mouseClickInContentsGrid: function(event) + { + var cell = event.target.enclosingNodeOrSelfWithNodeName("td"); + if (!cell || (!cell.hasStyleClass("object-column"))) + return; + var row = event.target.enclosingNodeOrSelfWithNodeName("tr"); + if (!row) + return; + var nodeItem = row._dataGridNode; + if (!nodeItem || nodeItem.isEventWithinDisclosureTriangle(event)) + return; + if (nodeItem.snapshotNodeIndex) + this.retainmentDataGrid.setDataSource(this, nodeItem.isDeletedNode ? nodeItem.dataGrid.baseSnapshot : nodeItem.dataGrid.snapshot, nodeItem.snapshotNodeIndex, nodeItem.isDeletedNode ? this.baseSelectElement.childNodes[this.baseSelectElement.selectedIndex].label + " | " : ""); + else + this.retainmentDataGrid.reset(); + }, + + _mouseDownInContentsGrid: function(event) + { + if (event.detail < 2) + return; + + var cell = event.target.enclosingNodeOrSelfWithNodeName("td"); + if (!cell || (!cell.hasStyleClass("count-column") && !cell.hasStyleClass("shallowSize-column") && !cell.hasStyleClass("retainedSize-column"))) + return; + + if (cell.hasStyleClass("count-column")) + this.showCountAsPercent = !this.showCountAsPercent; + else if (cell.hasStyleClass("shallowSize-column")) + this.showShallowSizeAsPercent = !this.showShallowSizeAsPercent; + else if (cell.hasStyleClass("retainedSize-column")) + this.showRetainedSizeAsPercent = !this.showRetainedSizeAsPercent; + this.refreshShowAsPercents(); + + event.preventDefault(); + event.stopPropagation(); + }, + + _mouseClickInRetainmentGrid: function(event) + { + var cell = event.target.enclosingNodeOrSelfWithNodeName("td"); + if (!cell || (!cell.hasStyleClass("path-column"))) + return; + var row = event.target.enclosingNodeOrSelfWithNodeName("tr"); + var nodeItem = row._dataGridNode; + if (!nodeItem || !nodeItem.route) + return; + function expandRoute() + { + this.dataGrid.expandRoute(nodeItem.route); + } + this.changeView("Containment", expandRoute.bind(this)); + }, + + changeView: function(viewTitle, callback) + { + var viewIndex = null; + for (var i = 0; i < this.views.length; ++i) + if (this.views[i].title === viewTitle) { + viewIndex = i; + break; + } + if (this.views.current === viewIndex) { + setTimeout(callback, 0); + return; + } + var grid = this.views[viewIndex].grid; + function sortingComplete() + { + grid.removeEventListener("sorting complete", sortingComplete, this); + setTimeout(callback, 0); + } + this.views[viewIndex].grid.addEventListener("sorting complete", sortingComplete, this); + this.viewSelectElement.selectedIndex = viewIndex; + this._changeView({target: {selectedIndex: viewIndex}}); + }, + + _changeView: function(event) + { + if (!event || !this._profileUid) + return; + if (event.target.selectedIndex === this.views.current) + return; + + this.views.current = event.target.selectedIndex; + this.currentView.detach(); + var view = this.views[this.views.current]; + this.currentView = view.view; + this.dataGrid = view.grid; + this.currentView.show(this.viewsContainer); + this.refreshVisibleData(); + this.dataGrid.updateWidths(); + + if (this.currentView === this.diffView) { + this.baseSelectElement.removeStyleClass("hidden"); + if (!this.dataGrid.snapshotView) { + this._changeBase(); + this.dataGrid.setDataSource(this, this.profileWrapper); + } + } else { + this.baseSelectElement.addStyleClass("hidden"); + if (!this.dataGrid.snapshotView) + this.dataGrid.setDataSource(this, this.profileWrapper); + } + + if (this.currentView === this.constructorsView) + this.filterSelectElement.removeStyleClass("hidden"); + else + this.filterSelectElement.addStyleClass("hidden"); + + if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults) + return; + + // The current search needs to be performed again. First negate out previous match + // count by calling the search finished callback with a negative number of matches. + // Then perform the search again the with same query and callback. + this._searchFinishedCallback(this, -this._searchResults.length); + this.performSearch(this.currentQuery, this._searchFinishedCallback); + }, + + _getHoverAnchor: function(target) + { + var span = target.enclosingNodeOrSelfWithNodeName("span"); + if (!span) + return; + var row = target.enclosingNodeOrSelfWithNodeName("tr"); + if (!row) + return; + var gridNode = row._dataGridNode; + if (!gridNode.hasHoverMessage) + return; + span.node = gridNode; + return span; + }, + + get _isShowingAsPercent() + { + return this.showCountAsPercent && this.showShallowSizeAsPercent && this.showRetainedSizeAsPercent; + }, + + _percentClicked: function(event) + { + var currentState = this._isShowingAsPercent; + this.showCountAsPercent = !currentState; + this.showShallowSizeAsPercent = !currentState; + this.showRetainedSizeAsPercent = !currentState; + this.refreshShowAsPercents(); + }, + + _showObjectPopover: function(element, showCallback) + { + element.node.queryObjectContent(showCallback); + }, + + _helpClicked: function(event) + { + if (!this._helpPopoverContentElement) { + var refTypes = ["a:", "console-formatted-name", WebInspector.UIString("property"), + "0:", "console-formatted-name", WebInspector.UIString("element"), + "a:", "console-formatted-number", WebInspector.UIString("context var"), + "a:", "console-formatted-null", WebInspector.UIString("system prop")]; + var objTypes = [" a ", "console-formatted-object", "Object", + "\"a\"", "console-formatted-string", "String", + "/a/", "console-formatted-string", "RegExp", + "a()", "console-formatted-function", "Function", + "a[]", "console-formatted-object", "Array", + "num", "console-formatted-number", "Number", + " a ", "console-formatted-null", "System"]; + + var contentElement = document.createElement("table"); + contentElement.className = "heapshot-help"; + var headerRow = document.createElement("tr"); + var propsHeader = document.createElement("th"); + propsHeader.textContent = WebInspector.UIString("Property types:"); + headerRow.appendChild(propsHeader); + var objsHeader = document.createElement("th"); + objsHeader.textContent = WebInspector.UIString("Object types:"); + headerRow.appendChild(objsHeader); + contentElement.appendChild(headerRow); + var len = Math.max(refTypes.length, objTypes.length); + for (var i = 0; i < len; i += 3) { + var row = document.createElement("tr"); + var refCell = document.createElement("td"); + if (refTypes[i]) + appendHelp(refTypes, i, refCell); + row.appendChild(refCell); + var objCell = document.createElement("td"); + if (objTypes[i]) + appendHelp(objTypes, i, objCell); + row.appendChild(objCell); + contentElement.appendChild(row); + } + this._helpPopoverContentElement = contentElement; + this.helpPopover = new WebInspector.Popover(); + + function appendHelp(help, index, cell) + { + var div = document.createElement("div"); + div.className = "source-code event-properties"; + var name = document.createElement("span"); + name.textContent = help[index]; + name.className = help[index + 1]; + div.appendChild(name); + var desc = document.createElement("span"); + desc.textContent = " " + help[index + 2]; + div.appendChild(desc); + cell.appendChild(div); + } + } + if (this.helpPopover.visible) + this.helpPopover.hide(); + else + this.helpPopover.show(this._helpPopoverContentElement, this.helpButton.element); + }, + + _startRetainersHeaderDragging: function(event) + { + if (!this.isShowing()) + return; + + WebInspector.elementDragStart(this.retainmentViewHeader, this._retainersHeaderDragging.bind(this), this._endRetainersHeaderDragging.bind(this), event, "row-resize"); + this._previousDragPosition = event.pageY; + event.stopPropagation(); + }, + + _retainersHeaderDragging: function(event) + { + var height = this.retainmentView.element.clientHeight; + height += this._previousDragPosition - event.pageY; + this._previousDragPosition = event.pageY; + this._updateRetainmentViewHeight(height); + event.preventDefault(); + event.stopPropagation(); + }, + + _endRetainersHeaderDragging: function(event) + { + WebInspector.elementDragEnd(event); + delete this._previousDragPosition; + event.stopPropagation(); + }, + + _updateRetainmentViewHeight: function(height) + { + height = Number.constrain(height, Preferences.minConsoleHeight, this.element.clientHeight - Preferences.minConsoleHeight); + this.viewsContainer.style.bottom = (height + this.retainmentViewHeader.clientHeight) + "px"; + this.retainmentView.element.style.height = height + "px"; + this.retainmentViewHeader.style.bottom = height + "px"; + }, + + _updateBaseOptions: function() + { + var list = this._profiles(); + // We're assuming that snapshots can only be added. + if (this.baseSelectElement.length === list.length) + return; + + for (var i = this.baseSelectElement.length, n = list.length; i < n; ++i) { + var baseOption = document.createElement("option"); + var title = list[i].title; + if (!title.indexOf(UserInitiatedProfileName)) + title = WebInspector.UIString("Snapshot %d", title.substring(UserInitiatedProfileName.length + 1)); + baseOption.label = title; + this.baseSelectElement.appendChild(baseOption); + } + }, + + _updateFilterOptions: function() + { + var list = this._profiles(); + // We're assuming that snapshots can only be added. + if (this.filterSelectElement.length - 1 === list.length) + return; + + if (!this.filterSelectElement.length) { + var filterOption = document.createElement("option"); + filterOption.label = WebInspector.UIString("All objects"); + this.filterSelectElement.appendChild(filterOption); + } + + for (var i = this.filterSelectElement.length - 1, n = list.length; i < n; ++i) { + var filterOption = document.createElement("option"); + var title = list[i].title; + if (!title.indexOf(UserInitiatedProfileName)) { + if (!i) + title = WebInspector.UIString("Objects allocated before Snapshot %d", title.substring(UserInitiatedProfileName.length + 1)); + else + title = WebInspector.UIString("Objects allocated between Snapshots %d and %d", title.substring(UserInitiatedProfileName.length + 1) - 1, title.substring(UserInitiatedProfileName.length + 1)); + } + filterOption.label = title; + this.filterSelectElement.appendChild(filterOption); + } + }, + + _updatePercentButton: function() + { + if (this._isShowingAsPercent) { + this.percentButton.title = WebInspector.UIString("Show absolute counts and sizes."); + this.percentButton.toggled = true; + } else { + this.percentButton.title = WebInspector.UIString("Show counts and sizes as percentages."); + this.percentButton.toggled = false; + } + } +}; + +WebInspector.DetailedHeapshotView.prototype.__proto__ = WebInspector.View.prototype; + +WebInspector.settings.showHeapSnapshotObjectsHiddenProperties = WebInspector.settings.createSetting("showHeaSnapshotObjectsHiddenProperties", false); + +WebInspector.DetailedHeapshotProfileType = function() +{ + WebInspector.ProfileType.call(this, WebInspector.DetailedHeapshotProfileType.TypeId, WebInspector.UIString("Take Heap Snapshot")); +} + +WebInspector.DetailedHeapshotProfileType.TypeId = "HEAP"; + +WebInspector.DetailedHeapshotProfileType.prototype = { + get buttonTooltip() + { + return WebInspector.UIString("Take heap snapshot."); + }, + + buttonClicked: function() + { + WebInspector.panels.profiles.takeHeapSnapshot(); + }, + + get treeItemTitle() + { + return WebInspector.UIString("HEAP SNAPSHOTS"); + }, + + get description() + { + return WebInspector.UIString("Heap snapshot profiles show memory distribution among your page's JavaScript objects and related DOM nodes."); + }, + + createSidebarTreeElementForProfile: function(profile) + { + return new WebInspector.ProfileSidebarTreeElement(profile, WebInspector.UIString("Snapshot %d"), "heap-snapshot-sidebar-tree-item"); + }, + + createView: function(profile) + { + return new WebInspector.DetailedHeapshotView(WebInspector.panels.profiles, profile); + } +} + +WebInspector.DetailedHeapshotProfileType.prototype.__proto__ = WebInspector.ProfileType.prototype; +/* DebuggerModel.js */ + +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.Object} + */ +WebInspector.DebuggerModel = function() +{ + this._debuggerPausedDetails = null; + /** + * @type {Object.} + */ + this._scripts = {}; + + this._canSetScriptSource = false; + + InspectorBackend.registerDebuggerDispatcher(new WebInspector.DebuggerDispatcher(this)); +} + +/** + * @constructor + * @param {Array.} callFrames + * @param {string} reason + * @param {*} auxData + */ +WebInspector.DebuggerPausedDetails = function(callFrames, reason, auxData) +{ + this.callFrames = callFrames; + this.reason = reason; + this.auxData = auxData; +} + +/** + * @constructor + * @extends {DebuggerAgent.Location} + * @param {number} lineNumber + * @param {number} columnNumber + */ +WebInspector.DebuggerModel.Location = function(lineNumber, columnNumber) +{ + this.lineNumber = lineNumber; + this.columnNumber = columnNumber; +} + +WebInspector.DebuggerModel.Events = { + DebuggerWasEnabled: "debugger-was-enabled", + DebuggerWasDisabled: "debugger-was-disabled", + DebuggerPaused: "debugger-paused", + DebuggerResumed: "debugger-resumed", + ParsedScriptSource: "parsed-script-source", + FailedToParseScriptSource: "failed-to-parse-script-source", + BreakpointResolved: "breakpoint-resolved", + GlobalObjectCleared: "global-object-cleared" +} + +WebInspector.DebuggerModel.BreakReason = { + DOM: "DOM", + EventListener: "EventListener", + XHR: "XHR", + Exception: "exception" +} + +WebInspector.DebuggerModel.prototype = { + enableDebugger: function() + { + function callback(error, result) + { + this._canSetScriptSource = result; + } + DebuggerAgent.canSetScriptSource(callback.bind(this)); + DebuggerAgent.enable(this._debuggerWasEnabled.bind(this)); + }, + + disableDebugger: function() + { + DebuggerAgent.disable(this._debuggerWasDisabled.bind(this)); + }, + + /** + * @return {boolean} + */ + canSetScriptSource: function() + { + return this._canSetScriptSource; + }, + + _debuggerWasEnabled: function() + { + this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.DebuggerWasEnabled); + }, + + _debuggerWasDisabled: function() + { + this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.DebuggerWasDisabled); + }, + + /** + * @param {DebuggerAgent.Location} location + */ + continueToLocation: function(location) + { + DebuggerAgent.continueToLocation(location); + }, + + /** + * @param {DebuggerAgent.Location} location + * @param {string} condition + * @param {function()} callback + */ + setBreakpointByScriptLocation: function(location, condition, callback) + { + var script = this.scriptForSourceID(location.scriptId); + if (script.sourceURL) + this.setBreakpoint(script.sourceURL, location.lineNumber, location.columnNumber, condition, callback); + else + this.setBreakpointBySourceId(location, condition, callback); + }, + + /** + * @param {string} url + * @param {number} lineNumber + * @param {number=} columnNumber + * @param {string=} condition + * @param {function(?DebuggerAgent.BreakpointId, Array.=)=} callback + */ + setBreakpoint: function(url, lineNumber, columnNumber, condition, callback) + { + // Adjust column if needed. + var minColumnNumber = 0; + for (var id in this._scripts) { + var script = this._scripts[id]; + if (url === script.sourceURL && lineNumber === script.lineOffset) + minColumnNumber = minColumnNumber ? Math.min(minColumnNumber, script.columnOffset) : script.columnOffset; + } + columnNumber = Math.max(columnNumber, minColumnNumber); + + /** + * @this {WebInspector.DebuggerModel} + * @param {?Protocol.Error} error + * @param {DebuggerAgent.BreakpointId} breakpointId + * @param {Array.=} locations + */ + function didSetBreakpoint(error, breakpointId, locations) + { + if (callback) + callback(error ? null : breakpointId, locations); + } + DebuggerAgent.setBreakpointByUrl(lineNumber, url, undefined, columnNumber, condition, didSetBreakpoint.bind(this)); + WebInspector.userMetrics.ScriptsBreakpointSet.record(); + }, + + /** + * @param {DebuggerAgent.Location} location + * @param {string} condition + * @param {function(?DebuggerAgent.BreakpointId, Array.)=} callback + */ + setBreakpointBySourceId: function(location, condition, callback) + { + /** + * @this {WebInspector.DebuggerModel} + * @param {?Protocol.Error} error + * @param {DebuggerAgent.BreakpointId} breakpointId + * @param {DebuggerAgent.Location} actualLocation + */ + function didSetBreakpoint(error, breakpointId, actualLocation) + { + if (callback) + callback(error ? null : breakpointId, [actualLocation]); + } + DebuggerAgent.setBreakpoint(location, condition, didSetBreakpoint.bind(this)); + WebInspector.userMetrics.ScriptsBreakpointSet.record(); + }, + + /** + * @param {DebuggerAgent.BreakpointId} breakpointId + * @param {function(?Protocol.Error)=} callback + */ + removeBreakpoint: function(breakpointId, callback) + { + DebuggerAgent.removeBreakpoint(breakpointId, callback); + }, + + /** + * @param {DebuggerAgent.BreakpointId} breakpointId + * @param {DebuggerAgent.Location} location + */ + _breakpointResolved: function(breakpointId, location) + { + this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.BreakpointResolved, {breakpointId: breakpointId, location: location}); + }, + + _globalObjectCleared: function() + { + this._debuggerPausedDetails = null; + this._scripts = {}; + this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.GlobalObjectCleared); + }, + + /** + * @return {Object.} + */ + get scripts() + { + return this._scripts; + }, + + /** + * @param {DebuggerAgent.ScriptId} scriptId + * @return {WebInspector.Script|undefined} + */ + scriptForSourceID: function(scriptId) + { + return this._scripts[scriptId]; + }, + + /** + * @param {string} url + */ + scriptsForURL: function(url) + { + return this.queryScripts(function(s) { return s.sourceURL === url; }); + }, + + /** + * @param {function(WebInspector.Script):boolean} filter + */ + queryScripts: function(filter) + { + var scripts = []; + for (var scriptId in this._scripts) { + var script = this._scripts[scriptId]; + if (filter(script)) + scripts.push(script); + } + return scripts; + }, + + /** + * @param {DebuggerAgent.ScriptId} scriptId + * @param {string} newSource + * @param {function(?Protocol.Error)} callback + */ + setScriptSource: function(scriptId, newSource, callback) + { + this._scripts[scriptId].editSource(newSource, this._didEditScriptSource.bind(this, scriptId, newSource, callback)); + }, + + /** + * @param {DebuggerAgent.ScriptId} scriptId + * @param {string} newSource + * @param {function(?Protocol.Error)} callback + * @param {?Protocol.Error} error + * @param {Array.=} callFrames + */ + _didEditScriptSource: function(scriptId, newSource, callback, error, callFrames) + { + if (!error && callFrames && callFrames.length) + this._debuggerPausedDetails.callFrames = callFrames; + callback(error); + }, + + /** + * @return {Array.} + */ + get callFrames() + { + return this._debuggerPausedDetails ? this._debuggerPausedDetails.callFrames : null; + }, + + /** + * @return {?WebInspector.DebuggerPausedDetails} + */ + get debuggerPausedDetails() + { + return this._debuggerPausedDetails; + }, + + /** + * @param {Array.} callFrames + * @param {string} reason + * @param {*} auxData + */ + _pausedScript: function(callFrames, reason, auxData) + { + this._debuggerPausedDetails = new WebInspector.DebuggerPausedDetails(callFrames, reason, auxData); + this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.DebuggerPaused, this._debuggerPausedDetails); + }, + + _resumedScript: function() + { + this._debuggerPausedDetails = null; + this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.DebuggerResumed); + }, + + /** + * @param {DebuggerAgent.ScriptId} scriptId + * @param {string} sourceURL + * @param {number} startLine + * @param {number} startColumn + * @param {number} endLine + * @param {number} endColumn + * @param {boolean} isContentScript + */ + _parsedScriptSource: function(scriptId, sourceURL, startLine, startColumn, endLine, endColumn, isContentScript, sourceMapURL) + { + var script = new WebInspector.Script(scriptId, sourceURL, startLine, startColumn, endLine, endColumn, isContentScript, sourceMapURL); + this._scripts[scriptId] = script; + this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.ParsedScriptSource, script); + }, + + /** + * @param {string} sourceURL + * @param {string} source + * @param {number} startingLine + * @param {number} errorLine + * @param {string} errorMessage + */ + _failedToParseScriptSource: function(sourceURL, source, startingLine, errorLine, errorMessage) + { + var script = new WebInspector.Script("", sourceURL, startingLine, 0, 0, 0, false); + this.dispatchEventToListeners(WebInspector.DebuggerModel.Events.FailedToParseScriptSource, script); + } +} + +WebInspector.DebuggerModel.prototype.__proto__ = WebInspector.Object.prototype; + +WebInspector.DebuggerEventTypes = { + JavaScriptPause: 0, + JavaScriptBreakpoint: 1, + NativeBreakpoint: 2 +}; + +/** + * @constructor + * @implements {DebuggerAgent.Dispatcher} + * @param {WebInspector.DebuggerModel} debuggerModel + */ +WebInspector.DebuggerDispatcher = function(debuggerModel) +{ + this._debuggerModel = debuggerModel; +} + +WebInspector.DebuggerDispatcher.prototype = { + /** + * @param {Array.} callFrames + * @param {string} reason + * @param {*} auxData + */ + paused: function(callFrames, reason, auxData) + { + this._debuggerModel._pausedScript(callFrames, reason, auxData); + }, + + resumed: function() + { + this._debuggerModel._resumedScript(); + }, + + globalObjectCleared: function() + { + this._debuggerModel._globalObjectCleared(); + }, + + /** + * @param {DebuggerAgent.ScriptId} scriptId + * @param {string} sourceURL + * @param {number} startLine + * @param {number} startColumn + * @param {number} endLine + * @param {number} endColumn + * @param {boolean=} isContentScript + */ + scriptParsed: function(scriptId, sourceURL, startLine, startColumn, endLine, endColumn, isContentScript, sourceMapURL) + { + this._debuggerModel._parsedScriptSource(scriptId, sourceURL, startLine, startColumn, endLine, endColumn, !!isContentScript, sourceMapURL); + }, + + /** + * @param {string} sourceURL + * @param {string} source + * @param {number} startingLine + * @param {number} errorLine + * @param {string} errorMessage + */ + scriptFailedToParse: function(sourceURL, source, startingLine, errorLine, errorMessage) + { + this._debuggerModel._failedToParseScriptSource(sourceURL, source, startingLine, errorLine, errorMessage); + }, + + /** + * @param {DebuggerAgent.BreakpointId} breakpointId + * @param {DebuggerAgent.Location} location + */ + breakpointResolved: function(breakpointId, location) + { + this._debuggerModel._breakpointResolved(breakpointId, location); + } +} + +/** + * @type {?WebInspector.DebuggerModel} + */ +WebInspector.debuggerModel = null; +/* DebuggerPresentationModel.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.Object} + */ +WebInspector.DebuggerPresentationModel = function() +{ + // FIXME: apply formatter from outside as a generic mapping. + this._formatter = new WebInspector.ScriptFormatter(); + this._rawSourceCodes = []; + this._rawSourceCodeForScriptId = {}; + this._rawSourceCodeForURL = {}; + this._rawSourceCodeForDocumentURL = {}; + this._presentationCallFrames = []; + + this._breakpointManager = new WebInspector.BreakpointManager(WebInspector.settings.breakpoints, this._breakpointAdded.bind(this), this._breakpointRemoved.bind(this), WebInspector.debuggerModel); + + WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.ParsedScriptSource, this._parsedScriptSource, this); + WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.FailedToParseScriptSource, this._failedToParseScriptSource, this); + WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.DebuggerPaused, this._debuggerPaused, this); + WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.DebuggerResumed, this._debuggerResumed, this); + WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.GlobalObjectCleared, this._debuggerReset, this); + + WebInspector.console.addEventListener(WebInspector.ConsoleModel.Events.MessageAdded, this._consoleMessageAdded, this); + WebInspector.console.addEventListener(WebInspector.ConsoleModel.Events.ConsoleCleared, this._consoleCleared, this); + + new WebInspector.DebuggerPresentationModelResourceBinding(this); +} + +WebInspector.DebuggerPresentationModel.Events = { + UISourceCodeAdded: "source-file-added", + UISourceCodeReplaced: "source-file-replaced", + UISourceCodeRemoved: "source-file-removed", + ConsoleMessageAdded: "console-message-added", + ConsoleMessagesCleared: "console-messages-cleared", + BreakpointAdded: "breakpoint-added", + BreakpointRemoved: "breakpoint-removed", + DebuggerPaused: "debugger-paused", + DebuggerResumed: "debugger-resumed", + DebuggerReset: "debugger-reset", + CallFrameSelected: "call-frame-selected", + ConsoleCommandEvaluatedInSelectedCallFrame: "console-command-evaluated-in-selected-call-frame", + ExecutionLineChanged: "execution-line-changed" +} + +WebInspector.DebuggerPresentationModel.prototype = { + /** + * @param {WebInspector.DebuggerPresentationModel.LinkifierFormatter=} formatter + */ + createLinkifier: function(formatter) + { + return new WebInspector.DebuggerPresentationModel.Linkifier(this, formatter); + }, + + /** + * @param {WebInspector.PresentationCallFrame} callFrame + * @return {WebInspector.DebuggerPresentationModel.CallFramePlacard} + */ + createPlacard: function(callFrame) + { + return new WebInspector.DebuggerPresentationModel.CallFramePlacard(callFrame); + }, + + /** + * @param {DebuggerAgent.Location} rawLocation + * @return {?WebInspector.UILocation} + */ + rawLocationToUILocation: function(rawLocation) + { + var rawSourceCode = this._rawSourceCodeForScriptId[rawLocation.scriptId]; + if (!rawSourceCode.sourceMapping) + return null; + return rawSourceCode.sourceMapping.rawLocationToUILocation(rawLocation); + }, + + /** + * @param {WebInspector.Event} event + */ + _parsedScriptSource: function(event) + { + var script = /** @type {WebInspector.Script} */ event.data; + this._addScript(script); + }, + + /** + * @param {WebInspector.Event} event + */ + _failedToParseScriptSource: function(event) + { + var script = /** @type {WebInspector.Script} */ event.data; + this._addScript(script); + }, + + /** + * @param {WebInspector.Script} script + */ + _addScript: function(script) + { + var resource = null; + var isInlineScript = false; + if (script.isInlineScript()) { + resource = WebInspector.networkManager.inflightResourceForURL(script.sourceURL) || WebInspector.resourceForURL(script.sourceURL); + if (resource && resource.type === WebInspector.Resource.Type.Document) { + isInlineScript = true; + var rawSourceCode = this._rawSourceCodeForDocumentURL[script.sourceURL]; + if (rawSourceCode) { + rawSourceCode.addScript(script); + this._bindScriptToRawSourceCode(script, rawSourceCode); + return; + } + } + } + + var compilerSourceMapping = null; + if (WebInspector.settings.sourceMapsEnabled.get() && script.sourceMapURL) + compilerSourceMapping = new WebInspector.ClosureCompilerSourceMapping(script.sourceMapURL, script.sourceURL); + + var rawSourceCode = new WebInspector.RawSourceCode(script.scriptId, script, resource, this._formatter, this._formatSource, compilerSourceMapping); + this._rawSourceCodes.push(rawSourceCode); + this._bindScriptToRawSourceCode(script, rawSourceCode); + + if (isInlineScript) + this._rawSourceCodeForDocumentURL[script.sourceURL] = rawSourceCode; + + if (rawSourceCode.sourceMapping) + this._updateSourceMapping(rawSourceCode, null); + rawSourceCode.addEventListener(WebInspector.RawSourceCode.Events.SourceMappingUpdated, this._sourceMappingUpdated, this); + }, + + /** + * @param {WebInspector.Script} script + * @param {WebInspector.RawSourceCode} rawSourceCode + */ + _bindScriptToRawSourceCode: function(script, rawSourceCode) + { + this._rawSourceCodeForScriptId[script.scriptId] = rawSourceCode; + this._rawSourceCodeForURL[script.sourceURL] = rawSourceCode; + }, + + /** + * @param {WebInspector.Event} event + */ + _sourceMappingUpdated: function(event) + { + var rawSourceCode = /** @type {WebInspector.RawSourceCode} */ event.target; + var oldSourceMapping = /** @type {WebInspector.RawSourceCode.SourceMapping} */ event.data["oldSourceMapping"]; + this._updateSourceMapping(rawSourceCode, oldSourceMapping); + }, + + /** + * @return {Array.} + */ + uiSourceCodes: function() + { + var result = []; + for (var i = 0; i < this._rawSourceCodes.length; ++i) { + var uiSourceCodeList = this._rawSourceCodes[i].sourceMapping.uiSourceCodeList(); + for (var j = 0; j < uiSourceCodeList.length; ++j) + result.push(uiSourceCodeList[j]); + } + return result; + }, + + /** + * @param {WebInspector.RawSourceCode} rawSourceCode + * @param {WebInspector.RawSourceCode.SourceMapping} oldSourceMapping + */ + _updateSourceMapping: function(rawSourceCode, oldSourceMapping) + { + if (oldSourceMapping) { + var oldUISourceCodeList = oldSourceMapping.uiSourceCodeList(); + for (var i = 0; i < oldUISourceCodeList.length; ++i) { + var breakpoints = this._breakpointManager.breakpointsForUISourceCode(oldUISourceCodeList[i]); + for (var lineNumber in breakpoints) { + var breakpoint = breakpoints[lineNumber]; + this._breakpointRemoved(breakpoint); + delete breakpoint.uiSourceCode; + } + } + } + + this._restoreBreakpoints(rawSourceCode); + this._restoreConsoleMessages(rawSourceCode); + + if (!oldSourceMapping) { + var uiSourceCodeList = rawSourceCode.sourceMapping.uiSourceCodeList(); + for (var i = 0; i < uiSourceCodeList.length; ++i) + this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.UISourceCodeAdded, uiSourceCodeList[i]); + } else { + var eventData = { uiSourceCodeList: rawSourceCode.sourceMapping.uiSourceCodeList(), oldUISourceCodeList: oldSourceMapping.uiSourceCodeList() }; + this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.UISourceCodeReplaced, eventData); + } + }, + + /** + * @param {WebInspector.RawSourceCode} rawSourceCode + */ + _restoreBreakpoints: function(rawSourceCode) + { + var uiSourceCodeList = rawSourceCode.sourceMapping.uiSourceCodeList(); + for (var i = 0; i < uiSourceCodeList.length; ++i) { + var uiSourceCode = uiSourceCodeList[i]; + this._breakpointManager.uiSourceCodeAdded(uiSourceCode); + var breakpoints = this._breakpointManager.breakpointsForUISourceCode(uiSourceCode); + for (var lineNumber in breakpoints) + this._breakpointAdded(breakpoints[lineNumber]); + } + + }, + + /** + * @param {WebInspector.RawSourceCode} rawSourceCode + */ + _restoreConsoleMessages: function(rawSourceCode) + { + var messages = rawSourceCode.messages; + for (var i = 0; i < messages.length; ++i) + messages[i]._presentationMessage = this._createPresentationMessage(messages[i], rawSourceCode.sourceMapping); + }, + + /** + * @param {WebInspector.UISourceCode} uiSourceCode + * @return {boolean} + */ + canEditScriptSource: function(uiSourceCode) + { + if (!WebInspector.debuggerModel.canSetScriptSource() || this._formatSource) + return false; + var rawSourceCode = uiSourceCode.rawSourceCode; + var script = this._scriptForRawSourceCode(rawSourceCode); + return script && !script.lineOffset && !script.columnOffset; + }, + + /** + * @param {WebInspector.UISourceCode} uiSourceCode + * @param {string} newSource + * @param {function(?Protocol.Error)} callback + */ + setScriptSource: function(uiSourceCode, newSource, callback) + { + var rawSourceCode = uiSourceCode.rawSourceCode; + var script = this._scriptForRawSourceCode(rawSourceCode); + + /** + * @this {WebInspector.DebuggerPresentationModel} + * @param {?Protocol.Error} error + */ + function didEditScriptSource(error) + { + callback(error); + if (error) + return; + + var resource = WebInspector.resourceForURL(rawSourceCode.url); + if (resource) + resource.addRevision(newSource); + + uiSourceCode.contentChanged(newSource); + + if (WebInspector.debuggerModel.callFrames) + this._debuggerPaused(); + } + WebInspector.debuggerModel.setScriptSource(script.scriptId, newSource, didEditScriptSource.bind(this)); + }, + + /** + * @param {WebInspector.UISourceCode} uiSourceCode + * @param {string} oldSource + * @param {string} newSource + */ + _updateBreakpointsAfterLiveEdit: function(uiSourceCode, oldSource, newSource) + { + var breakpoints = this._breakpointManager.breakpointsForUISourceCode(uiSourceCode); + + // Clear and re-create breakpoints according to text diff. + var diff = Array.diff(oldSource.split("\n"), newSource.split("\n")); + for (var lineNumber in breakpoints) { + var breakpoint = breakpoints[lineNumber]; + + this.removeBreakpoint(uiSourceCode, parseInt(lineNumber, 10)); + + var newLineNumber = diff.left[lineNumber].row; + if (newLineNumber === undefined) { + for (var i = lineNumber - 1; i >= 0; --i) { + if (diff.left[i].row === undefined) + continue; + var shiftedLineNumber = diff.left[i].row + lineNumber - i; + if (shiftedLineNumber < diff.right.length) { + var originalLineNumber = diff.right[shiftedLineNumber].row; + if (originalLineNumber === lineNumber || originalLineNumber === undefined) + newLineNumber = shiftedLineNumber; + } + break; + } + } + if (newLineNumber !== undefined) + this.setBreakpoint(uiSourceCode, newLineNumber, breakpoint.condition, breakpoint.enabled); + } + }, + + /** + * @param {boolean} formatSource + */ + setFormatSource: function(formatSource) + { + if (this._formatSource === formatSource) + return; + + this._formatSource = formatSource; + this._breakpointManager.reset(); + for (var i = 0; i < this._rawSourceCodes.length; ++i) + this._rawSourceCodes[i].setFormatted(this._formatSource); + }, + + /** + * @param {WebInspector.Event} event + */ + _consoleMessageAdded: function(event) + { + var message = /** @type {WebInspector.ConsoleMessage} */ event.data; + if (!message.url || !message.isErrorOrWarning()) + return; + + var rawSourceCode = this._rawSourceCodeForScriptWithURL(message.url); + if (!rawSourceCode) + return; + + rawSourceCode.messages.push(message); + if (rawSourceCode.sourceMapping) { + message._presentationMessage = this._createPresentationMessage(message, rawSourceCode.sourceMapping); + this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.ConsoleMessageAdded, message._presentationMessage); + } + }, + + /** + * @param {WebInspector.ConsoleMessage} message + * @param {WebInspector.RawSourceCode.SourceMapping} sourceMapping + * @return {WebInspector.PresentationConsoleMessage} + */ + _createPresentationMessage: function(message, sourceMapping) + { + // FIXME(62725): stack trace line/column numbers are one-based. + var lineNumber = message.stackTrace ? message.stackTrace[0].lineNumber - 1 : message.line - 1; + var columnNumber = message.stackTrace ? message.stackTrace[0].columnNumber - 1 : 0; + var uiLocation = sourceMapping.rawLocationToUILocation(/** @type {DebuggerAgent.Location} */ { lineNumber: lineNumber, columnNumber: columnNumber }); + var presentationMessage = new WebInspector.PresentationConsoleMessage(uiLocation.uiSourceCode, uiLocation.lineNumber, message); + return presentationMessage; + }, + + _consoleCleared: function() + { + for (var i = 0; i < this._rawSourceCodes.length; ++i) + this._rawSourceCodes[i].messages = []; + this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.ConsoleMessagesCleared); + }, + + /** + * @param {WebInspector.UISourceCode} uiSourceCode + * @param {number} lineNumber + */ + continueToLine: function(uiSourceCode, lineNumber) + { + // FIXME: use RawSourceCode.uiLocationToRawLocation. + var rawLocation = uiSourceCode.rawSourceCode.sourceMapping.uiLocationToRawLocation(uiSourceCode, lineNumber, 0); + WebInspector.debuggerModel.continueToLocation(rawLocation); + }, + + /** + * @param {WebInspector.UISourceCode} uiSourceCode + * @return {Array.} + */ + breakpointsForUISourceCode: function(uiSourceCode) + { + var breakpointsMap = this._breakpointManager.breakpointsForUISourceCode(uiSourceCode); + var breakpointsList = []; + for (var lineNumber in breakpointsMap) + breakpointsList.push(breakpointsMap[lineNumber]); + return breakpointsList; + }, + + /** + * @param {WebInspector.UISourceCode} uiSourceCode + * @return {Array.} + */ + messagesForUISourceCode: function(uiSourceCode) + { + var rawSourceCode = uiSourceCode.rawSourceCode; + var messages = []; + for (var i = 0; i < rawSourceCode.messages.length; ++i) + messages.push(rawSourceCode.messages[i]._presentationMessage); + return messages; + }, + + /** + * @param {WebInspector.UISourceCode} uiSourceCode + * @param {number} lineNumber + * @param {string} condition + * @param {boolean} enabled + */ + setBreakpoint: function(uiSourceCode, lineNumber, condition, enabled) + { + this._breakpointManager.setBreakpoint(uiSourceCode, lineNumber, condition, enabled); + }, + + /** + * @param {WebInspector.UISourceCode} uiSourceCode + * @param {number} lineNumber + * @param {boolean} enabled + */ + setBreakpointEnabled: function(uiSourceCode, lineNumber, enabled) + { + var breakpoint = this.findBreakpoint(uiSourceCode, lineNumber); + if (!breakpoint) + return; + this._breakpointManager.removeBreakpoint(uiSourceCode, lineNumber); + this._breakpointManager.setBreakpoint(uiSourceCode, lineNumber, breakpoint.condition, enabled); + }, + + /** + * @param {WebInspector.UISourceCode} uiSourceCode + * @param {number} lineNumber + * @param {string} condition + * @param {boolean} enabled + */ + updateBreakpoint: function(uiSourceCode, lineNumber, condition, enabled) + { + this._breakpointManager.removeBreakpoint(uiSourceCode, lineNumber); + this._breakpointManager.setBreakpoint(uiSourceCode, lineNumber, condition, enabled); + }, + + /** + * @param {WebInspector.UISourceCode} uiSourceCode + * @param {number} lineNumber + */ + removeBreakpoint: function(uiSourceCode, lineNumber) + { + this._breakpointManager.removeBreakpoint(uiSourceCode, lineNumber); + }, + + /** + */ + removeAllBreakpoints: function() + { + this._breakpointManager.removeAllBreakpoints(); + }, + + /** + * @param {WebInspector.UISourceCode} uiSourceCode + * @param {number} lineNumber + * @return {WebInspector.Breakpoint|undefined} + */ + findBreakpoint: function(uiSourceCode, lineNumber) + { + return this._breakpointManager.breakpointsForUISourceCode(uiSourceCode)[lineNumber]; + }, + + /** + * @param {WebInspector.Breakpoint} breakpoint + */ + _breakpointAdded: function(breakpoint) + { + if (breakpoint.uiSourceCode) + this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.BreakpointAdded, breakpoint); + }, + + /** + * @param {WebInspector.Breakpoint} breakpoint + */ + _breakpointRemoved: function(breakpoint) + { + if (breakpoint.uiSourceCode) + this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.BreakpointRemoved, breakpoint); + }, + + _debuggerPaused: function() + { + var callFrames = WebInspector.debuggerModel.callFrames; + this._presentationCallFrames = []; + for (var i = 0; i < callFrames.length; ++i) { + var callFrame = callFrames[i]; + var script = WebInspector.debuggerModel.scriptForSourceID(callFrame.location.scriptId); + if (!script) + continue; + var rawSourceCode = this._rawSourceCodeForScript(script); + this._presentationCallFrames.push(new WebInspector.PresentationCallFrame(callFrame, i, this, rawSourceCode)); + } + var details = WebInspector.debuggerModel.debuggerPausedDetails; + this.selectedCallFrame = this._presentationCallFrames[0]; + this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.DebuggerPaused, { callFrames: this._presentationCallFrames, details: details }); + }, + + _debuggerResumed: function() + { + this._presentationCallFrames = []; + this.selectedCallFrame = null; + this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.DebuggerResumed); + }, + + get paused() + { + return !!WebInspector.debuggerModel.debuggerPausedDetails; + }, + + set selectedCallFrame(callFrame) + { + if (this._selectedCallFrame) + this._selectedCallFrame.rawSourceCode.removeEventListener(WebInspector.RawSourceCode.Events.SourceMappingUpdated, this._dispatchExecutionLineChanged, this); + this._selectedCallFrame = callFrame; + if (!this._selectedCallFrame) + return; + + this._selectedCallFrame.rawSourceCode.forceUpdateSourceMapping(); + this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.CallFrameSelected, callFrame); + + this._selectedCallFrame.rawSourceCode.addEventListener(WebInspector.RawSourceCode.Events.SourceMappingUpdated, this._dispatchExecutionLineChanged, this); + }, + + get selectedCallFrame() + { + return this._selectedCallFrame; + }, + + /** + * @param {function(?WebInspector.RemoteObject, boolean, RuntimeAgent.RemoteObject=)} callback + */ + evaluateInSelectedCallFrame: function(code, objectGroup, includeCommandLineAPI, returnByValue, callback) + { + /** + * @param {?RuntimeAgent.RemoteObject} result + * @param {boolean} wasThrown + */ + function didEvaluate(result, wasThrown) + { + if (returnByValue) + callback(null, wasThrown, wasThrown ? null : result); + else + callback(WebInspector.RemoteObject.fromPayload(result), wasThrown); + + if (objectGroup === "console") + this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.ConsoleCommandEvaluatedInSelectedCallFrame); + } + + this.selectedCallFrame.evaluate(code, objectGroup, includeCommandLineAPI, returnByValue, didEvaluate.bind(this)); + }, + + /** + * @param {function(Object)} callback + */ + getSelectedCallFrameVariables: function(callback) + { + var result = { this: true }; + + var selectedCallFrame = this.selectedCallFrame; + if (!selectedCallFrame) + callback(result); + + var pendingRequests = 0; + + function propertiesCollected(properties) + { + for (var i = 0; properties && i < properties.length; ++i) + result[properties[i].name] = true; + if (--pendingRequests == 0) + callback(result); + } + + for (var i = 0; i < selectedCallFrame.scopeChain.length; ++i) { + var scope = selectedCallFrame.scopeChain[i]; + var object = WebInspector.RemoteObject.fromPayload(scope.object); + pendingRequests++; + object.getAllProperties(propertiesCollected); + } + }, + + /** + * @param {WebInspector.Event} event + */ + _dispatchExecutionLineChanged: function(event) + { + this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.ExecutionLineChanged, this.executionLineLocation); + }, + + /** + * @type {WebInspector.UILocation} + */ + get executionLineLocation() + { + if (!this._selectedCallFrame.rawSourceCode.sourceMapping) + return; + + var rawLocation = this._selectedCallFrame._callFrame.location; + var uiLocation = this._selectedCallFrame.rawSourceCode.sourceMapping.rawLocationToUILocation(rawLocation); + return uiLocation; + }, + + /** + * @param {string} sourceURL + */ + _rawSourceCodeForScriptWithURL: function(sourceURL) + { + return this._rawSourceCodeForURL[sourceURL]; + }, + + /** + * @param {WebInspector.Script} script + */ + _rawSourceCodeForScript: function(script) + { + return this._rawSourceCodeForScriptId[script.scriptId]; + }, + + /** + * @param {WebInspector.RawSourceCode} rawSourceCode + */ + _scriptForRawSourceCode: function(rawSourceCode) + { + /** + * @this {WebInspector.DebuggerPresentationModel} + * @param {WebInspector.Script} script + * @return {boolean} + */ + function filter(script) + { + return script.scriptId === rawSourceCode.id; + } + return WebInspector.debuggerModel.queryScripts(filter.bind(this))[0]; + }, + + _debuggerReset: function() + { + for (var i = 0; i < this._rawSourceCodes.length; ++i) { + var rawSourceCode = this._rawSourceCodes[i]; + if (rawSourceCode.sourceMapping) { + var uiSourceCodeList = rawSourceCode.sourceMapping.uiSourceCodeList(); + for (var j = 0; j < uiSourceCodeList.length; ++j) + this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.UISourceCodeRemoved, uiSourceCodeList[j]); + } + rawSourceCode.removeAllListeners(); + } + this._rawSourceCodes = []; + this._rawSourceCodeForScriptId = {}; + this._rawSourceCodeForURL = {}; + this._rawSourceCodeForDocumentURL = {}; + this._presentationCallFrames = []; + this._selectedCallFrame = null; + this._breakpointManager.debuggerReset(); + this.dispatchEventToListeners(WebInspector.DebuggerPresentationModel.Events.DebuggerReset); + } +} + +WebInspector.DebuggerPresentationModel.prototype.__proto__ = WebInspector.Object.prototype; + +/** + * @constructor + * @param {WebInspector.UISourceCode} uiSourceCode + * @param {number} lineNumber + * @param {WebInspector.ConsoleMessage} originalMessage + */ +WebInspector.PresentationConsoleMessage = function(uiSourceCode, lineNumber, originalMessage) +{ + this.uiSourceCode = uiSourceCode; + this.lineNumber = lineNumber; + this.originalMessage = originalMessage; +} + +/** + * @constructor + * @param {DebuggerAgent.CallFrame} callFrame + * @param {number} index + * @param {WebInspector.DebuggerPresentationModel} model + * @param {WebInspector.RawSourceCode} rawSourceCode + */ +WebInspector.PresentationCallFrame = function(callFrame, index, model, rawSourceCode) +{ + this._callFrame = callFrame; + this._index = index; + this._model = model; + this._rawSourceCode = rawSourceCode; +} + +WebInspector.PresentationCallFrame.prototype = { + /** + * @return {string} + */ + get type() + { + return this._callFrame.type; + }, + + /** + * @return {Array.} + */ + get scopeChain() + { + return this._callFrame.scopeChain; + }, + + /** + * @return {RuntimeAgent.RemoteObject} + */ + get this() + { + return this._callFrame.this; + }, + + /** + * @return {number} + */ + get index() + { + return this._index; + }, + + /** + * @return {WebInspector.RawSourceCode} + */ + get rawSourceCode() + { + return this._rawSourceCode; + }, + + /** + * @param {string} code + * @param {string} objectGroup + * @param {boolean} includeCommandLineAPI + * @param {boolean} returnByValue + * @param {function(?RuntimeAgent.RemoteObject, boolean=)=} callback + */ + evaluate: function(code, objectGroup, includeCommandLineAPI, returnByValue, callback) + { + /** + * @this {WebInspector.PresentationCallFrame} + * @param {?Protocol.Error} error + * @param {RuntimeAgent.RemoteObject} result + * @param {boolean=} wasThrown + */ + function didEvaluateOnCallFrame(error, result, wasThrown) + { + if (error) { + console.error(error); + callback(null, false); + return; + } + callback(result, wasThrown); + } + DebuggerAgent.evaluateOnCallFrame(this._callFrame.callFrameId, code, objectGroup, includeCommandLineAPI, returnByValue, didEvaluateOnCallFrame.bind(this)); + }, + + /** + * @param {function(WebInspector.UILocation)} callback + */ + uiLocation: function(callback) + { + function sourceMappingReady() + { + this._rawSourceCode.removeEventListener(WebInspector.RawSourceCode.Events.SourceMappingUpdated, sourceMappingReady, this); + callback(this._rawSourceCode.sourceMapping.rawLocationToUILocation(this._callFrame.location)); + } + if (this._rawSourceCode.sourceMapping) + sourceMappingReady.call(this); + else + this._rawSourceCode.addEventListener(WebInspector.RawSourceCode.Events.SourceMappingUpdated, sourceMappingReady, this); + } +} + +/** + * @constructor + * @extends {WebInspector.Placard} + * @param {WebInspector.PresentationCallFrame} callFrame + */ +WebInspector.DebuggerPresentationModel.CallFramePlacard = function(callFrame) +{ + WebInspector.Placard.call(this, callFrame._callFrame.functionName || WebInspector.UIString("(anonymous function)"), ""); + this._callFrame = callFrame; + var rawSourceCode = callFrame._rawSourceCode; + if (rawSourceCode.sourceMapping) + this._update(); + rawSourceCode.addEventListener(WebInspector.RawSourceCode.Events.SourceMappingUpdated, this._update, this); +} + +WebInspector.DebuggerPresentationModel.CallFramePlacard.prototype = { + discard: function() + { + this._callFrame._rawSourceCode.removeEventListener(WebInspector.RawSourceCode.Events.SourceMappingUpdated, this._update, this); + }, + + _update: function() + { + var rawSourceCode = this._callFrame._rawSourceCode; + var uiLocation = rawSourceCode.sourceMapping.rawLocationToUILocation(this._callFrame._callFrame.location); + this.subtitle = WebInspector.displayNameForURL(uiLocation.uiSourceCode.url) + ":" + (uiLocation.lineNumber + 1); + } +} + +WebInspector.DebuggerPresentationModel.CallFramePlacard.prototype.__proto__ = WebInspector.Placard.prototype; + +/** + * @constructor + * @implements {WebInspector.ResourceDomainModelBinding} + * @param {WebInspector.DebuggerPresentationModel} model + */ +WebInspector.DebuggerPresentationModelResourceBinding = function(model) +{ + this._presentationModel = model; + WebInspector.Resource.registerDomainModelBinding(WebInspector.Resource.Type.Script, this); +} + +WebInspector.DebuggerPresentationModelResourceBinding.prototype = { + /** + * @param {WebInspector.Resource} resource + */ + canSetContent: function(resource) + { + var rawSourceCode = this._presentationModel._rawSourceCodeForScriptWithURL(resource.url) + if (!rawSourceCode) + return false; + return this._presentationModel.canEditScriptSource(rawSourceCode.sourceMapping.uiSourceCodeList()[0]); + }, + + /** + * @param {WebInspector.Resource} resource + * @param {string} content + * @param {boolean} majorChange + * @param {function(?string)} userCallback + */ + setContent: function(resource, content, majorChange, userCallback) + { + if (!majorChange) + return; + + var rawSourceCode = this._presentationModel._rawSourceCodeForScriptWithURL(resource.url); + if (!rawSourceCode) { + userCallback("Resource is not editable"); + return; + } + + resource.requestContent(this._setContentWithInitialContent.bind(this, rawSourceCode.sourceMapping.uiSourceCodeList()[0], content, userCallback)); + }, + + /** + * @param {WebInspector.UISourceCode} uiSourceCode + * @param {string} content + * @param {function(?string)} userCallback + * @param {?string} oldContent + * @param {?string} oldContentEncoded + */ + _setContentWithInitialContent: function(uiSourceCode, content, userCallback, oldContent, oldContentEncoded) + { + /** + * @this {WebInspector.DebuggerPresentationModelResourceBinding} + * @param {?string} error + */ + function callback(error) + { + if (userCallback) + userCallback(error); + if (!error) + this._presentationModel._updateBreakpointsAfterLiveEdit(uiSourceCode, oldContent || "", content); + } + this._presentationModel.setScriptSource(uiSourceCode, content, callback.bind(this)); + } +} + +/** + * @interface + */ +WebInspector.DebuggerPresentationModel.LinkifierFormatter = function() +{ +} + +WebInspector.DebuggerPresentationModel.LinkifierFormatter.prototype = { + /** + * @param {WebInspector.RawSourceCode} rawSourceCode + * @param {Element} anchor + */ + formatRawSourceCodeAnchor: function(rawSourceCode, anchor) { }, +} + +/** + * @constructor + * @implements {WebInspector.DebuggerPresentationModel.LinkifierFormatter} + * @param {number=} maxLength + */ +WebInspector.DebuggerPresentationModel.DefaultLinkifierFormatter = function(maxLength) +{ + this._maxLength = maxLength; +} + +WebInspector.DebuggerPresentationModel.DefaultLinkifierFormatter.prototype = { + /** + * @param {WebInspector.RawSourceCode} rawSourceCode + * @param {Element} anchor + */ + formatRawSourceCodeAnchor: function(rawSourceCode, anchor) + { + var uiLocation = rawSourceCode.sourceMapping.rawLocationToUILocation(anchor.rawLocation); + + anchor.textContent = WebInspector.formatLinkText(uiLocation.uiSourceCode.url, uiLocation.lineNumber); + + var text = WebInspector.formatLinkText(uiLocation.uiSourceCode.url, uiLocation.lineNumber); + if (this._maxLength) + text = text.trimMiddle(this._maxLength); + anchor.textContent = text; + } +} + +WebInspector.DebuggerPresentationModel.DefaultLinkifierFormatter.prototype.__proto__ = WebInspector.DebuggerPresentationModel.LinkifierFormatter.prototype; + +/** + * @constructor + * @param {WebInspector.DebuggerPresentationModel} model + * @param {WebInspector.DebuggerPresentationModel.LinkifierFormatter=} formatter + */ +WebInspector.DebuggerPresentationModel.Linkifier = function(model, formatter) +{ + this._model = model; + this._formatter = formatter || new WebInspector.DebuggerPresentationModel.DefaultLinkifierFormatter(); + this._anchorsForRawSourceCode = {}; +} + +WebInspector.DebuggerPresentationModel.Linkifier.prototype = { + /** + * @param {string} sourceURL + * @param {number} lineNumber + * @param {number=} columnNumber + * @param {string=} classes + */ + linkifyLocation: function(sourceURL, lineNumber, columnNumber, classes) + { + var rawSourceCode = this._model._rawSourceCodeForScriptWithURL(sourceURL); + if (!rawSourceCode) + return WebInspector.linkifyResourceAsNode(sourceURL, lineNumber, classes); + + return this.linkifyRawSourceCode(rawSourceCode, lineNumber, columnNumber, classes); + }, + + /** + * @param {WebInspector.RawSourceCode} rawSourceCode + * @param {number=} lineNumber + * @param {number=} columnNumber + * @param {string=} classes + */ + linkifyRawSourceCode: function(rawSourceCode, lineNumber, columnNumber, classes) + { + var anchor = WebInspector.linkifyURLAsNode(rawSourceCode.url, "", classes, false); + anchor.rawLocation = { lineNumber: lineNumber, columnNumber: columnNumber }; + + var anchors = this._anchorsForRawSourceCode[rawSourceCode.id]; + if (!anchors) { + anchors = []; + this._anchorsForRawSourceCode[rawSourceCode.id] = anchors; + rawSourceCode.addEventListener(WebInspector.RawSourceCode.Events.SourceMappingUpdated, this._updateSourceAnchors, this); + } + + if (rawSourceCode.sourceMapping) + this._updateAnchor(rawSourceCode, anchor); + anchors.push(anchor); + return anchor; + }, + + reset: function() + { + for (var id in this._anchorsForRawSourceCode) { + if (this._model._rawSourceCodeForScriptId[id]) // In case of navigation the list of rawSourceCodes is empty. + this._model._rawSourceCodeForScriptId[id].removeEventListener(WebInspector.RawSourceCode.Events.SourceMappingUpdated, this._updateSourceAnchors, this); + } + this._anchorsForRawSourceCode = {}; + }, + + /** + * @param {WebInspector.Event} event + */ + _updateSourceAnchors: function(event) + { + var rawSourceCode = /** @type {WebInspector.RawSourceCode} */ event.target; + var anchors = this._anchorsForRawSourceCode[rawSourceCode.id]; + for (var i = 0; i < anchors.length; ++i) + this._updateAnchor(rawSourceCode, anchors[i]); + }, + + /** + * @param {WebInspector.RawSourceCode} rawSourceCode + * @param {Element} anchor + */ + _updateAnchor: function(rawSourceCode, anchor) + { + var uiLocation = rawSourceCode.sourceMapping.rawLocationToUILocation(anchor.rawLocation); + anchor.preferredPanel = "scripts"; + anchor.uiSourceCode = uiLocation.uiSourceCode; + anchor.lineNumber = uiLocation.lineNumber; + + this._formatter.formatRawSourceCodeAnchor(rawSourceCode, anchor); + } +} + +WebInspector.DebuggerPresentationModelResourceBinding.prototype.__proto__ = WebInspector.ResourceDomainModelBinding.prototype; + +/** + * @type {?WebInspector.DebuggerPresentationModel} + */ +WebInspector.debuggerPresentationModel = null; +/* BreakpointManager.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @param {WebInspector.Setting} breakpointStorage + * @param {function(WebInspector.Breakpoint)} breakpointAddedDelegate + * @param {function(WebInspector.Breakpoint)} breakpointRemovedDelegate + * @param {WebInspector.DebuggerModel} debuggerModel + */ +WebInspector.BreakpointManager = function(breakpointStorage, breakpointAddedDelegate, breakpointRemovedDelegate, debuggerModel) +{ + this._breakpointStorage = breakpointStorage; + this._breakpointAddedDelegate = breakpointAddedDelegate; + this._breakpointRemovedDelegate = breakpointRemovedDelegate; + /** + * @type {Object.>} + */ + this._breakpointsByUILocation = {}; + + this._debuggerModel = debuggerModel; + + /** + * @type {Object.} + */ + this._breakpointsByDebuggerId = {}; + this._debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.BreakpointResolved, this._breakpointResolved, this); + + var breakpoints = this._breakpointStorage.get(); + for (var i = 0; i < breakpoints.length; ++i) { + var breakpoint = WebInspector.Breakpoint.deserialize(breakpoints[i]); + if (!this._breakpoint(breakpoint.uiSourceCodeId, breakpoint.lineNumber)) + this._addBreakpointToUI(breakpoint); + } +} + +WebInspector.BreakpointManager.prototype = { + /** + * @param {WebInspector.UISourceCode} uiSourceCode + */ + uiSourceCodeAdded: function(uiSourceCode) + { + var breakpoints = this._breakpoints(uiSourceCode.id); + for (var lineNumber in breakpoints) { + var breakpoint = breakpoints[lineNumber]; + breakpoint.uiSourceCode = uiSourceCode; + this._materializeBreakpoint(breakpoint, uiSourceCode.rawSourceCode.sourceMapping, uiSourceCode); + if (breakpoint._debuggerLocation) + this._breakpointDebuggerLocationChanged(breakpoint); + } + }, + + /** + * @param {WebInspector.UISourceCode} uiSourceCode + */ + breakpointsForUISourceCode: function(uiSourceCode) + { + return this._breakpoints(uiSourceCode.id); + }, + + /** + * @param {WebInspector.UISourceCode} uiSourceCode + * @param {number} lineNumber + * @param {string} condition + * @param {boolean} enabled + */ + setBreakpoint: function(uiSourceCode, lineNumber, condition, enabled) + { + if (this._breakpoint(uiSourceCode.id, lineNumber)) + return; + + var persistent = !!uiSourceCode.url; + var breakpoint = new WebInspector.Breakpoint(uiSourceCode.id, lineNumber, condition, enabled, persistent); + breakpoint.uiSourceCode = uiSourceCode; + this._addBreakpointToUI(breakpoint); + this._materializeBreakpoint(breakpoint, uiSourceCode.rawSourceCode.sourceMapping, uiSourceCode); + }, + + /** + * @param {WebInspector.UISourceCode} uiSourceCode + * @param {number} lineNumber + */ + removeBreakpoint: function(uiSourceCode, lineNumber) + { + var breakpoint = this._breakpoint(uiSourceCode.id, lineNumber); + if (!breakpoint) + return; + this._removeBreakpoint(breakpoint); + }, + + /** + * @param {WebInspector.Breakpoint} breakpoint + */ + _removeBreakpoint: function(breakpoint) + { + this._deleteBreakpointFromUI(breakpoint); + this._removeBreakpointFromDebugger(breakpoint); + }, + + removeAllBreakpoints: function() + { + this._forEachBreakpoint(this._removeBreakpoint.bind(this)); + }, + + /** + * @param {WebInspector.Breakpoint} breakpoint + * @param {WebInspector.RawSourceCode.SourceMapping} sourceMapping + * @param {WebInspector.UISourceCode} uiSourceCode + */ + _materializeBreakpoint: function(breakpoint, sourceMapping, uiSourceCode) + { + if (!breakpoint.enabled || breakpoint._materialized) + return; + + breakpoint._materialized = true; + var rawLocation = sourceMapping.uiLocationToRawLocation(uiSourceCode, breakpoint.lineNumber, 0); + this._setBreakpointInDebugger(breakpoint, rawLocation); + }, + + /** + * @param {WebInspector.Breakpoint} breakpoint + */ + _breakpointDebuggerLocationChanged: function(breakpoint) + { + if (!breakpoint.uiSourceCode) + return; + var uiLocation = breakpoint.uiSourceCode.rawSourceCode.sourceMapping.rawLocationToUILocation(breakpoint._debuggerLocation); + if (uiLocation.lineNumber === breakpoint.lineNumber) + return; + + if (!this._moveBreakpointInUI(breakpoint, uiLocation.lineNumber)) + this._removeBreakpointFromDebugger(breakpoint); + }, + + /** + * @param {WebInspector.Breakpoint} breakpoint + */ + _addBreakpointToUI: function(breakpoint) + { + console.assert(!this._breakpoint(breakpoint.uiSourceCodeId, breakpoint.lineNumber)); + this._breakpoints(breakpoint.uiSourceCodeId)[breakpoint.lineNumber] = breakpoint; + this._saveBreakpoints(); + this._breakpointAddedDelegate(breakpoint); + }, + + /** + * @param {WebInspector.Breakpoint} breakpoint + */ + _deleteBreakpointFromUI: function(breakpoint) + { + console.assert(this._breakpoint(breakpoint.uiSourceCodeId, breakpoint.lineNumber) === breakpoint); + delete this._breakpoints(breakpoint.uiSourceCodeId)[breakpoint.lineNumber]; + this._saveBreakpoints(); + this._breakpointRemovedDelegate(breakpoint); + }, + + /** + * @param {WebInspector.Breakpoint} breakpoint + * @param {number} lineNumber + * @return {boolean} + */ + _moveBreakpointInUI: function(breakpoint, lineNumber) + { + this._deleteBreakpointFromUI(breakpoint); + if (this._breakpoint(breakpoint.uiSourceCodeId, lineNumber)) + return false; + breakpoint.lineNumber = lineNumber; + this._addBreakpointToUI(breakpoint); + return true; + }, + + /** + * @param {string} uiSourceCodeId + * @return {?Object.} + */ + _breakpoints: function(uiSourceCodeId) + { + if (!this._breakpointsByUILocation[uiSourceCodeId]) + this._breakpointsByUILocation[uiSourceCodeId] = {}; + return this._breakpointsByUILocation[uiSourceCodeId]; + }, + + /** + * @param {string} uiSourceCodeId + * @param {number} lineNumber + * @return {?WebInspector.Breakpoint} + */ + _breakpoint: function(uiSourceCodeId, lineNumber) + { + return this._breakpoints(uiSourceCodeId)[String(lineNumber)]; + }, + + /** + * @param {function(WebInspector.Breakpoint)} handler + */ + _forEachBreakpoint: function(handler) + { + for (var uiSourceCodeId in this._breakpointsByUILocation) { + var breakpoints = this._breakpointsByUILocation[uiSourceCodeId]; + for (var lineNumber in breakpoints) + handler(breakpoints[lineNumber]); + } + }, + + /** + * @param {WebInspector.Breakpoint} breakpoint + * @param {DebuggerAgent.Location} rawLocation + */ + _setBreakpointInDebugger: function(breakpoint, rawLocation) + { + /** + * @this {WebInspector.BreakpointManager} + * @param {DebuggerAgent.BreakpointId} breakpointId + * @param {Array.} locations + */ + function didSetBreakpoint(breakpointId, locations) + { + if (breakpoint === this._breakpoint(breakpoint.uiSourceCodeId, breakpoint.lineNumber)) { + if (!breakpointId) { + this._deleteBreakpointFromUI(breakpoint); + return; + } + } else { + if (breakpointId) + this._debuggerModel.removeBreakpoint(breakpointId); + return; + } + + this._breakpointsByDebuggerId[breakpointId] = breakpoint; + breakpoint._debuggerId = breakpointId; + breakpoint._debuggerLocation = locations[0]; + if (breakpoint._debuggerLocation) + this._breakpointDebuggerLocationChanged(breakpoint); + } + this._debuggerModel.setBreakpointByScriptLocation(rawLocation, breakpoint.condition, didSetBreakpoint.bind(this)); + }, + + /** + * @param {WebInspector.Breakpoint} breakpoint + */ + _removeBreakpointFromDebugger: function(breakpoint) + { + if (!("_debuggerId" in breakpoint)) + return; + this._debuggerModel.removeBreakpoint(breakpoint._debuggerId); + delete this._breakpointsByDebuggerId[breakpoint._debuggerId]; + delete breakpoint._debuggerId; + delete breakpoint._debuggerLocation; + }, + + /** + * @param {WebInspector.Event} event + */ + _breakpointResolved: function(event) + { + var breakpoint = this._breakpointsByDebuggerId[event.data["breakpointId"]]; + breakpoint._debuggerLocation = event.data["location"]; + this._breakpointDebuggerLocationChanged(breakpoint); + }, + + _saveBreakpoints: function() + { + var serializedBreakpoints = []; + /** + * @this {WebInspector.BreakpointManager} + * @param {WebInspector.Breakpoint} breakpoint + */ + function serializePersistent(breakpoint) + { + if (breakpoint.persistent) + serializedBreakpoints.push(breakpoint.serialize()); + } + this._forEachBreakpoint(serializePersistent.bind(this)); + this._breakpointStorage.set(serializedBreakpoints); + }, + + reset: function() + { + /** + * @this {WebInspector.BreakpointManager} + * @param {WebInspector.Breakpoint} breakpoint + */ + function resetBreakpoint(breakpoint) + { + this._removeBreakpointFromDebugger(breakpoint); + delete breakpoint._materialized; + } + this._forEachBreakpoint(resetBreakpoint.bind(this)); + }, + + debuggerReset: function() + { + /** + * @this {WebInspector.BreakpointManager} + * @param {WebInspector.Breakpoint} breakpoint + */ + function resetOrDeleteBreakpoint(breakpoint) + { + if (breakpoint.persistent) { + delete breakpoint.uiSourceCode; + delete breakpoint._debuggerLocation; + } else { + this._deleteBreakpointFromUI(breakpoint); + delete this._breakpointsByDebuggerId[breakpoint._debuggerId]; + } + } + this._forEachBreakpoint(resetOrDeleteBreakpoint.bind(this)); + + for (var id in this._breakpointsByUILocation) { + var empty = true; + for (var lineNumber in this._breakpointsByUILocation[id]) { + empty = false; + break; + } + if (empty) + delete this._breakpointsByUILocation[id]; + } + } +} + +/** + * @constructor + * @param {string} uiSourceCodeId + * @param {number} lineNumber + * @param {string} condition + * @param {boolean} enabled + * @param {boolean} persistent + */ +WebInspector.Breakpoint = function(uiSourceCodeId, lineNumber, condition, enabled, persistent) +{ + this.uiSourceCodeId = uiSourceCodeId; + this.lineNumber = lineNumber; + this.condition = condition; + this.enabled = enabled; + this.persistent = persistent; +} + +WebInspector.Breakpoint.prototype = { + /** + * @return {Object} + */ + serialize: function() + { + var serializedBreakpoint = {}; + serializedBreakpoint.sourceFileId = this.uiSourceCodeId; + serializedBreakpoint.lineNumber = this.lineNumber; + serializedBreakpoint.condition = this.condition; + serializedBreakpoint.enabled = this.enabled; + return serializedBreakpoint; + } +} + +/** + * @param {Object} serializedBreakpoint + * @return {WebInspector.Breakpoint} + */ +WebInspector.Breakpoint.deserialize = function(serializedBreakpoint) +{ + return new WebInspector.Breakpoint( + serializedBreakpoint.sourceFileId, + serializedBreakpoint.lineNumber, + serializedBreakpoint.condition, + serializedBreakpoint.enabled, + true); +} +/* UISourceCode.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.Object} + * @param {string} id + * @param {string} url + * @param {WebInspector.RawSourceCode} rawSourceCode + * @param {WebInspector.ContentProvider} contentProvider + */ +WebInspector.UISourceCode = function(id, url, rawSourceCode, contentProvider) +{ + this._id = id; + this._url = url; + this._rawSourceCode = rawSourceCode; + this._contentProvider = contentProvider; + this.isContentScript = false; + /** + * @type Array. + */ + this._requestContentCallbacks = []; +} + +WebInspector.UISourceCode.Events = { + ContentChanged: "content-changed" +} + +WebInspector.UISourceCode.prototype = { + /** + * @return {string} + */ + get id() + { + return this._id; + }, + + /** + * @return {string} + */ + get url() + { + return this._url; + }, + + /** + * @return {WebInspector.RawSourceCode} + */ + get rawSourceCode() + { + return this._rawSourceCode; + }, + + /** + * @param {function(string,string)} callback + */ + requestContent: function(callback) + { + if (this._contentLoaded) { + callback(this._mimeType, this._content); + return; + } + + this._requestContentCallbacks.push(callback); + if (this._requestContentCallbacks.length === 1) + this._contentProvider.requestContent(this._didRequestContent.bind(this)); + }, + + /** + * @param {string} newContent + */ + contentChanged: function(newContent) + { + console.assert(this._contentLoaded); + this._content = newContent; + this.dispatchEventToListeners(WebInspector.UISourceCode.Events.ContentChanged); + }, + + /** + * @param {string} query + * @param {boolean} caseSensitive + * @param {boolean} isRegex + * @param {function(Array.)} callback + */ + searchInContent: function(query, caseSensitive, isRegex, callback) + { + this._contentProvider.searchInContent(query, caseSensitive, isRegex, callback); + }, + + /** + * @type {string} + */ + get domain() + { + if (typeof(this._domain) === "undefined") + this._parseURL(); + + return this._domain; + }, + + /** + * @type {string} + */ + get folderName() + { + if (typeof(this._folderName) === "undefined") + this._parseURL(); + + return this._folderName; + }, + + /** + * @type {string} + */ + get fileName() + { + if (typeof(this._fileName) === "undefined") + this._parseURL(); + + return this._fileName; + }, + + /** + * @type {string} + */ + get displayName() + { + if (typeof(this._displayName) === "undefined") + this._parseURL(); + + return this._displayName; + }, + + _parseURL: function() + { + var parsedURL = this.url.asParsedURL(); + var url = parsedURL ? parsedURL.path : this.url; + + var folderName = ""; + var fileName = url; + + var pathLength = fileName.indexOf("?"); + if (pathLength === -1) + pathLength = fileName.length; + + var fromIndex = fileName.lastIndexOf("/", pathLength - 2); + if (fromIndex !== -1) { + folderName = fileName.substring(0, fromIndex); + fileName = fileName.substring(fromIndex + 1); + } + + var indexOfQuery = fileName.indexOf("?"); + if (indexOfQuery === -1) + indexOfQuery = fileName.length; + var lastPathComponent = fileName.substring(0, indexOfQuery); + var queryParams = fileName.substring(indexOfQuery, fileName.length); + + const maxDisplayNameLength = 30; + const minDisplayQueryParamLength = 5; + + var maxDisplayQueryParamLength = Math.max(minDisplayQueryParamLength, maxDisplayNameLength - lastPathComponent.length); + var displayQueryParams = queryParams.trimEnd(maxDisplayQueryParamLength); + var displayLastPathComponent = lastPathComponent.trimMiddle(maxDisplayNameLength - displayQueryParams.length); + var displayName = displayLastPathComponent + displayQueryParams; + if (!displayName) + displayName = WebInspector.UIString("(program)"); + + if (folderName.length > 80) + folderName = "\u2026" + folderName.substring(folderName.length - 80); + + this._domain = parsedURL ? parsedURL.host : ""; + this._folderName = folderName; + this._fileName = fileName; + this._displayName = displayName; + }, + + /** + * @param {string} mimeType + * @param {string} content + */ + _didRequestContent: function(mimeType, content) + { + this._contentLoaded = true; + this._mimeType = mimeType; + this._content = content; + + for (var i = 0; i < this._requestContentCallbacks.length; ++i) + this._requestContentCallbacks[i](mimeType, content); + this._requestContentCallbacks = []; + } +} + +WebInspector.UISourceCode.prototype.__proto__ = WebInspector.Object.prototype; + +/** + * @interface + */ +WebInspector.ContentProvider = function() { } +WebInspector.ContentProvider.prototype = { + /** + * @param {function(string,string)} callback + */ + requestContent: function(callback) { }, + + /** + * @param {string} query + * @param {boolean} caseSensitive + * @param {boolean} isRegex + * @param {function(Array.)} callback + */ + searchInContent: function(query, caseSensitive, isRegex, callback) { } +} + +/** + * @constructor + * @param {number} lineNumber + * @param {string} lineContent + */ +WebInspector.ContentProvider.SearchMatch = function(lineNumber, lineContent) { + this.lineNumber = lineNumber; + this.lineContent = lineContent; +} +/* ContentProviders.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @implements {WebInspector.ContentProvider} + */ +WebInspector.ScriptContentProvider = function(script) +{ + this._mimeType = "text/javascript"; + this._script = script; +} + +WebInspector.ScriptContentProvider.prototype = { + /** + * @param {function(string,string)} callback + */ + requestContent: function(callback) + { + function didRequestSource(source) + { + callback(this._mimeType, source); + } + this._script.requestSource(didRequestSource.bind(this)); + }, + + /** + * @param {string} query + * @param {boolean} caseSensitive + * @param {boolean} isRegex + * @param {function(Array.)} callback + */ + searchInContent: function(query, caseSensitive, isRegex, callback) + { + this._script.searchInContent(query, caseSensitive, isRegex, callback); + } +} + +WebInspector.ScriptContentProvider.prototype.__proto__ = WebInspector.ContentProvider.prototype; + +/** + * @constructor + * @implements {WebInspector.ContentProvider} + */ +WebInspector.ConcatenatedScriptsContentProvider = function(scripts) +{ + this._mimeType = "text/html"; + this._scripts = scripts; +} + +WebInspector.ConcatenatedScriptsContentProvider.scriptOpenTag = ""; + +WebInspector.ConcatenatedScriptsContentProvider.prototype = { + /** + * @return {Array.} + */ + _sortedScripts: function() + { + if (this._sortedScriptsArray) + return this._sortedScriptsArray; + + this._sortedScriptsArray = []; + + var scripts = this._scripts.slice(); + scripts.sort(function(x, y) { return x.lineOffset - y.lineOffset || x.columnOffset - y.columnOffset; }); + + var scriptOpenTagLength = WebInspector.ConcatenatedScriptsContentProvider.scriptOpenTag.length; + var scriptCloseTagLength = WebInspector.ConcatenatedScriptsContentProvider.scriptCloseTag.length; + + this._sortedScriptsArray.push(scripts[0]); + for (var i = 1; i < scripts.length; ++i) { + var previousScript = this._sortedScriptsArray[this._sortedScriptsArray.length - 1]; + + var lineNumber = previousScript.endLine; + var columnNumber = previousScript.endColumn + scriptCloseTagLength + scriptOpenTagLength; + + if (lineNumber < scripts[i].lineOffset || (lineNumber === scripts[i].lineOffset && columnNumber <= scripts[i].columnOffset)) + this._sortedScriptsArray.push(scripts[i]); + } + return this._sortedScriptsArray; + }, + + /** + * @param {function(string,string)} callback + */ + requestContent: function(callback) + { + var scripts = this._sortedScripts(); + var sources = []; + function didRequestSource(source) + { + sources.push(source); + if (sources.length == scripts.length) + callback(this._mimeType, this._concatenateScriptsContent(scripts, sources)); + } + for (var i = 0; i < scripts.length; ++i) + scripts[i].requestSource(didRequestSource.bind(this)); + }, + + /** + * @param {string} query + * @param {boolean} caseSensitive + * @param {boolean} isRegex + * @param {function(Array.)} callback + */ + searchInContent: function(query, caseSensitive, isRegex, callback) + { + var results = {}; + var scripts = this._sortedScripts(); + var scriptsLeft = scripts.length; + + function maybeCallback() + { + if (scriptsLeft) + return; + + var result = []; + for (var i = 0; i < scripts.length; ++i) + result = result.concat(results[scripts[i].scriptId]); + callback(result); + } + + /** + * @param {WebInspector.Script} script + * @param {Array.} searchMatches + */ + function searchCallback(script, searchMatches) + { + results[script.scriptId] = []; + for (var i = 0; i < searchMatches.length; ++i) { + var searchMatch = new WebInspector.ContentProvider.SearchMatch(searchMatches[i].lineNumber + script.lineOffset, searchMatches[i].lineContent); + results[script.scriptId].push(searchMatch); + } + scriptsLeft--; + maybeCallback.call(this); + } + + maybeCallback(); + for (var i = 0; i < scripts.length; ++i) + scripts[i].searchInContent(query, caseSensitive, isRegex, searchCallback.bind(this, scripts[i])); + }, + + /** + * @return {string} + */ + _concatenateScriptsContent: function(scripts, sources) + { + var content = ""; + var lineNumber = 0; + var columnNumber = 0; + + var scriptOpenTag = WebInspector.ConcatenatedScriptsContentProvider.scriptOpenTag; + var scriptCloseTag = WebInspector.ConcatenatedScriptsContentProvider.scriptCloseTag; + for (var i = 0; i < scripts.length; ++i) { + // Fill the gap with whitespace characters. + for (var newLinesCount = scripts[i].lineOffset - lineNumber; newLinesCount > 0; --newLinesCount) { + columnNumber = 0; + content += "\n"; + } + for (var spacesCount = scripts[i].columnOffset - columnNumber - scriptOpenTag.length; spacesCount > 0; --spacesCount) + content += " "; + + // Add script tag. + content += scriptOpenTag; + content += sources[i]; + content += scriptCloseTag; + lineNumber = scripts[i].endLine; + columnNumber = scripts[i].endColumn + scriptCloseTag.length; + } + + return content; + } +} + +WebInspector.ConcatenatedScriptsContentProvider.prototype.__proto__ = WebInspector.ContentProvider.prototype; + +/** + * @constructor + * @implements {WebInspector.ContentProvider} + */ +WebInspector.ResourceContentProvider = function(resource) +{ + this._mimeType = resource.type === WebInspector.Resource.Type.Script ? "text/javascript" : "text/html"; + this._resource = resource; +}; + +WebInspector.ResourceContentProvider.prototype = { + /** + * @param {function(string,string)} callback + */ + requestContent: function(callback) + { + function didRequestContent(content) + { + callback(this._mimeType, content); + } + this._resource.requestContent(didRequestContent.bind(this)); + }, + + /** + * @param {string} query + * @param {boolean} caseSensitive + * @param {boolean} isRegex + * @param {function(Array.)} callback + */ + searchInContent: function(query, caseSensitive, isRegex, callback) + { + this._resource.searchInContent(query, caseSensitive, isRegex, callback); + } +} + +WebInspector.ResourceContentProvider.prototype.__proto__ = WebInspector.ContentProvider.prototype; + +/** + * @constructor + * @implements {WebInspector.ContentProvider} + */ +WebInspector.CompilerSourceMappingContentProvider = function(sourceURL, compilerSourceMapping) +{ + this._mimeType = "text/javascript"; + this._sourceURL = sourceURL; + this._compilerSourceMapping = compilerSourceMapping; +} + +WebInspector.CompilerSourceMappingContentProvider.prototype = { + /** + * @param {function(string,string)} callback + */ + requestContent: function(callback) + { + var sourceCode = this._compilerSourceMapping.loadSourceCode(this._sourceURL); + callback(this._mimeType, sourceCode); + }, + + /** + * @param {string} query + * @param {boolean} caseSensitive + * @param {boolean} isRegex + * @param {function(Array.)} callback + */ + searchInContent: function(query, caseSensitive, isRegex, callback) + { + callback([]); + } +} + +WebInspector.CompilerSourceMappingContentProvider.prototype.__proto__ = WebInspector.ContentProvider.prototype; + +/** + * @constructor + * @implements {WebInspector.ContentProvider} + */ +WebInspector.StaticContentProvider = function(mimeType, content) +{ + this._mimeType = mimeType; + this._content = content; +} + +WebInspector.StaticContentProvider.prototype = { + /** + * @param {function(string,string)} callback + */ + requestContent: function(callback) + { + callback(this._mimeType, this._content); + }, + + /** + * @param {string} query + * @param {boolean} caseSensitive + * @param {boolean} isRegex + * @param {function(Array.)} callback + */ + searchInContent: function(query, caseSensitive, isRegex, callback) + { + function performSearch() + { + var regex = createSearchRegex(query, caseSensitive, isRegex); + + var result = []; + var lineEndings = this._content.lineEndings(); + for (var i = 0; i < lineEndings.length; ++i) { + var lineStart = i > 0 ? lineEndings[i - 1] + 1 : 0; + var lineEnd = lineEndings[i]; + var lineContent = this._content.substring(lineStart, lineEnd); + if (lineContent.length > 0 && lineContent.charAt(lineContent.length - 1) === "\r") + lineContent = lineContent.substring(0, lineContent.length - 1) + + if (regex.exec(lineContent)) + result.push(new WebInspector.ContentProvider.SearchMatch(i, lineContent)); + } + callback(result); + } + + // searchInContent should call back later. + window.setTimeout(performSearch.bind(this), 0); + } +} + +WebInspector.StaticContentProvider.prototype.__proto__ = WebInspector.ContentProvider.prototype; +/* RawSourceCode.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// RawSourceCode represents JavaScript resource or HTML resource with inlined scripts +// as it came from network. + +/** + * @constructor + * @extends {WebInspector.Object} + * @param {string} id + * @param {WebInspector.Script} script + * @param {WebInspector.Resource} resource + * @param {WebInspector.ScriptFormatter} formatter + * @param {boolean} formatted + * @param {WebInspector.CompilerSourceMapping} compilerSourceMapping + */ +WebInspector.RawSourceCode = function(id, script, resource, formatter, formatted, compilerSourceMapping) +{ + this.id = id; + this.url = script.sourceURL; + this.isContentScript = script.isContentScript; + this._scripts = [script]; + this._formatter = formatter; + this._formatted = formatted; + this._compilerSourceMapping = compilerSourceMapping; + this._resource = resource; + this.messages = []; + + this._useTemporaryContent = !this._compilerSourceMapping && this._resource && !this._resource.finished; + this._hasNewScripts = true; + if (!this._useTemporaryContent) + this._updateSourceMapping(); + else if (this._resource) + this._resource.addEventListener("finished", this._resourceFinished.bind(this)); +} + +WebInspector.RawSourceCode.Events = { + SourceMappingUpdated: "source-mapping-updated" +} + +WebInspector.RawSourceCode.prototype = { + /** + * @param {WebInspector.Script} script + */ + addScript: function(script) + { + this._scripts.push(script); + this._hasNewScripts = true; + }, + + /** + * @return {WebInspector.RawSourceCode.SourceMapping} + */ + get sourceMapping() + { + return this._sourceMapping; + }, + + /** + * @param {boolean} formatted + */ + setFormatted: function(formatted) + { + if (this._formatted === formatted) + return; + this._formatted = formatted; + if (!this._compilerSourceMapping) + this._updateSourceMapping(); + }, + + _resourceFinished: function() + { + if (this._compilerSourceMapping) + return; + this._useTemporaryContent = false; + this._updateSourceMapping(); + }, + + /** + * @param {number} lineNumber + * @param {number=} columnNumber + * @return {WebInspector.Script} + */ + _scriptForRawLocation: function(lineNumber, columnNumber) + { + var closestScript = this._scripts[0]; + for (var i = 1; i < this._scripts.length; ++i) { + var script = this._scripts[i]; + if (script.lineOffset > lineNumber || (script.lineOffset === lineNumber && script.columnOffset > columnNumber)) + continue; + if (script.lineOffset > closestScript.lineOffset || + (script.lineOffset === closestScript.lineOffset && script.columnOffset > closestScript.columnOffset)) + closestScript = script; + } + return closestScript; + }, + + /** + * @param {WebInspector.Script} script + */ + forceUpdateSourceMapping: function(script) + { + if (!this._useTemporaryContent || !this._hasNewScripts) + return; + this._hasNewScripts = false; + this._updateSourceMapping(); + }, + + _updateSourceMapping: function() + { + if (this._updatingSourceMapping) { + this._updateNeeded = true; + return; + } + this._updatingSourceMapping = true; + this._updateNeeded = false; + + this._createSourceMapping(didCreateSourceMapping.bind(this)); + + /** + * @this {WebInspector.RawSourceCode} + * @param {WebInspector.RawSourceCode.SourceMapping} sourceMapping + */ + function didCreateSourceMapping(sourceMapping) + { + this._updatingSourceMapping = false; + if (sourceMapping && !this._updateNeeded) + this._saveSourceMapping(sourceMapping); + else + this._updateSourceMapping(); + } + }, + + _createContentProvider: function() + { + if (this._resource && this._resource.finished) + return new WebInspector.ResourceContentProvider(this._resource); + if (this._scripts.length === 1 && !this._scripts[0].lineOffset && !this._scripts[0].columnOffset) + return new WebInspector.ScriptContentProvider(this._scripts[0]); + return new WebInspector.ConcatenatedScriptsContentProvider(this._scripts); + }, + + /** + * @param {function(WebInspector.RawSourceCode.SourceMapping)} callback + */ + _createSourceMapping: function(callback) + { + if (this._compilerSourceMapping) { + var success = this._compilerSourceMapping.load(); + if (!success) { + delete this._compilerSourceMapping; + callback(null); + return; + } + var uiSourceCodeList = []; + var sourceURLs = this._compilerSourceMapping.sources(); + for (var i = 0; i < sourceURLs.length; ++i) { + var sourceURL = sourceURLs[i]; + var contentProvider = new WebInspector.CompilerSourceMappingContentProvider(sourceURL, this._compilerSourceMapping); + var uiSourceCode = this._createUISourceCode(sourceURL, sourceURL, contentProvider); + uiSourceCodeList.push(uiSourceCode); + } + var sourceMapping = new WebInspector.RawSourceCode.CompilerSourceMapping(this, uiSourceCodeList, this._compilerSourceMapping); + callback(sourceMapping); + return; + } + + var originalContentProvider = this._createContentProvider(); + if (!this._formatted) { + var uiSourceCode = this._createUISourceCode(this.url, this.url, originalContentProvider); + var sourceMapping = new WebInspector.RawSourceCode.PlainSourceMapping(this, uiSourceCode); + callback(sourceMapping); + return; + } + + /** + * @this {WebInspector.RawSourceCode} + * @param {string} mimeType + * @param {string} content + */ + function didRequestContent(mimeType, content) + { + /** + * @this {WebInspector.RawSourceCode} + * @param {string} formattedContent + * @param {WebInspector.FormattedSourceMapping} mapping + */ + function didFormatContent(formattedContent, mapping) + { + var contentProvider = new WebInspector.StaticContentProvider(mimeType, formattedContent) + var uiSourceCode = this._createUISourceCode("deobfuscated:" + this.url, this.url, contentProvider); + var sourceMapping = new WebInspector.RawSourceCode.FormattedSourceMapping(this, uiSourceCode, mapping); + callback(sourceMapping); + } + this._formatter.formatContent(mimeType, content, didFormatContent.bind(this)); + } + originalContentProvider.requestContent(didRequestContent.bind(this)); + }, + + /** + * @param {string} id + * @param {string} url + * @param {WebInspector.ContentProvider} contentProvider + */ + _createUISourceCode: function(id, url, contentProvider) + { + var uiSourceCode = new WebInspector.UISourceCode(id, url, this, contentProvider); + uiSourceCode.isContentScript = this.isContentScript; + return uiSourceCode; + }, + + /** + * @param {WebInspector.RawSourceCode.SourceMapping} sourceMapping + */ + _saveSourceMapping: function(sourceMapping) + { + var oldSourceMapping; + if (this._sourceMapping) + oldSourceMapping = this._sourceMapping; + this._sourceMapping = sourceMapping; + this.dispatchEventToListeners(WebInspector.RawSourceCode.Events.SourceMappingUpdated, { oldSourceMapping: oldSourceMapping }); + } +} + +WebInspector.RawSourceCode.prototype.__proto__ = WebInspector.Object.prototype; + +/** + * @interface + */ +WebInspector.RawSourceCode.SourceMapping = function() +{ +} + +WebInspector.RawSourceCode.SourceMapping.prototype = { + /** + * @param {DebuggerAgent.Location} rawLocation + * @return {WebInspector.UILocation} + */ + rawLocationToUILocation: function(rawLocation) { }, + + /** + * @param {WebInspector.UISourceCode} uiSourceCode + * @param {number} lineNumber + * @param {number} columnNumber + * @return {DebuggerAgent.Location} + */ + uiLocationToRawLocation: function(uiSourceCode, lineNumber, columnNumber) { } +} + +/** + * @constructor + * @implements {WebInspector.RawSourceCode.SourceMapping} + * @param {WebInspector.RawSourceCode} rawSourceCode + * @param {WebInspector.UISourceCode} uiSourceCode + */ +WebInspector.RawSourceCode.PlainSourceMapping = function(rawSourceCode, uiSourceCode) +{ + this._rawSourceCode = rawSourceCode; + this._uiSourceCodeList = [uiSourceCode]; +} + +WebInspector.RawSourceCode.PlainSourceMapping.prototype = { + /** + * @param {DebuggerAgent.Location} rawLocation + * @return {WebInspector.UILocation} + */ + rawLocationToUILocation: function(rawLocation) + { + return new WebInspector.UILocation(this._uiSourceCodeList[0], rawLocation.lineNumber, rawLocation.columnNumber || 0); + }, + + /** + * @param {WebInspector.UISourceCode} uiSourceCode + * @param {number} lineNumber + * @param {number} columnNumber + * @return {DebuggerAgent.Location} + */ + uiLocationToRawLocation: function(uiSourceCode, lineNumber, columnNumber) + { + console.assert(uiSourceCode === this._uiSourceCodeList[0]); + var rawLocation = { lineNumber: lineNumber, columnNumber: columnNumber }; + rawLocation.scriptId = this._rawSourceCode._scriptForRawLocation(rawLocation.lineNumber, rawLocation.columnNumber).scriptId; + return /** @type {DebuggerAgent.Location} */ rawLocation; + }, + + /** + * @return {Array.} + */ + uiSourceCodeList: function() + { + return this._uiSourceCodeList; + } +} + +/** + * @constructor + * @implements {WebInspector.RawSourceCode.SourceMapping} + * @param {WebInspector.RawSourceCode} rawSourceCode + * @param {WebInspector.UISourceCode} uiSourceCode + * @param {WebInspector.FormattedSourceMapping} mapping + */ +WebInspector.RawSourceCode.FormattedSourceMapping = function(rawSourceCode, uiSourceCode, mapping) +{ + this._rawSourceCode = rawSourceCode; + this._uiSourceCodeList = [uiSourceCode]; + this._mapping = mapping; +} + +WebInspector.RawSourceCode.FormattedSourceMapping.prototype = { + /** + * @param {DebuggerAgent.Location} rawLocation + */ + rawLocationToUILocation: function(rawLocation) + { + var location = this._mapping.originalToFormatted(rawLocation); + return new WebInspector.UILocation(this._uiSourceCodeList[0], location.lineNumber, location.columnNumber || 0); + }, + + /** + * @param {WebInspector.UISourceCode} uiSourceCode + * @param {number} lineNumber + * @param {number} columnNumber + * @return {DebuggerAgent.Location} + */ + uiLocationToRawLocation: function(uiSourceCode, lineNumber, columnNumber) + { + console.assert(uiSourceCode === this._uiSourceCodeList[0]); + var rawLocation = this._mapping.formattedToOriginal(new WebInspector.DebuggerModel.Location(lineNumber, columnNumber)); + rawLocation.scriptId = this._rawSourceCode._scriptForRawLocation(rawLocation.lineNumber, rawLocation.columnNumber).scriptId; + return rawLocation; + }, + + /** + * @return {Array.} + */ + uiSourceCodeList: function() + { + return this._uiSourceCodeList; + } +} + +/** + * @constructor + * @implements {WebInspector.RawSourceCode.SourceMapping} + * @param {WebInspector.RawSourceCode} rawSourceCode + * @param {Array.} uiSourceCodeList + * @param {WebInspector.CompilerSourceMapping} mapping + */ +WebInspector.RawSourceCode.CompilerSourceMapping = function(rawSourceCode, uiSourceCodeList, mapping) +{ + this._rawSourceCode = rawSourceCode; + this._uiSourceCodeList = uiSourceCodeList; + this._mapping = mapping; + this._uiSourceCodeByURL = {}; + for (var i = 0; i < uiSourceCodeList.length; ++i) + this._uiSourceCodeByURL[uiSourceCodeList[i].url] = uiSourceCodeList[i]; +} + +WebInspector.RawSourceCode.CompilerSourceMapping.prototype = { + /** + * @param {DebuggerAgent.Location} rawLocation + */ + rawLocationToUILocation: function(rawLocation) + { + var location = this._mapping.compiledLocationToSourceLocation(rawLocation.lineNumber, rawLocation.columnNumber || 0); + var uiSourceCode = this._uiSourceCodeByURL[location.sourceURL]; + return new WebInspector.UILocation(uiSourceCode, location.lineNumber, location.columnNumber); + }, + + /** + * @param {WebInspector.UISourceCode} uiSourceCode + * @param {number} lineNumber + * @param {number} columnNumber + * @return {DebuggerAgent.Location} + */ + uiLocationToRawLocation: function(uiSourceCode, lineNumber, columnNumber) + { + var rawLocation = this._mapping.sourceLocationToCompiledLocation(uiSourceCode.url, lineNumber); + rawLocation.scriptId = this._rawSourceCode._scriptForRawLocation(rawLocation.lineNumber, rawLocation.columnNumber).scriptId; + return rawLocation; + }, + + /** + * @return {Array.} + */ + uiSourceCodeList: function() + { + return this._uiSourceCodeList; + } +} + +/** + * @constructor + * @param {WebInspector.UISourceCode} uiSourceCode + * @param {number} lineNumber + * @param {number} columnNumber + */ +WebInspector.UILocation = function(uiSourceCode, lineNumber, columnNumber) +{ + this.uiSourceCode = uiSourceCode; + this.lineNumber = lineNumber; + this.columnNumber = columnNumber; +} +/* CompilerSourceMapping.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @interface + */ +WebInspector.CompilerSourceMapping = function() +{ +} + +WebInspector.CompilerSourceMapping.prototype = { + /** + * @param {number} lineNumber + * @param {number} columnNumber + * @return {Object} + */ + compiledLocationToSourceLocation: function(lineNumber, columnNumber) { }, + + /** + * @param {string} sourceURL + * @param {number} lineNumber + * @return {DebuggerAgent.Location} + */ + sourceLocationToCompiledLocation: function(sourceURL, lineNumber) { }, + + /** + * @return {Array.} + */ + sources: function() { } +} + +/** + * @constructor + */ +WebInspector.ClosureCompilerSourceMappingPayload = function() +{ + this.mappings = ""; + this.sourceRoot = ""; + this.sources = []; +} + +/** + * Implements Source Map V3 consumer. See http://code.google.com/p/closure-compiler/wiki/SourceMaps + * for format description. + * @implements {WebInspector.CompilerSourceMapping} + * @constructor + * @param {string} sourceMappingURL + * @param {string} scriptSourceOrigin + */ +WebInspector.ClosureCompilerSourceMapping = function(sourceMappingURL, scriptSourceOrigin) +{ + if (!WebInspector.ClosureCompilerSourceMapping.prototype._base64Map) { + const base64Digits = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + WebInspector.ClosureCompilerSourceMapping.prototype._base64Map = {}; + for (var i = 0; i < base64Digits.length; ++i) + WebInspector.ClosureCompilerSourceMapping.prototype._base64Map[base64Digits.charAt(i)] = i; + } + + this._sourceMappingURL = this._canonicalizeURL(sourceMappingURL, scriptSourceOrigin); + this._mappings = []; + this._reverseMappingsBySourceURL = {}; +} + +WebInspector.ClosureCompilerSourceMapping.prototype = { + /** + * @return {boolean} + */ + load: function() + { + try { + // FIXME: make sendRequest async. + var response = InspectorFrontendHost.loadResourceSynchronously(this._sourceMappingURL); + if (response.slice(0, 3) === ")]}") + response = response.substring(response.indexOf('\n')); + this._parseMappingPayload(JSON.parse(response)); + return true + } catch(e) { + console.error(e.message); + return false; + } + }, + + /** + * @param {number} lineNumber + * @param {number} columnNumber + * @return {Object} + */ + compiledLocationToSourceLocation: function(lineNumber, columnNumber) + { + var mapping = this._findMapping(lineNumber, columnNumber); + return { sourceURL: mapping[2], lineNumber: mapping[3], columnNumber: mapping[4] }; + }, + + sourceLocationToCompiledLocation: function(sourceURL, lineNumber) + { + var mappings = this._reverseMappingsBySourceURL[sourceURL]; + for ( ; lineNumber < mappings.length; ++lineNumber) { + var mapping = mappings[lineNumber]; + if (mapping) + return { lineNumber: mapping[0], columnNumber: mapping[1] }; + } + }, + + /** + * @return {Array.} + */ + sources: function() + { + var sources = []; + for (var sourceURL in this._reverseMappingsBySourceURL) + sources.push(sourceURL); + return sources; + }, + + /** + * @param {string} sourceURL + * @return {string} + */ + loadSourceCode: function(sourceURL) + { + try { + // FIXME: make sendRequest async. + return InspectorFrontendHost.loadResourceSynchronously(sourceURL); + } catch(e) { + console.error(e.message); + return ""; + } + }, + + _findMapping: function(lineNumber, columnNumber) + { + var first = 0; + var count = this._mappings.length; + while (count > 1) { + var step = count >> 1; + var middle = first + step; + var mapping = this._mappings[middle]; + if (lineNumber < mapping[0] || (lineNumber == mapping[0] && columnNumber < mapping[1])) + count = step; + else { + first = middle; + count -= step; + } + } + return this._mappings[first]; + }, + + _parseMappingPayload: function(mappingPayload) + { + if (mappingPayload.sections) + this._parseSections(mappingPayload.sections); + else + this._parseMap(mappingPayload, 0, 0); + }, + + _parseSections: function(sections) + { + for (var i = 0; i < sections.length; ++i) { + var section = sections[i]; + this._parseMap(section.map, section.offset.line, section.offset.column) + } + }, + + _parseMap: function(map, lineNumber, columnNumber) + { + var sourceIndex = 0; + var sourceLineNumber = 0; + var sourceColumnNumber = 0; + var nameIndex = 0; + + var sources = []; + for (var i = 0; i < map.sources.length; ++i) { + var sourceURL = map.sources[i]; + if (map.sourceRoot) + sourceURL = map.sourceRoot + "/" + sourceURL; + var url = this._canonicalizeURL(sourceURL, this._sourceMappingURL); + sources.push(url); + if (!this._reverseMappingsBySourceURL[url]) + this._reverseMappingsBySourceURL[url] = []; + } + + var stringCharIterator = new WebInspector.ClosureCompilerSourceMapping.StringCharIterator(map.mappings); + var sourceURL = sources[sourceIndex]; + var reverseMappings = this._reverseMappingsBySourceURL[sourceURL]; + + while (true) { + if (stringCharIterator.peek() === ",") + stringCharIterator.next(); + else { + while (stringCharIterator.peek() === ";") { + lineNumber += 1; + columnNumber = 0; + stringCharIterator.next(); + } + if (!stringCharIterator.hasNext()) + break; + } + + columnNumber += this._decodeVLQ(stringCharIterator); + if (!this._isSeparator(stringCharIterator.peek())) { + var sourceIndexDelta = this._decodeVLQ(stringCharIterator); + if (sourceIndexDelta) { + sourceIndex += sourceIndexDelta; + sourceURL = sources[sourceIndex]; + reverseMappings = this._reverseMappingsBySourceURL[sourceURL]; + } + sourceLineNumber += this._decodeVLQ(stringCharIterator); + sourceColumnNumber += this._decodeVLQ(stringCharIterator); + if (!this._isSeparator(stringCharIterator.peek())) + nameIndex += this._decodeVLQ(stringCharIterator); + + this._mappings.push([lineNumber, columnNumber, sourceURL, sourceLineNumber, sourceColumnNumber]); + if (!reverseMappings[sourceLineNumber]) + reverseMappings[sourceLineNumber] = [lineNumber, columnNumber]; + } + } + }, + + _isSeparator: function(char) + { + return char === "," || char === ";"; + }, + + _decodeVLQ: function(stringCharIterator) + { + // Read unsigned value. + var result = 0; + var shift = 0; + do { + var digit = this._base64Map[stringCharIterator.next()]; + result += (digit & this._VLQ_BASE_MASK) << shift; + shift += this._VLQ_BASE_SHIFT; + } while (digit & this._VLQ_CONTINUATION_MASK); + + // Fix the sign. + var negative = result & 1; + result >>= 1; + return negative ? -result : result; + }, + + _canonicalizeURL: function(url, baseURL) + { + if (!url || !baseURL || url.asParsedURL()) + return url; + + var base = baseURL.asParsedURL(); + if (!base) + return url; + + var baseHost = base.scheme + "://" + base.host + (base.port ? ":" + base.port : ""); + if (url[0] === "/") + return baseHost + url; + return baseHost + base.firstPathComponents + url; + }, + + _VLQ_BASE_SHIFT: 5, + _VLQ_BASE_MASK: (1 << 5) - 1, + _VLQ_CONTINUATION_MASK: 1 << 5 +} + +/** + * @constructor + */ +WebInspector.ClosureCompilerSourceMapping.StringCharIterator = function(string) +{ + this._string = string; + this._position = 0; +} + +WebInspector.ClosureCompilerSourceMapping.StringCharIterator.prototype = { + next: function() + { + return this._string.charAt(this._position++); + }, + + peek: function() + { + return this._string.charAt(this._position); + }, + + hasNext: function() + { + return this._position < this._string.length; + } +} +/* ScriptsSearchScope.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY GOOGLE INC. AND ITS CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE INC. + * OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @implements {WebInspector.SearchScope} + */ +WebInspector.ScriptsSearchScope = function() +{ + // FIXME: Add title once it is used by search controller. + WebInspector.SearchScope.call(this) + this._searchId = 0; +} + +WebInspector.ScriptsSearchScope.prototype = { + /** + * @param {WebInspector.SearchConfig} searchConfig + * @param {function(Object)} searchResultCallback + * @param {function(boolean)} searchFinishedCallback + */ + performSearch: function(searchConfig, searchResultCallback, searchFinishedCallback) + { + this.stopSearch(); + + var uiSourceCodes = this._sortedUISourceCodes(); + var uiSourceCodeIndex = 0; + + function filterOutContentScripts(uiSourceCode) + { + return !uiSourceCode.isContentScript; + } + + if (!WebInspector.settings.searchInContentScripts.get()) + uiSourceCodes = uiSourceCodes.filter(filterOutContentScripts); + + function continueSearch() + { + // FIXME: Enable support for counting matches for incremental search. + // FIXME: Enable support for bounding search results/matches number to keep inspector responsive. + if (uiSourceCodeIndex < uiSourceCodes.length) { + var uiSourceCode = uiSourceCodes[uiSourceCodeIndex++]; + uiSourceCode.searchInContent(searchConfig.query, !searchConfig.ignoreCase, searchConfig.isRegex, searchCallbackWrapper.bind(this, this._searchId, uiSourceCode)); + } else + searchFinishedCallback(true); + } + + function searchCallbackWrapper(searchId, uiSourceCode, searchMatches) + { + if (searchId !== this._searchId) { + searchFinishedCallback(false); + return; + } + + var searchResult = new WebInspector.FileBasedSearchResultsPane.SearchResult(uiSourceCode, searchMatches); + searchResultCallback(searchResult); + continueSearch.call(this); + } + + continueSearch.call(this); + return uiSourceCodes.length; + }, + + stopSearch: function() + { + ++this._searchId; + }, + + /** + * @param {WebInspector.SearchConfig} searchConfig + */ + createSearchResultsPane: function(searchConfig) + { + return new WebInspector.ScriptsSearchResultsPane(searchConfig); + }, + + /** + * @return {Array.} + */ + _sortedUISourceCodes: function() + { + function filterOutAnonymous(uiSourceCode) + { + return !!uiSourceCode.url; + } + + function comparator(a, b) + { + return a.url.localeCompare(b.url); + } + + var uiSourceCodes = WebInspector.debuggerPresentationModel.uiSourceCodes(); + + uiSourceCodes = uiSourceCodes.filter(filterOutAnonymous); + uiSourceCodes.sort(comparator); + + return uiSourceCodes; + } +} + +WebInspector.ScriptsSearchScope.prototype.__proto__ = WebInspector.SearchScope.prototype; + +/** + * @constructor + * @extends {WebInspector.FileBasedSearchResultsPane} + * @param {WebInspector.SearchConfig} searchConfig + */ +WebInspector.ScriptsSearchResultsPane = function(searchConfig) +{ + WebInspector.FileBasedSearchResultsPane.call(this, searchConfig) + + this._linkifier = WebInspector.debuggerPresentationModel.createLinkifier(new WebInspector.ScriptsSearchResultsPane.LinkifierFormatter()); +} + +WebInspector.ScriptsSearchResultsPane.prototype = { + /** + * @param {Object} file + * @param {number} lineNumber + * @param {number} columnNumber + */ + createAnchor: function(file, lineNumber, columnNumber) + { + + var uiSourceCode = file; + var rawSourceCode = uiSourceCode.rawSourceCode; + var rawLocation = rawSourceCode.sourceMapping.uiLocationToRawLocation(uiSourceCode, lineNumber, columnNumber); + var anchor = this._linkifier.linkifyRawSourceCode(uiSourceCode.rawSourceCode, rawLocation.lineNumber, rawLocation.columnNumber); + anchor.removeChildren(); + return anchor; + }, + + /** + * @param {Object} file + * @return {string} + */ + fileName: function(file) + { + var uiSourceCode = file; + return uiSourceCode.url; + }, +} + +WebInspector.ScriptsSearchResultsPane.prototype.__proto__ = WebInspector.FileBasedSearchResultsPane.prototype; + +/** + * @constructor + * @implements {WebInspector.DebuggerPresentationModel.LinkifierFormatter} + */ +WebInspector.ScriptsSearchResultsPane.LinkifierFormatter = function() +{ +} + +WebInspector.ScriptsSearchResultsPane.LinkifierFormatter.prototype = { + /** + * @param {WebInspector.RawSourceCode} rawSourceCode + * @param {Element} anchor + */ + formatRawSourceCodeAnchor: function(rawSourceCode, anchor) + { + // Empty because we don't want to ever update anchor contents after creation. + } +} + +WebInspector.ScriptsSearchResultsPane.LinkifierFormatter.prototype.__proto__ = WebInspector.DebuggerPresentationModel.LinkifierFormatter.prototype; + +WebInspector.settings.searchInContentScripts = WebInspector.settings.createSetting("searchInContentScripts", false); +/* DOMAgent.js */ + +/* + * Copyright (C) 2009, 2010 Google Inc. All rights reserved. + * Copyright (C) 2009 Joseph Pecoraro + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @param {WebInspector.DOMAgent} domAgent + * @param {?WebInspector.DOMDocument} doc + * @param {DOMAgent.Node} payload + */ +WebInspector.DOMNode = function(domAgent, doc, payload) { + this._domAgent = domAgent; + this.ownerDocument = doc; + + this.id = payload.nodeId; + this._nodeType = payload.nodeType; + this._nodeName = payload.nodeName; + this._localName = payload.localName; + this._nodeValue = payload.nodeValue; + + this._attributes = []; + this._attributesMap = {}; + if (payload.attributes) + this._setAttributesPayload(payload.attributes); + + this._childNodeCount = payload.childNodeCount; + this.children = null; + + this.nextSibling = null; + this.previousSibling = null; + this.firstChild = null; + this.lastChild = null; + this.parentNode = null; + + if (payload.children) + this._setChildrenPayload(payload.children); + + if (payload.contentDocument) { + this._contentDocument = new WebInspector.DOMDocument(domAgent, payload.contentDocument); + this.children = [this._contentDocument]; + this._renumber(); + } + + if (this._nodeType === Node.ELEMENT_NODE) { + // HTML and BODY from internal iframes should not overwrite top-level ones. + if (this.ownerDocument && !this.ownerDocument.documentElement && this._nodeName === "HTML") + this.ownerDocument.documentElement = this; + if (this.ownerDocument && !this.ownerDocument.body && this._nodeName === "BODY") + this.ownerDocument.body = this; + } else if (this._nodeType === Node.DOCUMENT_TYPE_NODE) { + this.publicId = payload.publicId; + this.systemId = payload.systemId; + this.internalSubset = payload.internalSubset; + } else if (this._nodeType === Node.ATTRIBUTE_NODE) { + this.name = payload.name; + this.value = payload.value; + } +} + +WebInspector.DOMNode.prototype = { + /** + * @return {boolean} + */ + hasAttributes: function() + { + return this._attributes.length > 0; + }, + + /** + * @return {boolean} + */ + hasChildNodes: function() + { + return this._childNodeCount > 0; + }, + + /** + * @return {number} + */ + nodeType: function() + { + return this._nodeType; + }, + + /** + * @return {string} + */ + nodeName: function() + { + return this._nodeName; + }, + + /** + * @return {string} + */ + nodeNameInCorrectCase: function() + { + return this.isXMLNode() ? this.nodeName() : this.nodeName().toLowerCase(); + }, + + /** + * @param {string} name + * @param {function(?Protocol.Error)=} callback + */ + setNodeName: function(name, callback) + { + DOMAgent.setNodeName(this.id, name, WebInspector.domAgent._markRevision(this, callback)); + }, + + /** + * @return {string} + */ + localName: function() + { + return this._localName; + }, + + /** + * @return {string} + */ + nodeValue: function() + { + return this._nodeValue; + }, + + /** + * @param {string} value + * @param {function(?Protocol.Error)=} callback + */ + setNodeValue: function(value, callback) + { + DOMAgent.setNodeValue(this.id, value, WebInspector.domAgent._markRevision(this, callback)); + }, + + /** + * @param {string} name + * @return {string} + */ + getAttribute: function(name) + { + var attr = this._attributesMap[name]; + return attr ? attr.value : undefined; + }, + + /** + * @param {string} name + * @param {string} text + * @param {function(?Protocol.Error)=} callback + */ + setAttribute: function(name, text, callback) + { + DOMAgent.setAttributesAsText(this.id, text, name, WebInspector.domAgent._markRevision(this, callback)); + }, + + /** + * @param {string} name + * @param {string} value + * @param {function(?Protocol.Error)=} callback + */ + setAttributeValue: function(name, value, callback) + { + DOMAgent.setAttributeValue(this.id, name, value, WebInspector.domAgent._markRevision(this, callback)); + }, + + /** + * @return {Object} + */ + attributes: function() + { + return this._attributes; + }, + + /** + * @param {string} name + * @param {function(?Protocol.Error)=} callback + */ + removeAttribute: function(name, callback) + { + function mycallback(error, success) + { + if (!error) { + delete this._attributesMap[name]; + for (var i = 0; i < this._attributes.length; ++i) { + if (this._attributes[i].name === name) { + this._attributes.splice(i, 1); + break; + } + } + } + + WebInspector.domAgent._markRevision(this, callback)(error); + } + DOMAgent.removeAttribute(this.id, name, mycallback.bind(this)); + }, + + /** + * @param {function(Array.)=} callback + */ + getChildNodes: function(callback) + { + if (this.children) { + if (callback) + callback(this.children); + return; + } + + /** + * @this {WebInspector.DOMNode} + * @param {?Protocol.Error} error + */ + function mycallback(error) + { + if (!error && callback) + callback(this.children); + } + + DOMAgent.requestChildNodes(this.id, mycallback.bind(this)); + }, + + /** + * @param {function(?Protocol.Error)=} callback + */ + getOuterHTML: function(callback) + { + DOMAgent.getOuterHTML(this.id, callback); + }, + + /** + * @param {string} html + * @param {function(?Protocol.Error)=} callback + */ + setOuterHTML: function(html, callback) + { + DOMAgent.setOuterHTML(this.id, html, WebInspector.domAgent._markRevision(this, callback)); + }, + + /** + * @param {function(?Protocol.Error)=} callback + */ + removeNode: function(callback) + { + DOMAgent.removeNode(this.id, WebInspector.domAgent._markRevision(this, callback)); + }, + + copyNode: function() + { + function copy(error, text) + { + if (!error) + InspectorFrontendHost.copyText(text); + } + DOMAgent.getOuterHTML(this.id, copy); + }, + + /** + * @param {function(?Protocol.Error)=} callback + */ + eventListeners: function(callback) + { + DOMAgent.getEventListenersForNode(this.id, callback); + }, + + /** + * @return {string} + */ + path: function() + { + var path = []; + var node = this; + while (node && "index" in node && node._nodeName.length) { + path.push([node.index, node._nodeName]); + node = node.parentNode; + } + path.reverse(); + return path.join(","); + }, + + /** + * @param {boolean} justSelector + * @return {string} + */ + appropriateSelectorFor: function(justSelector) + { + var lowerCaseName = this.localName() || this.nodeName().toLowerCase(); + + var id = this.getAttribute("id"); + if (id) { + var selector = "#" + id; + return (justSelector ? selector : lowerCaseName + selector); + } + + var className = this.getAttribute("class"); + if (className) { + var selector = "." + className.replace(/\s+/, "."); + return (justSelector ? selector : lowerCaseName + selector); + } + + if (lowerCaseName === "input" && this.getAttribute("type")) + return lowerCaseName + "[type=\"" + this.getAttribute("type") + "\"]"; + + return lowerCaseName; + }, + + /** + * @param {WebInspector.DOMNode} node + * @return {boolean} + */ + isAncestor: function(node) + { + if (!node) + return false; + + var currentNode = node.parentNode; + while (currentNode) { + if (this === currentNode) + return true; + currentNode = currentNode.parentNode; + } + return false; + }, + + /** + * @param {WebInspector.DOMNode} descendant + * @return {boolean} + */ + isDescendant: function(descendant) + { + return descendant !== null && descendant.isAncestor(this); + }, + + /** + * @param {Array.} attrs + */ + _setAttributesPayload: function(attrs) + { + this._attributes = []; + this._attributesMap = {}; + for (var i = 0; i < attrs.length; i += 2) + this._addAttribute(attrs[i], attrs[i + 1]); + }, + + /** + * @param {WebInspector.DOMNode} prev + * @param {DOMAgent.Node} payload + * @return {WebInspector.DOMNode} + */ + _insertChild: function(prev, payload) + { + var node = new WebInspector.DOMNode(this._domAgent, this.ownerDocument, payload); + if (!prev) { + if (!this.children) { + // First node + this.children = [ node ]; + } else + this.children.unshift(node); + } else + this.children.splice(this.children.indexOf(prev) + 1, 0, node); + this._renumber(); + return node; + }, + + /** + * @param {WebInspector.DOMNode} node + */ + _removeChild: function(node) + { + this.children.splice(this.children.indexOf(node), 1); + node.parentNode = null; + this._renumber(); + }, + + /** + * @param {Array.} payloads + */ + _setChildrenPayload: function(payloads) + { + // We set children in the constructor. + if (this._contentDocument) + return; + + this.children = []; + for (var i = 0; i < payloads.length; ++i) { + var payload = payloads[i]; + var node = new WebInspector.DOMNode(this._domAgent, this.ownerDocument, payload); + this.children.push(node); + } + this._renumber(); + }, + + _renumber: function() + { + this._childNodeCount = this.children.length; + if (this._childNodeCount == 0) { + this.firstChild = null; + this.lastChild = null; + return; + } + this.firstChild = this.children[0]; + this.lastChild = this.children[this._childNodeCount - 1]; + for (var i = 0; i < this._childNodeCount; ++i) { + var child = this.children[i]; + child.index = i; + child.nextSibling = i + 1 < this._childNodeCount ? this.children[i + 1] : null; + child.previousSibling = i - 1 >= 0 ? this.children[i - 1] : null; + child.parentNode = this; + } + }, + + /** + * @param {string} name + * @param {string} value + */ + _addAttribute: function(name, value) + { + var attr = { + name: name, + value: value, + _node: this + }; + this._attributesMap[name] = attr; + this._attributes.push(attr); + }, + + /** + * @param {string} name + * @param {string} value + */ + _setAttribute: function(name, value) + { + var attr = this._attributesMap[name]; + if (attr) + attr.value = value; + else + this._addAttribute(name, value); + }, + + /** + * @param {string} name + */ + _removeAttribute: function(name) + { + var attr = this._attributesMap[name]; + if (attr) { + this._attributes.remove(attr); + delete this._attributesMap[name]; + } + }, + + /** + * @param {WebInspector.DOMNode} targetNode + * @param {?WebInspector.DOMNode} anchorNode + * @param {function(?Protocol.Error)=} callback + */ + moveTo: function(targetNode, anchorNode, callback) + { + DOMAgent.moveTo(this.id, targetNode.id, anchorNode ? anchorNode.id : undefined, WebInspector.domAgent._markRevision(this, callback)); + }, + + /** + * @return {boolean} + */ + isXMLNode: function() + { + return !!this.ownerDocument && !!this.ownerDocument.xmlVersion; + } +} + +/** + * @extends {WebInspector.DOMNode} + * @constructor + * @param {WebInspector.DOMAgent} domAgent + * @param {DOMAgent.Node} payload + */ +WebInspector.DOMDocument = function(domAgent, payload) +{ + WebInspector.DOMNode.call(this, domAgent, this, payload); + this.documentURL = payload.documentURL || ""; + this.xmlVersion = payload.xmlVersion; + domAgent._idToDOMNode[this.id] = this; + this._listeners = {}; +} + +WebInspector.DOMDocument.prototype.__proto__ = WebInspector.DOMNode.prototype; + +/** + * @extends {WebInspector.Object} + * @constructor + */ +WebInspector.DOMAgent = function() { + /** @type {Object|undefined} */ + this._idToDOMNode = {}; + this._document = null; + this._attributeLoadNodeIds = {}; + InspectorBackend.registerDOMDispatcher(new WebInspector.DOMDispatcher(this)); + if (WebInspector.experimentsSettings.freeFlowDOMEditing.isEnabled()) + new WebInspector.DOMModelResourceBinding(this); +} + +WebInspector.DOMAgent.Events = { + AttrModified: "AttrModified", + AttrRemoved: "AttrRemoved", + CharacterDataModified: "CharacterDataModified", + NodeInserted: "NodeInserted", + NodeRemoved: "NodeRemoved", + DocumentUpdated: "DocumentUpdated", + ChildNodeCountUpdated: "ChildNodeCountUpdated", + InspectElementRequested: "InspectElementRequested", + StyleInvalidated: "StyleInvalidated" +} + +WebInspector.DOMAgent.prototype = { + /** + * @param {function(WebInspector.DOMDocument)=} callback + */ + requestDocument: function(callback) + { + if (this._document) { + if (callback) + callback(this._document); + return; + } + + if (this._pendingDocumentRequestCallbacks) { + this._pendingDocumentRequestCallbacks.push(callback); + return; + } + + this._pendingDocumentRequestCallbacks = [callback]; + + /** + * @this {WebInspector.DOMAgent} + * @param {?Protocol.Error} error + * @param {DOMAgent.Node} root + */ + function onDocumentAvailable(error, root) + { + if (!error) + this._setDocument(root); + + for (var i = 0; i < this._pendingDocumentRequestCallbacks.length; ++i) { + var callback = this._pendingDocumentRequestCallbacks[i]; + if (callback) + callback(this._document); + } + delete this._pendingDocumentRequestCallbacks; + } + + DOMAgent.getDocument(onDocumentAvailable.bind(this)); + }, + + /** + * @param {RuntimeAgent.RemoteObjectId} objectId + * @param {function()=} callback + */ + pushNodeToFrontend: function(objectId, callback) + { + this._dispatchWhenDocumentAvailable(DOMAgent.requestNode.bind(DOMAgent, objectId), callback); + }, + + /** + * @param {string} path + * @param {function(?WebInspector.DOMNode)=} callback + */ + pushNodeByPathToFrontend: function(path, callback) + { + var callbackCast = /** @type {function(*)} */ callback; + this._dispatchWhenDocumentAvailable(DOMAgent.pushNodeByPathToFrontend.bind(DOMAgent, path), callbackCast); + }, + + /** + * @param {function(*)=} callback + * @return {function(?Protocol.Error,*=)|undefined} + */ + _wrapClientCallback: function(callback) + { + if (!callback) + return; + return function(error, result) + { + if (error) + console.error("Error during DOMAgent operation: " + error); + callback(error ? null : result); + } + }, + + /** + * @param {function(function()=)} func + * @param {function(*)=} callback + */ + _dispatchWhenDocumentAvailable: function(func, callback) + { + var callbackWrapper = /** @type {function(?Protocol.Error, *=)} */ this._wrapClientCallback(callback); + + function onDocumentAvailable() + { + if (this._document) + func(callbackWrapper); + else { + if (callbackWrapper) + callbackWrapper("No document"); + } + } + this.requestDocument(onDocumentAvailable.bind(this)); + }, + + /** + * @param {DOMAgent.NodeId} nodeId + * @param {string} name + * @param {string} value + */ + _attributeModified: function(nodeId, name, value) + { + var node = this._idToDOMNode[nodeId]; + if (!node) + return; + node._setAttribute(name, value); + this.dispatchEventToListeners(WebInspector.DOMAgent.Events.AttrModified, { node: node, name: name }); + }, + + /** + * @param {DOMAgent.NodeId} nodeId + * @param {string} name + */ + _attributeRemoved: function(nodeId, name) + { + var node = this._idToDOMNode[nodeId]; + if (!node) + return; + node._removeAttribute(name); + this.dispatchEventToListeners(WebInspector.DOMAgent.Events.AttrRemoved, { node: node, name: name }); + }, + + /** + * @param {Array.} nodeIds + */ + _inlineStyleInvalidated: function(nodeIds) + { + for (var i = 0; i < nodeIds.length; ++i) + this._attributeLoadNodeIds[nodeIds[i]] = true; + if ("_loadNodeAttributesTimeout" in this) + return; + this._loadNodeAttributesTimeout = setTimeout(this._loadNodeAttributes.bind(this), 0); + }, + + _loadNodeAttributes: function() + { + /** + * @this {WebInspector.DOMAgent} + * @param {DOMAgent.NodeId} nodeId + * @param {?Protocol.Error} error + * @param {Array.} attributes + */ + function callback(nodeId, error, attributes) + { + if (error) { + console.error("Error during DOMAgent operation: " + error); + return; + } + var node = this._idToDOMNode[nodeId]; + if (node) { + node._setAttributesPayload(attributes); + this.dispatchEventToListeners(WebInspector.DOMAgent.Events.AttrModified, { node: node, name: "style" }); + this.dispatchEventToListeners(WebInspector.DOMAgent.Events.StyleInvalidated, node); + } + } + + delete this._loadNodeAttributesTimeout; + + for (var nodeId in this._attributeLoadNodeIds) { + var nodeIdAsNumber = parseInt(nodeId, 10); + DOMAgent.getAttributes(nodeIdAsNumber, callback.bind(this, nodeIdAsNumber)); + } + this._attributeLoadNodeIds = {}; + }, + + /** + * @param {DOMAgent.NodeId} nodeId + * @param {string} newValue + */ + _characterDataModified: function(nodeId, newValue) + { + var node = this._idToDOMNode[nodeId]; + node._nodeValue = newValue; + this.dispatchEventToListeners(WebInspector.DOMAgent.Events.CharacterDataModified, node); + }, + + /** + * @param {DOMAgent.NodeId} nodeId + * @return {WebInspector.DOMNode|undefined} + */ + nodeForId: function(nodeId) + { + return this._idToDOMNode[nodeId]; + }, + + _documentUpdated: function() + { + this._setDocument(null); + }, + + /** + * @param {DOMAgent.Node} payload + */ + _setDocument: function(payload) + { + this._idToDOMNode = {}; + if (payload && "nodeId" in payload) { + this._document = new WebInspector.DOMDocument(this, payload); + if (this._document.children) + this._bindNodes(this._document.children); + } else + this._document = null; + this.dispatchEventToListeners(WebInspector.DOMAgent.Events.DocumentUpdated, this._document); + }, + + /** + * @param {DOMAgent.Node} payload + */ + _setDetachedRoot: function(payload) + { + var root = new WebInspector.DOMNode(this, null, payload); + this._idToDOMNode[payload.nodeId] = root; + }, + + /** + * @param {DOMAgent.NodeId} parentId + * @param {Array.} payloads + */ + _setChildNodes: function(parentId, payloads) + { + if (!parentId && payloads.length) { + this._setDetachedRoot(payloads[0]); + return; + } + + var parent = this._idToDOMNode[parentId]; + parent._setChildrenPayload(payloads); + this._bindNodes(parent.children); + }, + + /** + * @param {Array.} children + */ + _bindNodes: function(children) + { + for (var i = 0; i < children.length; ++i) { + var child = children[i]; + this._idToDOMNode[child.id] = child; + if (child.children) + this._bindNodes(child.children); + } + }, + + /** + * @param {DOMAgent.NodeId} nodeId + * @param {number} newValue + */ + _childNodeCountUpdated: function(nodeId, newValue) + { + var node = this._idToDOMNode[nodeId]; + node._childNodeCount = newValue; + this.dispatchEventToListeners(WebInspector.DOMAgent.Events.ChildNodeCountUpdated, node); + }, + + /** + * @param {DOMAgent.NodeId} parentId + * @param {DOMAgent.NodeId} prevId + * @param {DOMAgent.Node} payload + */ + _childNodeInserted: function(parentId, prevId, payload) + { + var parent = this._idToDOMNode[parentId]; + var prev = this._idToDOMNode[prevId]; + var node = parent._insertChild(prev, payload); + this._idToDOMNode[node.id] = node; + this.dispatchEventToListeners(WebInspector.DOMAgent.Events.NodeInserted, node); + }, + + /** + * @param {DOMAgent.NodeId} parentId + * @param {DOMAgent.NodeId} nodeId + */ + _childNodeRemoved: function(parentId, nodeId) + { + var parent = this._idToDOMNode[parentId]; + var node = this._idToDOMNode[nodeId]; + parent._removeChild(node); + this.dispatchEventToListeners(WebInspector.DOMAgent.Events.NodeRemoved, {node:node, parent:parent}); + delete this._idToDOMNode[nodeId]; + }, + + /** + * @param {number} nodeId + */ + inspectElement: function(nodeId) + { + var node = this._idToDOMNode[nodeId]; + if (node) + this.dispatchEventToListeners(WebInspector.DOMAgent.Events.InspectElementRequested, node); + }, + + /** + * @param {string} query + * @param {function(number)} searchCallback + */ + performSearch: function(query, searchCallback) + { + this.cancelSearch(); + + /** + * @param {?Protocol.Error} error + * @param {string} searchId + * @param {number} resultsCount + */ + function callback(error, searchId, resultsCount) + { + this._searchId = searchId; + searchCallback(resultsCount); + } + DOMAgent.performSearch(query, callback.bind(this)); + }, + + /** + * @param {number} index + * @param {?function(DOMAgent.Node)} callback + */ + searchResult: function(index, callback) + { + if (this._searchId) { + /** + * @param {?Protocol.Error} error + * @param {Array.} nodeIds + */ + function mycallback(error, nodeIds) + { + if (error) { + console.error(error); + callback(null); + return; + } + if (nodeIds.length != 1) + return; + + callback(this._idToDOMNode[nodeIds[0]]); + } + DOMAgent.getSearchResults(this._searchId, index, index + 1, mycallback.bind(this)); + } else + callback(null); + }, + + cancelSearch: function() + { + if (this._searchId) { + DOMAgent.discardSearchResults(this._searchId); + delete this._searchId; + } + }, + + /** + * @param {DOMAgent.NodeId} nodeId + * @param {string} selectors + * @param {function(?DOMAgent.NodeId)=} callback + */ + querySelector: function(nodeId, selectors, callback) + { + var callbackCast = /** @type {function(*)|undefined} */callback; + DOMAgent.querySelector(nodeId, selectors, this._wrapClientCallback(callbackCast)); + }, + + /** + * @param {DOMAgent.NodeId} nodeId + * @param {string} selectors + * @param {function(?Array.)=} callback + */ + querySelectorAll: function(nodeId, selectors, callback) + { + var callbackCast = /** @type {function(*)|undefined} */callback; + DOMAgent.querySelectorAll(nodeId, selectors, this._wrapClientCallback(callbackCast)); + }, + + /** + * @param {?number} nodeId + * @param {string=} mode + */ + highlightDOMNode: function(nodeId, mode) + { + if (this._hideDOMNodeHighlightTimeout) { + clearTimeout(this._hideDOMNodeHighlightTimeout); + delete this._hideDOMNodeHighlightTimeout; + } + + this._highlightedDOMNodeId = nodeId; + if (nodeId) + DOMAgent.highlightNode(nodeId, this._buildHighlightConfig(mode)); + else + DOMAgent.hideHighlight(); + }, + + hideDOMNodeHighlight: function() + { + this.highlightDOMNode(0); + }, + + /** + * @param {?DOMAgent.NodeId} nodeId + */ + highlightDOMNodeForTwoSeconds: function(nodeId) + { + this.highlightDOMNode(nodeId); + this._hideDOMNodeHighlightTimeout = setTimeout(this.hideDOMNodeHighlight.bind(this), 2000); + }, + + /** + * @param {boolean} enabled + * @param {function()=} callback + */ + setInspectModeEnabled: function(enabled, callback) + { + DOMAgent.setInspectModeEnabled(enabled, this._buildHighlightConfig(), callback); + }, + + /** + * @param {string=} mode + */ + _buildHighlightConfig: function(mode) + { + mode = mode || "all"; + var highlightConfig = { showInfo: mode === "all" }; + if (mode === "all" || mode === "content") + highlightConfig.contentColor = WebInspector.Color.PageHighlight.Content.toProtocolRGBA(); + + if (mode === "all" || mode === "padding") + highlightConfig.paddingColor = WebInspector.Color.PageHighlight.Padding.toProtocolRGBA(); + + if (mode === "all" || mode === "border") + highlightConfig.borderColor = WebInspector.Color.PageHighlight.Border.toProtocolRGBA(); + + if (mode === "all" || mode === "margin") + highlightConfig.marginColor = WebInspector.Color.PageHighlight.Margin.toProtocolRGBA(); + + return highlightConfig; + }, + + /** + * @param {WebInspector.DOMNode} node + * @param {function(?Protocol.Error)=} callback + * @return {function(?Protocol.Error)} + */ + _markRevision: function(node, callback) + { + function wrapperFunction(error) + { + if (callback) + callback(error); + if (error || !WebInspector.experimentsSettings.freeFlowDOMEditing.isEnabled()) + return; + if (this._captureDOMTimer) + clearTimeout(this._captureDOMTimer); + this._captureDOMTimer = setTimeout(this._captureDOM.bind(this, node), 500); + } + return wrapperFunction.bind(this); + }, + + /** + * @param {WebInspector.DOMNode} node + */ + _captureDOM: function(node) + { + delete this._captureDOMTimer; + if (!node.ownerDocument) + return; + + function callback(error, text) + { + if (error) { + console.error(error); + return; + } + + var url = node.ownerDocument.documentURL; + if (!url) + return; + + var resource = WebInspector.resourceForURL(url); + if (!resource) + return; + + resource.addRevision(text); + } + DOMAgent.getOuterHTML(node.ownerDocument.id, callback); + + } +} + +WebInspector.DOMAgent.prototype.__proto__ = WebInspector.Object.prototype; + +/** + * @constructor + * @implements {DOMAgent.Dispatcher} + * @param {WebInspector.DOMAgent} domAgent + */ +WebInspector.DOMDispatcher = function(domAgent) +{ + this._domAgent = domAgent; +} + +WebInspector.DOMDispatcher.prototype = { + documentUpdated: function() + { + this._domAgent._documentUpdated(); + }, + + /** + * @param {DOMAgent.NodeId} nodeId + * @param {string} name + * @param {string} value + */ + attributeModified: function(nodeId, name, value) + { + this._domAgent._attributeModified(nodeId, name, value); + }, + + /** + * @param {DOMAgent.NodeId} nodeId + * @param {string} name + */ + attributeRemoved: function(nodeId, name) + { + this._domAgent._attributeRemoved(nodeId, name); + }, + + /** + * @param {Array.} nodeIds + */ + inlineStyleInvalidated: function(nodeIds) + { + this._domAgent._inlineStyleInvalidated(nodeIds); + }, + + /** + * @param {DOMAgent.NodeId} nodeId + * @param {string} characterData + */ + characterDataModified: function(nodeId, characterData) + { + this._domAgent._characterDataModified(nodeId, characterData); + }, + + /** + * @param {DOMAgent.NodeId} parentId + * @param {Array.} payloads + */ + setChildNodes: function(parentId, payloads) + { + this._domAgent._setChildNodes(parentId, payloads); + }, + + /** + * @param {DOMAgent.NodeId} nodeId + * @param {number} childNodeCount + */ + childNodeCountUpdated: function(nodeId, childNodeCount) + { + this._domAgent._childNodeCountUpdated(nodeId, childNodeCount); + }, + + /** + * @param {DOMAgent.NodeId} parentNodeId + * @param {DOMAgent.NodeId} previousNodeId + * @param {DOMAgent.Node} payload + */ + childNodeInserted: function(parentNodeId, previousNodeId, payload) + { + this._domAgent._childNodeInserted(parentNodeId, previousNodeId, payload); + }, + + /** + * @param {DOMAgent.NodeId} parentNodeId + * @param {DOMAgent.NodeId} nodeId + */ + childNodeRemoved: function(parentNodeId, nodeId) + { + this._domAgent._childNodeRemoved(parentNodeId, nodeId); + } +} + +/** + * @type {?WebInspector.DOMAgent} + */ +WebInspector.domAgent = null; + +/** + * @constructor + * @implements {WebInspector.ResourceDomainModelBinding} + */ +WebInspector.DOMModelResourceBinding = function(domAgent) +{ + this._domAgent = domAgent; + WebInspector.Resource.registerDomainModelBinding(WebInspector.Resource.Type.Document, this); +} + +WebInspector.DOMModelResourceBinding.prototype = { + setContent: function(resource, content, majorChange, userCallback) + { + var frameId = resource.frameId; + if (!frameId) + return; + + PageAgent.setDocumentContent(frameId, content, callbackWrapper); + + function callbackWrapper(error) + { + if (majorChange) + resource.addRevision(content); + if (userCallback) + userCallback(error); + } + }, + + canSetContent: function() + { + return true; + } +} + +WebInspector.DOMModelResourceBinding.prototype.__proto__ = WebInspector.ResourceDomainModelBinding.prototype; +/* TimelineAgent.js */ + +/* + * Copyright (C) 2009 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + */ +WebInspector.TimelineAgent = function() { + // Not implemented. +} + +// Must be kept in sync with InspectorTimelineAgent.h +WebInspector.TimelineAgent.RecordType = { + EventDispatch: "EventDispatch", + Layout: "Layout", + RecalculateStyles: "RecalculateStyles", + Paint: "Paint", + ParseHTML: "ParseHTML", + + + TimerInstall: "TimerInstall", + TimerRemove: "TimerRemove", + TimerFire: "TimerFire", + + XHRReadyStateChange: "XHRReadyStateChange", + XHRLoad: "XHRLoad", + EvaluateScript: "EvaluateScript", + + TimeStamp: "TimeStamp", + + MarkLoad: "MarkLoad", + MarkDOMContent: "MarkDOMContent", + + ScheduleResourceRequest: "ScheduleResourceRequest", + ResourceSendRequest: "ResourceSendRequest", + ResourceReceiveResponse: "ResourceReceiveResponse", + ResourceReceivedData: "ResourceReceivedData", + ResourceFinish: "ResourceFinish", + + FunctionCall: "FunctionCall", + GCEvent: "GCEvent", + + RegisterAnimationFrameCallback: "RegisterAnimationFrameCallback", + CancelAnimationFrameCallback: "CancelAnimationFrameCallback", + FireAnimationFrameEvent: "FireAnimationFrameEvent" +} +/* TimelinePanel.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.Panel} + */ +WebInspector.TimelinePanel = function() +{ + WebInspector.Panel.call(this, "timeline"); + this.registerRequiredCSS("timelinePanel.css"); + + this.element.appendChild(this._createTopPane()); + this.element.addEventListener("contextmenu", this._contextMenu.bind(this), true); + this.element.tabIndex = 0; + + this._sidebarBackgroundElement = document.createElement("div"); + this._sidebarBackgroundElement.className = "sidebar split-view-sidebar-left timeline-sidebar-background"; + this.element.appendChild(this._sidebarBackgroundElement); + + this.createSplitViewWithSidebarTree(); + this._containerElement = this.splitView.element; + this._containerElement.id = "timeline-container"; + this._containerElement.addEventListener("scroll", this._onScroll.bind(this), false); + + var itemsTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("RECORDS"), {}, true); + this.sidebarTree.appendChild(itemsTreeElement); + + this._sidebarListElement = document.createElement("div"); + this.sidebarElement.appendChild(this._sidebarListElement); + + this._containerContentElement = this.splitView.mainElement; + this._containerContentElement.id = "resources-container-content"; + + this._timelineGrid = new WebInspector.TimelineGrid(); + this._itemsGraphsElement = this._timelineGrid.itemsGraphsElement; + this._itemsGraphsElement.id = "timeline-graphs"; + this._itemsGraphsElement.addEventListener("mousewheel", this._overviewPane.scrollWindow.bind(this._overviewPane), true); + this._containerContentElement.appendChild(this._timelineGrid.element); + + this._topGapElement = document.createElement("div"); + this._topGapElement.className = "timeline-gap"; + this._itemsGraphsElement.appendChild(this._topGapElement); + + this._graphRowsElement = document.createElement("div"); + this._itemsGraphsElement.appendChild(this._graphRowsElement); + + this._bottomGapElement = document.createElement("div"); + this._bottomGapElement.className = "timeline-gap"; + this._itemsGraphsElement.appendChild(this._bottomGapElement); + + this._expandElements = document.createElement("div"); + this._expandElements.id = "orphan-expand-elements"; + this._itemsGraphsElement.appendChild(this._expandElements); + + this._rootRecord = this._createRootRecord(); + this._sendRequestRecords = {}; + this._scheduledResourceRequests = {}; + this._timerRecords = {}; + this._registeredAnimationCallbackRecords = {}; + + this._calculator = new WebInspector.TimelineCalculator(); + this._calculator._showShortEvents = false; + var shortRecordThresholdTitle = Number.secondsToString(WebInspector.TimelinePanel.shortRecordThreshold); + this._showShortRecordsTitleText = WebInspector.UIString("Show the records that are shorter than %s", shortRecordThresholdTitle); + this._hideShortRecordsTitleText = WebInspector.UIString("Hide the records that are shorter than %s", shortRecordThresholdTitle); + this._createStatusbarButtons(); + + this._boundariesAreValid = true; + this._scrollTop = 0; + + this._popoverHelper = new WebInspector.PopoverHelper(this._containerElement, this._getPopoverAnchor.bind(this), this._showPopover.bind(this)); + this._containerElement.addEventListener("mousemove", this._mouseMove.bind(this), false); + this._containerElement.addEventListener("mouseout", this._mouseOut.bind(this), false); + + // Disable short events filter by default. + this.toggleFilterButton.toggled = true; + this._calculator._showShortEvents = this.toggleFilterButton.toggled; + this._timeStampRecords = []; + this._expandOffset = 15; + + this._createFileSelector(); + this._model = new WebInspector.TimelineModel(this); + + this._registerShortcuts(); + WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.EventTypes.TimelineEventRecorded, this._onTimelineEventRecorded, this); + this._linkifier = WebInspector.debuggerPresentationModel.createLinkifier(); +} + +// Define row height, should be in sync with styles for timeline graphs. +WebInspector.TimelinePanel.rowHeight = 18; +WebInspector.TimelinePanel.shortRecordThreshold = 0.015; + +WebInspector.TimelinePanel.prototype = { + _linkifyLocation: function(url, lineNumber, columnNumber) + { + // FIXME(62725): stack trace line/column numbers are one-based. + lineNumber = lineNumber ? lineNumber - 1 : lineNumber; + columnNumber = columnNumber ? columnNumber - 1 : 0; + return this._linkifier.linkifyLocation(url, lineNumber, columnNumber, "timeline-details"); + }, + + _linkifyCallFrame: function(callFrame) + { + return this._linkifyLocation(callFrame.url, callFrame.lineNumber, callFrame.columnNumber); + }, + + _createTopPane: function() { + var topPaneElement = document.createElement("div"); + topPaneElement.id = "timeline-overview-panel"; + + this._topPaneSidebarElement = document.createElement("div"); + this._topPaneSidebarElement.id = "timeline-overview-sidebar"; + + var overviewTreeElement = document.createElement("ol"); + overviewTreeElement.className = "sidebar-tree"; + this._topPaneSidebarElement.appendChild(overviewTreeElement); + topPaneElement.appendChild(this._topPaneSidebarElement); + + var topPaneSidebarTree = new TreeOutline(overviewTreeElement); + var timelinesOverviewItem = new WebInspector.SidebarTreeElement("resources-time-graph-sidebar-item", WebInspector.UIString("Timelines")); + topPaneSidebarTree.appendChild(timelinesOverviewItem); + timelinesOverviewItem.revealAndSelect(false); + timelinesOverviewItem.onselect = this._timelinesOverviewItemSelected.bind(this); + + var memoryOverviewItem = new WebInspector.SidebarTreeElement("resources-size-graph-sidebar-item", WebInspector.UIString("Memory")); + topPaneSidebarTree.appendChild(memoryOverviewItem); + memoryOverviewItem.onselect = this._memoryOverviewItemSelected.bind(this); + + this._overviewPane = new WebInspector.TimelineOverviewPane(this.categories); + this._overviewPane.addEventListener("window changed", this._windowChanged, this); + this._overviewPane.addEventListener("filter changed", this._refresh, this); + topPaneElement.appendChild(this._overviewPane.element); + + var separatorElement = document.createElement("div"); + separatorElement.id = "timeline-overview-separator"; + topPaneElement.appendChild(separatorElement); + return topPaneElement; + }, + + get toolbarItemLabel() + { + return WebInspector.UIString("Timeline"); + }, + + get statusBarItems() + { + return [this.toggleFilterButton.element, this.toggleTimelineButton.element, this.garbageCollectButton.element, this.clearButton.element, this._overviewPane.statusBarFilters]; + }, + + get categories() + { + if (!this._categories) { + this._categories = { + loading: new WebInspector.TimelineCategory("loading", WebInspector.UIString("Loading"), "rgb(47,102,236)"), + scripting: new WebInspector.TimelineCategory("scripting", WebInspector.UIString("Scripting"), "rgb(157,231,119)"), + rendering: new WebInspector.TimelineCategory("rendering", WebInspector.UIString("Rendering"), "rgb(164,60,255)") + }; + } + return this._categories; + }, + + get defaultFocusedElement() + { + return this.element; + }, + + get _recordStyles() + { + if (!this._recordStylesArray) { + var recordTypes = WebInspector.TimelineAgent.RecordType; + var recordStyles = {}; + recordStyles[recordTypes.EventDispatch] = { title: WebInspector.UIString("Event"), category: this.categories.scripting }; + recordStyles[recordTypes.Layout] = { title: WebInspector.UIString("Layout"), category: this.categories.rendering }; + recordStyles[recordTypes.RecalculateStyles] = { title: WebInspector.UIString("Recalculate Style"), category: this.categories.rendering }; + recordStyles[recordTypes.Paint] = { title: WebInspector.UIString("Paint"), category: this.categories.rendering }; + recordStyles[recordTypes.ParseHTML] = { title: WebInspector.UIString("Parse"), category: this.categories.loading }; + recordStyles[recordTypes.TimerInstall] = { title: WebInspector.UIString("Install Timer"), category: this.categories.scripting }; + recordStyles[recordTypes.TimerRemove] = { title: WebInspector.UIString("Remove Timer"), category: this.categories.scripting }; + recordStyles[recordTypes.TimerFire] = { title: WebInspector.UIString("Timer Fired"), category: this.categories.scripting }; + recordStyles[recordTypes.XHRReadyStateChange] = { title: WebInspector.UIString("XHR Ready State Change"), category: this.categories.scripting }; + recordStyles[recordTypes.XHRLoad] = { title: WebInspector.UIString("XHR Load"), category: this.categories.scripting }; + recordStyles[recordTypes.EvaluateScript] = { title: WebInspector.UIString("Evaluate Script"), category: this.categories.scripting }; + recordStyles[recordTypes.TimeStamp] = { title: WebInspector.UIString("Stamp"), category: this.categories.scripting }; + recordStyles[recordTypes.ResourceSendRequest] = { title: WebInspector.UIString("Send Request"), category: this.categories.loading }; + recordStyles[recordTypes.ResourceReceiveResponse] = { title: WebInspector.UIString("Receive Response"), category: this.categories.loading }; + recordStyles[recordTypes.ResourceFinish] = { title: WebInspector.UIString("Finish Loading"), category: this.categories.loading }; + recordStyles[recordTypes.FunctionCall] = { title: WebInspector.UIString("Function Call"), category: this.categories.scripting }; + recordStyles[recordTypes.ResourceReceivedData] = { title: WebInspector.UIString("Receive Data"), category: this.categories.loading }; + recordStyles[recordTypes.GCEvent] = { title: WebInspector.UIString("GC Event"), category: this.categories.scripting }; + recordStyles[recordTypes.MarkDOMContent] = { title: WebInspector.UIString("DOMContent event"), category: this.categories.scripting }; + recordStyles[recordTypes.MarkLoad] = { title: WebInspector.UIString("Load event"), category: this.categories.scripting }; + recordStyles[recordTypes.ScheduleResourceRequest] = { title: WebInspector.UIString("Schedule Request"), category: this.categories.loading }; + recordStyles[recordTypes.RegisterAnimationFrameCallback] = { title: WebInspector.UIString("Register Animation Callback"), category: this.categories.scripting }; + recordStyles[recordTypes.CancelAnimationFrameCallback] = { title: WebInspector.UIString("Cancel Animation Callback"), category: this.categories.scripting }; + recordStyles[recordTypes.FireAnimationFrameEvent] = { title: WebInspector.UIString("Animation Frame Event"), category: this.categories.scripting }; + this._recordStylesArray = recordStyles; + } + return this._recordStylesArray; + }, + + _createStatusbarButtons: function() + { + this.toggleTimelineButton = new WebInspector.StatusBarButton(WebInspector.UIString("Record"), "record-profile-status-bar-item"); + this.toggleTimelineButton.addEventListener("click", this._toggleTimelineButtonClicked, this); + + this.clearButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear"), "clear-status-bar-item"); + this.clearButton.addEventListener("click", this._clearPanel, this); + + this.toggleFilterButton = new WebInspector.StatusBarButton(this._hideShortRecordsTitleText, "timeline-filter-status-bar-item"); + this.toggleFilterButton.addEventListener("click", this._toggleFilterButtonClicked, this); + + this.garbageCollectButton = new WebInspector.StatusBarButton(WebInspector.UIString("Collect Garbage"), "garbage-collect-status-bar-item"); + this.garbageCollectButton.addEventListener("click", this._garbageCollectButtonClicked, this); + + this.recordsCounter = document.createElement("span"); + this.recordsCounter.className = "timeline-records-counter"; + }, + + _registerShortcuts: function() + { + var shortcut = WebInspector.KeyboardShortcut; + var modifiers = shortcut.Modifiers; + var section = WebInspector.shortcutsScreen.section(WebInspector.UIString("Timeline Panel")); + + this._shortcuts[shortcut.makeKey("e", modifiers.CtrlOrMeta)] = this._toggleTimelineButtonClicked.bind(this); + section.addKey(shortcut.shortcutToString("e", modifiers.CtrlOrMeta), WebInspector.UIString("Start/stop recording")); + + this._shortcuts[shortcut.makeKey("s", modifiers.CtrlOrMeta)] = this._saveToFile.bind(this); + section.addKey(shortcut.shortcutToString("s", modifiers.CtrlOrMeta), WebInspector.UIString("Save Timeline data\u2026")); + + this._shortcuts[shortcut.makeKey("o", modifiers.CtrlOrMeta)] = this._fileSelectorElement.click.bind(this._fileSelectorElement); + section.addKey(shortcut.shortcutToString("o", modifiers.CtrlOrMeta), WebInspector.UIString("Load Timeline data\u2026")); + }, + + _createFileSelector: function() + { + if (this._fileSelectorElement) + this.element.removeChild(this._fileSelectorElement); + + var fileSelectorElement = document.createElement("input"); + fileSelectorElement.type = "file"; + fileSelectorElement.style.zIndex = -1; + fileSelectorElement.style.position = "absolute"; + fileSelectorElement.onchange = this._loadFromFile.bind(this); + this.element.appendChild(fileSelectorElement); + this._fileSelectorElement = fileSelectorElement; + }, + + _contextMenu: function(event) + { + var contextMenu = new WebInspector.ContextMenu(); + contextMenu.appendItem(WebInspector.UIString("&Save Timeline data\u2026"), this._saveToFile.bind(this)); + contextMenu.appendItem(WebInspector.UIString("L&oad Timeline data\u2026"), this._fileSelectorElement.click.bind(this._fileSelectorElement)); + contextMenu.show(event); + }, + + _saveToFile: function() + { + this._model._saveToFile(); + }, + + _loadFromFile: function() + { + if (this.toggleTimelineButton.toggled) + WebInspector.timelineManager.stop(); + + this._clearPanel(); + + this._model._loadFromFile(this._fileSelectorElement.files[0]); + this._createFileSelector(); + }, + + _updateRecordsCounter: function() + { + this.recordsCounter.textContent = WebInspector.UIString("%d of %d captured records are visible", this._rootRecord._visibleRecordsCount, this._rootRecord._allRecordsCount); + }, + + _updateEventDividers: function() + { + this._timelineGrid.removeEventDividers(); + var clientWidth = this._graphRowsElement.offsetWidth - this._expandOffset; + var dividers = []; + for (var i = 0; i < this._timeStampRecords.length; ++i) { + var record = this._timeStampRecords[i]; + var positions = this._calculator.computeBarGraphWindowPosition(record, clientWidth); + var dividerPosition = Math.round(positions.left); + if (dividerPosition < 0 || dividerPosition >= clientWidth || dividers[dividerPosition]) + continue; + var divider = this._createEventDivider(record); + divider.style.left = (dividerPosition + this._expandOffset) + "px"; + dividers[dividerPosition] = divider; + } + this._timelineGrid.addEventDividers(dividers); + this._overviewPane.updateEventDividers(this._timeStampRecords, this._createEventDivider.bind(this)); + }, + + _createEventDivider: function(record) + { + var eventDivider = document.createElement("div"); + eventDivider.className = "resources-event-divider"; + var recordTypes = WebInspector.TimelineAgent.RecordType; + + var eventDividerPadding = document.createElement("div"); + eventDividerPadding.className = "resources-event-divider-padding"; + eventDividerPadding.title = record.title; + + if (record.type === recordTypes.MarkDOMContent) + eventDivider.className += " resources-blue-divider"; + else if (record.type === recordTypes.MarkLoad) + eventDivider.className += " resources-red-divider"; + else if (record.type === recordTypes.TimeStamp) { + eventDivider.className += " resources-orange-divider"; + eventDividerPadding.title = record.data["message"]; + } + eventDividerPadding.appendChild(eventDivider); + return eventDividerPadding; + }, + + _timelinesOverviewItemSelected: function(event) + { + this._overviewPane.showTimelines(); + }, + + _memoryOverviewItemSelected: function(event) + { + this._overviewPane.showMemoryGraph(this._rootRecord.children); + }, + + _toggleTimelineButtonClicked: function() + { + if (this.toggleTimelineButton.toggled) + WebInspector.timelineManager.stop(); + else { + this._clearPanel(); + WebInspector.timelineManager.start(30); + WebInspector.userMetrics.TimelineStarted.record(); + } + this.toggleTimelineButton.toggled = !this.toggleTimelineButton.toggled; + }, + + _toggleFilterButtonClicked: function() + { + this.toggleFilterButton.toggled = !this.toggleFilterButton.toggled; + this._calculator._showShortEvents = this.toggleFilterButton.toggled; + this.toggleFilterButton.element.title = this._calculator._showShortEvents ? this._hideShortRecordsTitleText : this._showShortRecordsTitleText; + this._scheduleRefresh(true); + }, + + _garbageCollectButtonClicked: function() + { + ProfilerAgent.collectGarbage(); + }, + + _onTimelineEventRecorded: function(event) + { + if (this.toggleTimelineButton.toggled) + this._addRecordToTimeline(event.data); + }, + + _addRecordToTimeline: function(record) + { + this._model._addRecord(record); + this._innerAddRecordToTimeline(record, this._rootRecord); + this._scheduleRefresh(false); + }, + + _findParentRecord: function(record) + { + var recordTypes = WebInspector.TimelineAgent.RecordType; + var parentRecord; + if (record.type === recordTypes.ResourceReceiveResponse || + record.type === recordTypes.ResourceFinish || + record.type === recordTypes.ResourceReceivedData) + parentRecord = this._sendRequestRecords[record.data["requestId"]]; + else if (record.type === recordTypes.TimerFire) + parentRecord = this._timerRecords[record.data["timerId"]]; + else if (record.type === recordTypes.ResourceSendRequest) + parentRecord = this._scheduledResourceRequests[record.data["url"]]; + return parentRecord; + }, + + _innerAddRecordToTimeline: function(record, parentRecord) + { + var connectedToOldRecord = false; + var recordTypes = WebInspector.TimelineAgent.RecordType; + + if (record.type === recordTypes.RegisterAnimationFrameCallback) { + this._registeredAnimationCallbackRecords[record.data["id"]] = record; + return; + } + + if (record.type === recordTypes.MarkDOMContent || record.type === recordTypes.MarkLoad) + parentRecord = null; // No bar entry for load events. + else if (parentRecord === this._rootRecord || + record.type === recordTypes.ResourceReceiveResponse || + record.type === recordTypes.ResourceFinish || + record.type === recordTypes.ResourceReceivedData) { + var newParentRecord = this._findParentRecord(record); + if (newParentRecord) { + parentRecord = newParentRecord; + connectedToOldRecord = true; + } + } + + var children = record.children; + var scriptDetails; + if (record.data && record.data["scriptName"]) { + scriptDetails = { + scriptName: record.data["scriptName"], + scriptLine: record.data["scriptLine"] + } + }; + + if ((record.type === recordTypes.TimerFire || record.type === recordTypes.FireAnimationFrameEvent) && children && children.length) { + var childRecord = children[0]; + if (childRecord.type === recordTypes.FunctionCall) { + scriptDetails = { + scriptName: childRecord.data["scriptName"], + scriptLine: childRecord.data["scriptLine"] + }; + children = childRecord.children.concat(children.slice(1)); + } + } + + var formattedRecord = new WebInspector.TimelinePanel.FormattedRecord(record, parentRecord, this, scriptDetails); + + if (record.type === recordTypes.MarkDOMContent || record.type === recordTypes.MarkLoad) { + this._timeStampRecords.push(formattedRecord); + return; + } + + ++this._rootRecord._allRecordsCount; + formattedRecord.collapsed = (parentRecord === this._rootRecord); + + var childrenCount = children ? children.length : 0; + for (var i = 0; i < childrenCount; ++i) + this._innerAddRecordToTimeline(children[i], formattedRecord); + + formattedRecord._calculateAggregatedStats(this.categories); + + if (connectedToOldRecord) { + record = formattedRecord; + do { + var parent = record.parent; + parent._cpuTime += formattedRecord._cpuTime; + if (parent._lastChildEndTime < record._lastChildEndTime) + parent._lastChildEndTime = record._lastChildEndTime; + for (var category in formattedRecord._aggregatedStats) + parent._aggregatedStats[category] += formattedRecord._aggregatedStats[category]; + record = parent; + } while (record.parent); + } else + if (parentRecord !== this._rootRecord) + parentRecord._selfTime -= formattedRecord.endTime - formattedRecord.startTime; + + // Keep bar entry for mark timeline since nesting might be interesting to the user. + if (record.type === recordTypes.TimeStamp) + this._timeStampRecords.push(formattedRecord); + }, + + sidebarResized: function(event) + { + var width = event.data; + this._sidebarBackgroundElement.style.width = width + "px"; + this._topPaneSidebarElement.style.width = width + "px"; + this._scheduleRefresh(false); + this._overviewPane.sidebarResized(width); + }, + + onResize: function() + { + this._closeRecordDetails(); + this._scheduleRefresh(false); + }, + + _createRootRecord: function() + { + var rootRecord = {}; + rootRecord.children = []; + rootRecord._visibleRecordsCount = 0; + rootRecord._allRecordsCount = 0; + rootRecord._aggregatedStats = {}; + return rootRecord; + }, + + _clearPanel: function() + { + this._timeStampRecords = []; + this._sendRequestRecords = {}; + this._scheduledResourceRequests = {}; + this._timerRecords = {}; + this._registeredAnimationCallbackRecords = {}; + this._rootRecord = this._createRootRecord(); + this._boundariesAreValid = false; + this._overviewPane.reset(); + this._adjustScrollPosition(0); + this._refresh(); + this._closeRecordDetails(); + this._model._reset(); + this._linkifier.reset(); + }, + + elementsToRestoreScrollPositionsFor: function() + { + return [this._containerElement]; + }, + + wasShown: function() + { + WebInspector.Panel.prototype.wasShown.call(this); + this._refresh(); + WebInspector.drawer.currentPanelCounters = this.recordsCounter; + }, + + willHide: function() + { + this._closeRecordDetails(); + WebInspector.drawer.currentPanelCounters = null; + WebInspector.Panel.prototype.willHide.call(this); + }, + + _onScroll: function(event) + { + this._closeRecordDetails(); + var scrollTop = this._containerElement.scrollTop; + var dividersTop = Math.max(0, scrollTop); + this._timelineGrid.setScrollAndDividerTop(scrollTop, dividersTop); + this._scheduleRefresh(true); + }, + + _windowChanged: function() + { + this._closeRecordDetails(); + this._scheduleRefresh(false); + }, + + _scheduleRefresh: function(preserveBoundaries) + { + this._closeRecordDetails(); + this._boundariesAreValid &= preserveBoundaries; + + if (!this.isShowing()) + return; + + if (preserveBoundaries) + this._refresh(); + else + if (!this._refreshTimeout) + this._refreshTimeout = setTimeout(this._refresh.bind(this), 100); + }, + + _refresh: function() + { + if (this._refreshTimeout) { + clearTimeout(this._refreshTimeout); + delete this._refreshTimeout; + } + + this._overviewPane.update(this._rootRecord.children, this._calculator._showShortEvents); + this._refreshRecords(!this._boundariesAreValid); + this._updateRecordsCounter(); + if(!this._boundariesAreValid) + this._updateEventDividers(); + this._boundariesAreValid = true; + }, + + _updateBoundaries: function() + { + this._calculator.reset(); + this._calculator.windowLeft = this._overviewPane.windowLeft; + this._calculator.windowRight = this._overviewPane.windowRight; + + for (var i = 0; i < this._rootRecord.children.length; ++i) + this._calculator.updateBoundaries(this._rootRecord.children[i]); + + this._calculator.calculateWindow(); + }, + + /** + * @param {boolean=} parentIsCollapsed + */ + _addToRecordsWindow: function(record, recordsWindow, parentIsCollapsed) + { + if (!this._calculator._showShortEvents && !record.isLong()) + return; + var percentages = this._calculator.computeBarGraphPercentages(record); + if (percentages.start < 100 && percentages.endWithChildren >= 0 && !record.category.hidden) { + ++this._rootRecord._visibleRecordsCount; + ++record.parent._invisibleChildrenCount; + if (!parentIsCollapsed) + recordsWindow.push(record); + } + + var index = recordsWindow.length; + record._invisibleChildrenCount = 0; + for (var i = 0; i < record.children.length; ++i) + this._addToRecordsWindow(record.children[i], recordsWindow, parentIsCollapsed || record.collapsed); + record._visibleChildrenCount = recordsWindow.length - index; + }, + + _filterRecords: function() + { + var recordsInWindow = []; + this._rootRecord._visibleRecordsCount = 0; + for (var i = 0; i < this._rootRecord.children.length; ++i) + this._addToRecordsWindow(this._rootRecord.children[i], recordsInWindow); + return recordsInWindow; + }, + + _refreshRecords: function(updateBoundaries) + { + if (updateBoundaries) + this._updateBoundaries(); + + var recordsInWindow = this._filterRecords(); + + // Calculate the visible area. + this._scrollTop = this._containerElement.scrollTop; + var visibleTop = this._scrollTop; + var visibleBottom = visibleTop + this._containerElement.clientHeight; + + const rowHeight = WebInspector.TimelinePanel.rowHeight; + + // Convert visible area to visible indexes. Always include top-level record for a visible nested record. + var startIndex = Math.max(0, Math.min(Math.floor(visibleTop / rowHeight) - 1, recordsInWindow.length - 1)); + var endIndex = Math.min(recordsInWindow.length, Math.ceil(visibleBottom / rowHeight)); + + // Resize gaps first. + const top = (startIndex * rowHeight) + "px"; + this._topGapElement.style.height = top; + this.sidebarElement.style.top = top; + this.splitView.sidebarResizerElement.style.top = top; + this._bottomGapElement.style.height = (recordsInWindow.length - endIndex) * rowHeight + "px"; + + // Update visible rows. + var listRowElement = this._sidebarListElement.firstChild; + var width = this._graphRowsElement.offsetWidth; + this._itemsGraphsElement.removeChild(this._graphRowsElement); + var graphRowElement = this._graphRowsElement.firstChild; + var scheduleRefreshCallback = this._scheduleRefresh.bind(this, true); + this._itemsGraphsElement.removeChild(this._expandElements); + this._expandElements.removeChildren(); + + for (var i = 0; i < endIndex; ++i) { + var record = recordsInWindow[i]; + var isEven = !(i % 2); + + if (i < startIndex) { + var lastChildIndex = i + record._visibleChildrenCount; + if (lastChildIndex >= startIndex && lastChildIndex < endIndex) { + var expandElement = new WebInspector.TimelineExpandableElement(this._expandElements); + expandElement._update(record, i, this._calculator.computeBarGraphWindowPosition(record, width - this._expandOffset)); + } + } else { + if (!listRowElement) { + listRowElement = new WebInspector.TimelineRecordListRow().element; + this._sidebarListElement.appendChild(listRowElement); + } + if (!graphRowElement) { + graphRowElement = new WebInspector.TimelineRecordGraphRow(this._itemsGraphsElement, scheduleRefreshCallback).element; + this._graphRowsElement.appendChild(graphRowElement); + } + + listRowElement.row.update(record, isEven, this._calculator, visibleTop); + graphRowElement.row.update(record, isEven, this._calculator, width, this._expandOffset, i); + + listRowElement = listRowElement.nextSibling; + graphRowElement = graphRowElement.nextSibling; + } + } + + // Remove extra rows. + while (listRowElement) { + var nextElement = listRowElement.nextSibling; + listRowElement.row.dispose(); + listRowElement = nextElement; + } + while (graphRowElement) { + var nextElement = graphRowElement.nextSibling; + graphRowElement.row.dispose(); + graphRowElement = nextElement; + } + + this._itemsGraphsElement.insertBefore(this._graphRowsElement, this._bottomGapElement); + this._itemsGraphsElement.appendChild(this._expandElements); + this.splitView.sidebarResizerElement.style.height = this.sidebarElement.clientHeight + "px"; + // Reserve some room for expand / collapse controls to the left for records that start at 0ms. + var timelinePaddingLeft = this._calculator.windowLeft === 0 ? this._expandOffset : 0; + if (updateBoundaries) + this._timelineGrid.updateDividers(true, this._calculator, timelinePaddingLeft); + this._adjustScrollPosition((recordsInWindow.length + 1) * rowHeight); + }, + + _adjustScrollPosition: function(totalHeight) + { + // Prevent the container from being scrolled off the end. + if ((this._containerElement.scrollTop + this._containerElement.offsetHeight) > totalHeight + 1) + this._containerElement.scrollTop = (totalHeight - this._containerElement.offsetHeight); + }, + + _getPopoverAnchor: function(element) + { + return element.enclosingNodeOrSelfWithClass("timeline-graph-bar") || element.enclosingNodeOrSelfWithClass("timeline-tree-item"); + }, + + _mouseOut: function(e) + { + this._hideRectHighlight(); + }, + + _mouseMove: function(e) + { + var anchor = this._getPopoverAnchor(e.target); + + if (anchor && anchor.row._record.type === "Paint") + this._highlightRect(anchor.row._record); + else + this._hideRectHighlight(); + }, + + _highlightRect: function(record) + { + if (this._highlightedRect === record.data) + return; + this._highlightedRect = record.data; + DOMAgent.highlightRect(this._highlightedRect.x, this._highlightedRect.y, this._highlightedRect.width, this._highlightedRect.height, WebInspector.Color.PageHighlight.Content.toProtocolRGBA(), WebInspector.Color.PageHighlight.ContentOutline.toProtocolRGBA()); + }, + + _hideRectHighlight: function() + { + if (this._highlightedRect) { + delete this._highlightedRect; + DOMAgent.hideHighlight(); + } + }, + + _showPopover: function(anchor, popover) + { + var record = anchor.row._record; + popover.show(record._generatePopupContent(this._calculator, this.categories), anchor); + }, + + _closeRecordDetails: function() + { + this._popoverHelper.hidePopover(); + } +} + +WebInspector.TimelinePanel.prototype.__proto__ = WebInspector.Panel.prototype; + +/** + * @constructor + */ +WebInspector.TimelineCategory = function(name, title, color) +{ + this.name = name; + this.title = title; + this.color = color; +} + +/** + * @constructor + */ +WebInspector.TimelineCalculator = function() +{ + this.reset(); + this.windowLeft = 0.0; + this.windowRight = 1.0; +} + +WebInspector.TimelineCalculator.prototype = { + computeBarGraphPercentages: function(record) + { + var start = (record.startTime - this.minimumBoundary) / this.boundarySpan * 100; + var end = (record.startTime + record._selfTime - this.minimumBoundary) / this.boundarySpan * 100; + var endWithChildren = (record._lastChildEndTime - this.minimumBoundary) / this.boundarySpan * 100; + var cpuWidth = record._cpuTime / this.boundarySpan * 100; + return {start: start, end: end, endWithChildren: endWithChildren, cpuWidth: cpuWidth}; + }, + + computeBarGraphWindowPosition: function(record, clientWidth) + { + const minWidth = 5; + const borderWidth = 4; + var workingArea = clientWidth - minWidth - borderWidth; + var percentages = this.computeBarGraphPercentages(record); + var left = percentages.start / 100 * workingArea; + var width = (percentages.end - percentages.start) / 100 * workingArea + minWidth; + var widthWithChildren = (percentages.endWithChildren - percentages.start) / 100 * workingArea; + var cpuWidth = percentages.cpuWidth / 100 * workingArea + minWidth; + if (percentages.endWithChildren > percentages.end) + widthWithChildren += borderWidth + minWidth; + return {left: left, width: width, widthWithChildren: widthWithChildren, cpuWidth: cpuWidth}; + }, + + calculateWindow: function() + { + this.minimumBoundary = this._absoluteMinimumBoundary + this.windowLeft * (this._absoluteMaximumBoundary - this._absoluteMinimumBoundary); + this.maximumBoundary = this._absoluteMinimumBoundary + this.windowRight * (this._absoluteMaximumBoundary - this._absoluteMinimumBoundary); + this.boundarySpan = this.maximumBoundary - this.minimumBoundary; + }, + + reset: function() + { + this._absoluteMinimumBoundary = -1; + this._absoluteMaximumBoundary = -1; + }, + + updateBoundaries: function(record) + { + var lowerBound = record.startTime; + if (this._absoluteMinimumBoundary === -1 || lowerBound < this._absoluteMinimumBoundary) + this._absoluteMinimumBoundary = lowerBound; + + const minimumTimeFrame = 0.1; + const minimumDeltaForZeroSizeEvents = 0.01; + var upperBound = Math.max(record._lastChildEndTime + minimumDeltaForZeroSizeEvents, lowerBound + minimumTimeFrame); + if (this._absoluteMaximumBoundary === -1 || upperBound > this._absoluteMaximumBoundary) + this._absoluteMaximumBoundary = upperBound; + }, + + formatValue: function(value) + { + return Number.secondsToString(value + this.minimumBoundary - this._absoluteMinimumBoundary); + } +} + +/** + * @constructor + */ +WebInspector.TimelineRecordListRow = function() +{ + this.element = document.createElement("div"); + this.element.row = this; + this.element.style.cursor = "pointer"; + var iconElement = document.createElement("span"); + iconElement.className = "timeline-tree-icon"; + this.element.appendChild(iconElement); + + this._typeElement = document.createElement("span"); + this._typeElement.className = "type"; + this.element.appendChild(this._typeElement); + + var separatorElement = document.createElement("span"); + separatorElement.className = "separator"; + separatorElement.textContent = " "; + + this._dataElement = document.createElement("span"); + this._dataElement.className = "data dimmed"; + + this.element.appendChild(separatorElement); + this.element.appendChild(this._dataElement); +} + +WebInspector.TimelineRecordListRow.prototype = { + update: function(record, isEven, calculator, offset) + { + this._record = record; + this._calculator = calculator; + this._offset = offset; + + this.element.className = "timeline-tree-item timeline-category-" + record.category.name + (isEven ? " even" : ""); + this._typeElement.textContent = record.title; + + if (this._dataElement.firstChild) + this._dataElement.removeChildren(); + if (record.details) { + var detailsContainer = document.createElement("span"); + if (typeof record.details === "object") { + detailsContainer.appendChild(document.createTextNode("(")); + detailsContainer.appendChild(record.details); + detailsContainer.appendChild(document.createTextNode(")")); + } else + detailsContainer.textContent = "(" + record.details + ")"; + this._dataElement.appendChild(detailsContainer); + } + }, + + dispose: function() + { + this.element.parentElement.removeChild(this.element); + } +} + +/** + * @constructor + */ +WebInspector.TimelineRecordGraphRow = function(graphContainer, scheduleRefresh) +{ + this.element = document.createElement("div"); + this.element.row = this; + + this._barAreaElement = document.createElement("div"); + this._barAreaElement.className = "timeline-graph-bar-area"; + this.element.appendChild(this._barAreaElement); + + this._barWithChildrenElement = document.createElement("div"); + this._barWithChildrenElement.className = "timeline-graph-bar with-children"; + this._barWithChildrenElement.row = this; + this._barAreaElement.appendChild(this._barWithChildrenElement); + + this._barCpuElement = document.createElement("div"); + this._barCpuElement.className = "timeline-graph-bar cpu" + this._barCpuElement.row = this; + this._barAreaElement.appendChild(this._barCpuElement); + + this._barElement = document.createElement("div"); + this._barElement.className = "timeline-graph-bar"; + this._barElement.row = this; + this._barAreaElement.appendChild(this._barElement); + + this._expandElement = new WebInspector.TimelineExpandableElement(graphContainer); + this._expandElement._element.addEventListener("click", this._onClick.bind(this)); + + this._scheduleRefresh = scheduleRefresh; +} + +WebInspector.TimelineRecordGraphRow.prototype = { + update: function(record, isEven, calculator, clientWidth, expandOffset, index) + { + this._record = record; + this.element.className = "timeline-graph-side timeline-category-" + record.category.name + (isEven ? " even" : ""); + var barPosition = calculator.computeBarGraphWindowPosition(record, clientWidth - expandOffset); + this._barWithChildrenElement.style.left = barPosition.left + expandOffset + "px"; + this._barWithChildrenElement.style.width = barPosition.widthWithChildren + "px"; + this._barElement.style.left = barPosition.left + expandOffset + "px"; + this._barElement.style.width = barPosition.width + "px"; + this._barCpuElement.style.left = barPosition.left + expandOffset + "px"; + this._barCpuElement.style.width = barPosition.cpuWidth + "px"; + this._expandElement._update(record, index, barPosition); + }, + + _onClick: function(event) + { + this._record.collapsed = !this._record.collapsed; + this._scheduleRefresh(false); + }, + + dispose: function() + { + this.element.parentElement.removeChild(this.element); + this._expandElement._dispose(); + } +} + +/** + * @constructor + */ +WebInspector.TimelinePanel.FormattedRecord = function(record, parentRecord, panel, scriptDetails) +{ + this._panel = panel; + var recordTypes = WebInspector.TimelineAgent.RecordType; + var style = panel._recordStyles[record.type]; + this.parent = parentRecord; + if (parentRecord) + parentRecord.children.push(this); + this.category = style.category; + this.title = style.title; + this.startTime = record.startTime / 1000; + this.data = record.data; + this.type = record.type; + this.endTime = (typeof record.endTime !== "undefined") ? record.endTime / 1000 : this.startTime; + this._selfTime = this.endTime - this.startTime; + this._lastChildEndTime = this.endTime; + if (record.stackTrace && record.stackTrace.length) + this.stackTrace = record.stackTrace; + this.totalHeapSize = record.totalHeapSize; + this.usedHeapSize = record.usedHeapSize; + if (record.data && record.data["url"]) + this.url = record.data["url"]; + if (scriptDetails) { + this.scriptName = scriptDetails.scriptName; + this.scriptLine = scriptDetails.scriptLine; + } + // Make resource receive record last since request was sent; make finish record last since response received. + if (record.type === recordTypes.ResourceSendRequest) { + panel._sendRequestRecords[record.data["requestId"]] = this; + } else if (record.type === recordTypes.ScheduleResourceRequest) { + panel._scheduledResourceRequests[record.data["url"]] = this; + } else if (record.type === recordTypes.ResourceReceiveResponse) { + var sendRequestRecord = panel._sendRequestRecords[record.data["requestId"]]; + if (sendRequestRecord) { // False if we started instrumentation in the middle of request. + this.url = sendRequestRecord.url; + // Now that we have resource in the collection, recalculate details in order to display short url. + sendRequestRecord._refreshDetails(); + if (sendRequestRecord.parent !== panel._rootRecord && sendRequestRecord.parent.type === recordTypes.ScheduleResourceRequest) + sendRequestRecord.parent._refreshDetails(); + } + } else if (record.type === recordTypes.ResourceReceivedData || record.type === recordTypes.ResourceFinish) { + var sendRequestRecord = panel._sendRequestRecords[record.data["requestId"]]; + if (sendRequestRecord) // False for main resource. + this.url = sendRequestRecord.url; + } else if (record.type === recordTypes.TimerInstall) { + this.timeout = record.data["timeout"]; + this.singleShot = record.data["singleShot"]; + panel._timerRecords[record.data["timerId"]] = this; + } else if (record.type === recordTypes.TimerFire) { + var timerInstalledRecord = panel._timerRecords[record.data["timerId"]]; + if (timerInstalledRecord) { + this.callSiteStackTrace = timerInstalledRecord.stackTrace; + this.timeout = timerInstalledRecord.timeout; + this.singleShot = timerInstalledRecord.singleShot; + } + } else if (record.type === recordTypes.FireAnimationFrameEvent) { + var registerCallbackRecord = panel._registeredAnimationCallbackRecords[record.data["id"]]; + if (registerCallbackRecord) + this.callSiteStackTrace = registerCallbackRecord.stackTrace; + } + this._refreshDetails(); +} + +WebInspector.TimelinePanel.FormattedRecord.prototype = { + isLong: function() + { + return (this._lastChildEndTime - this.startTime) > WebInspector.TimelinePanel.shortRecordThreshold; + }, + + get children() + { + if (!this._children) + this._children = []; + return this._children; + }, + + _generateAggregatedInfo: function() + { + var cell = document.createElement("span"); + cell.className = "timeline-aggregated-info"; + for (var index in this._aggregatedStats) { + var label = document.createElement("div"); + label.className = "timeline-aggregated-category timeline-" + index; + cell.appendChild(label); + var text = document.createElement("span"); + text.textContent = Number.secondsToString(this._aggregatedStats[index] + 0.0001); + cell.appendChild(text); + } + return cell; + }, + + _generatePopupContent: function(calculator, categories) + { + var contentHelper = new WebInspector.TimelinePanel.PopupContentHelper(this.title, this._panel); + + if (this._children && this._children.length) { + contentHelper._appendTextRow(WebInspector.UIString("Self Time"), Number.secondsToString(this._selfTime + 0.0001)); + contentHelper._appendElementRow(WebInspector.UIString("Aggregated Time"), this._generateAggregatedInfo()); + } + var text = WebInspector.UIString("%s (at %s)", Number.secondsToString(this._lastChildEndTime - this.startTime), + calculator.formatValue(this.startTime - calculator.minimumBoundary)); + contentHelper._appendTextRow(WebInspector.UIString("Duration"), text); + + const recordTypes = WebInspector.TimelineAgent.RecordType; + + switch (this.type) { + case recordTypes.GCEvent: + contentHelper._appendTextRow(WebInspector.UIString("Collected"), Number.bytesToString(this.data["usedHeapSizeDelta"])); + break; + case recordTypes.TimerInstall: + case recordTypes.TimerFire: + case recordTypes.TimerRemove: + contentHelper._appendTextRow(WebInspector.UIString("Timer ID"), this.data["timerId"]); + if (typeof this.timeout === "number") { + contentHelper._appendTextRow(WebInspector.UIString("Timeout"), Number.secondsToString(this.timeout / 1000)); + contentHelper._appendTextRow(WebInspector.UIString("Repeats"), !this.singleShot); + } + break; + case recordTypes.FireAnimationFrameEvent: + contentHelper._appendTextRow(WebInspector.UIString("Callback ID"), this.data["id"]); + break; + case recordTypes.FunctionCall: + contentHelper._appendLinkRow(WebInspector.UIString("Location"), this.scriptName, this.scriptLine); + break; + case recordTypes.ScheduleResourceRequest: + case recordTypes.ResourceSendRequest: + case recordTypes.ResourceReceiveResponse: + case recordTypes.ResourceReceivedData: + case recordTypes.ResourceFinish: + contentHelper._appendLinkRow(WebInspector.UIString("Resource"), this.url); + if (this.data["requestMethod"]) + contentHelper._appendTextRow(WebInspector.UIString("Request Method"), this.data["requestMethod"]); + if (typeof this.data["statusCode"] === "number") + contentHelper._appendTextRow(WebInspector.UIString("Status Code"), this.data["statusCode"]); + if (this.data["mimeType"]) + contentHelper._appendTextRow(WebInspector.UIString("MIME Type"), this.data["mimeType"]); + break; + case recordTypes.EvaluateScript: + if (this.data && this.url) + contentHelper._appendLinkRow(WebInspector.UIString("Script"), this.url, this.data["lineNumber"]); + break; + case recordTypes.Paint: + contentHelper._appendTextRow(WebInspector.UIString("Location"), WebInspector.UIString("(%d, %d)", this.data["x"], this.data["y"])); + contentHelper._appendTextRow(WebInspector.UIString("Dimensions"), WebInspector.UIString("%d × %d", this.data["width"], this.data["height"])); + case recordTypes.RecalculateStyles: // We don't want to see default details. + break; + default: + if (this.details) + contentHelper._appendTextRow(WebInspector.UIString("Details"), this.details); + break; + } + + if (this.scriptName && this.type !== recordTypes.FunctionCall) + contentHelper._appendLinkRow(WebInspector.UIString("Function Call"), this.scriptName, this.scriptLine); + + if (this.usedHeapSize) + contentHelper._appendTextRow(WebInspector.UIString("Used Heap Size"), WebInspector.UIString("%s of %s", Number.bytesToString(this.usedHeapSize), Number.bytesToString(this.totalHeapSize))); + + if (this.callSiteStackTrace && this.callSiteStackTrace.length) + contentHelper._appendStackTrace(WebInspector.UIString("Call Site stack"), this.callSiteStackTrace); + + if (this.stackTrace) + contentHelper._appendStackTrace(WebInspector.UIString("Call Stack"), this.stackTrace); + + return contentHelper._contentTable; + }, + + _refreshDetails: function() + { + this.details = this._getRecordDetails(); + }, + + _getRecordDetails: function() + { + switch (this.type) { + case WebInspector.TimelineAgent.RecordType.GCEvent: + return WebInspector.UIString("%s collected", Number.bytesToString(this.data["usedHeapSizeDelta"])); + case WebInspector.TimelineAgent.RecordType.TimerFire: + return this.scriptName ? this._panel._linkifyLocation(this.scriptName, this.scriptLine, 0) : this.data["timerId"]; + case WebInspector.TimelineAgent.RecordType.FunctionCall: + return this.scriptName ? this._panel._linkifyLocation(this.scriptName, this.scriptLine, 0) : null; + case WebInspector.TimelineAgent.RecordType.FireAnimationFrameEvent: + return this.scriptName ? this._panel._linkifyLocation(this.scriptName, this.scriptLine, 0) : this.data["id"]; + case WebInspector.TimelineAgent.RecordType.EventDispatch: + return this.data ? this.data["type"] : null; + case WebInspector.TimelineAgent.RecordType.Paint: + return this.data["width"] + "\u2009\u00d7\u2009" + this.data["height"]; + case WebInspector.TimelineAgent.RecordType.TimerInstall: + case WebInspector.TimelineAgent.RecordType.TimerRemove: + return this.stackTrace ? this._panel._linkifyCallFrame(this.stackTrace[0]) : this.data["timerId"]; + case WebInspector.TimelineAgent.RecordType.RegisterAnimationFrameCallback: + case WebInspector.TimelineAgent.RecordType.CancelAnimationFrameCallback: + return this.stackTrace ? this._panel._linkifyCallFrame(this.stackTrace[0]) : this.data["id"]; + case WebInspector.TimelineAgent.RecordType.ParseHTML: + case WebInspector.TimelineAgent.RecordType.RecalculateStyles: + return this.stackTrace ? this._panel._linkifyCallFrame(this.stackTrace[0]) : null; + case WebInspector.TimelineAgent.RecordType.EvaluateScript: + return this.url ? this._panel._linkifyLocation(this.url, this.data["lineNumber"], 0) : null; + case WebInspector.TimelineAgent.RecordType.XHRReadyStateChange: + case WebInspector.TimelineAgent.RecordType.XHRLoad: + case WebInspector.TimelineAgent.RecordType.ScheduleResourceRequest: + case WebInspector.TimelineAgent.RecordType.ResourceSendRequest: + case WebInspector.TimelineAgent.RecordType.ResourceReceivedData: + case WebInspector.TimelineAgent.RecordType.ResourceReceiveResponse: + case WebInspector.TimelineAgent.RecordType.ResourceFinish: + return WebInspector.displayNameForURL(this.url); + case WebInspector.TimelineAgent.RecordType.TimeStamp: + return this.data["message"]; + default: + return null; + } + }, + + _calculateAggregatedStats: function(categories) + { + this._aggregatedStats = {}; + for (var category in categories) + this._aggregatedStats[category] = 0; + this._cpuTime = this._selfTime; + + if (this._children) { + for (var index = this._children.length; index; --index) { + var child = this._children[index - 1]; + this._aggregatedStats[child.category.name] += child._selfTime; + for (var category in categories) + this._aggregatedStats[category] += child._aggregatedStats[category]; + } + for (var category in this._aggregatedStats) + this._cpuTime += this._aggregatedStats[category]; + } + } +} + +/** + * @constructor + */ +WebInspector.TimelinePanel.PopupContentHelper = function(title, panel) +{ + this._panel = panel; + this._contentTable = document.createElement("table");; + var titleCell = this._createCell(WebInspector.UIString("%s - Details", title), "timeline-details-title"); + titleCell.colSpan = 2; + var titleRow = document.createElement("tr"); + titleRow.appendChild(titleCell); + this._contentTable.appendChild(titleRow); +} + +WebInspector.TimelinePanel.PopupContentHelper.prototype = { + /** + * @param {string=} styleName + */ + _createCell: function(content, styleName) + { + var text = document.createElement("label"); + text.appendChild(document.createTextNode(content)); + var cell = document.createElement("td"); + cell.className = "timeline-details"; + if (styleName) + cell.className += " " + styleName; + cell.textContent = content; + return cell; + }, + + _appendTextRow: function(title, content) + { + var row = document.createElement("tr"); + row.appendChild(this._createCell(title, "timeline-details-row-title")); + row.appendChild(this._createCell(content, "timeline-details-row-data")); + this._contentTable.appendChild(row); + }, + + /** + * @param {string=} titleStyle + */ + _appendElementRow: function(title, content, titleStyle) + { + var row = document.createElement("tr"); + var titleCell = this._createCell(title, "timeline-details-row-title"); + if (titleStyle) + titleCell.addStyleClass(titleStyle); + row.appendChild(titleCell); + var cell = document.createElement("td"); + cell.className = "timeline-details"; + cell.appendChild(content); + row.appendChild(cell); + this._contentTable.appendChild(row); + }, + + /** + * @param {number=} scriptLine + */ + _appendLinkRow: function(title, scriptName, scriptLine) + { + var link = this._panel._linkifyLocation(scriptName, scriptLine, 0, "timeline-details"); + this._appendElementRow(title, link); + }, + + _appendStackTrace: function(title, stackTrace) + { + this._appendTextRow("", ""); + var framesTable = document.createElement("table"); + for (var i = 0; i < stackTrace.length; ++i) { + var stackFrame = stackTrace[i]; + var row = document.createElement("tr"); + row.className = "timeline-details"; + row.appendChild(this._createCell(stackFrame.functionName ? stackFrame.functionName : WebInspector.UIString("(anonymous function)"), "timeline-function-name")); + row.appendChild(this._createCell(" @ ")); + var linkCell = document.createElement("td"); + var urlElement = this._panel._linkifyCallFrame(stackFrame); + linkCell.appendChild(urlElement); + row.appendChild(linkCell); + framesTable.appendChild(row); + } + this._appendElementRow(title, framesTable, "timeline-stacktrace-title"); + } +} + +/** + * @constructor + */ +WebInspector.TimelineExpandableElement = function(container) +{ + this._element = document.createElement("div"); + this._element.className = "timeline-expandable"; + + var leftBorder = document.createElement("div"); + leftBorder.className = "timeline-expandable-left"; + this._element.appendChild(leftBorder); + + container.appendChild(this._element); +} + +WebInspector.TimelineExpandableElement.prototype = { + _update: function(record, index, barPosition) + { + const rowHeight = WebInspector.TimelinePanel.rowHeight; + if (record._visibleChildrenCount || record._invisibleChildrenCount) { + this._element.style.top = index * rowHeight + "px"; + this._element.style.left = barPosition.left + "px"; + this._element.style.width = Math.max(12, barPosition.width + 25) + "px"; + if (!record.collapsed) { + this._element.style.height = (record._visibleChildrenCount + 1) * rowHeight + "px"; + this._element.addStyleClass("timeline-expandable-expanded"); + this._element.removeStyleClass("timeline-expandable-collapsed"); + } else { + this._element.style.height = rowHeight + "px"; + this._element.addStyleClass("timeline-expandable-collapsed"); + this._element.removeStyleClass("timeline-expandable-expanded"); + } + this._element.removeStyleClass("hidden"); + } else + this._element.addStyleClass("hidden"); + }, + + _dispose: function() + { + this._element.parentElement.removeChild(this._element); + } +} + +/** + * @constructor + */ +WebInspector.TimelineModel = function(timelinePanel) +{ + this._panel = timelinePanel; + this._records = []; +} + +WebInspector.TimelineModel.prototype = { + _addRecord: function(record) + { + this._records.push(record); + }, + + _loadNextChunk: function(data, index) + { + for (var i = 0; i < 20 && index < data.length; ++i, ++index) + this._panel._addRecordToTimeline(data[index]); + + if (index !== data.length) + setTimeout(this._loadNextChunk.bind(this, data, index), 0); + }, + + _loadFromFile: function(file) + { + function onLoad(e) + { + var data = JSON.parse(e.target.result); + var version = data[0]; + this._loadNextChunk(data, 1); + } + + function onError(e) + { + switch(e.target.error.code) { + case e.target.error.NOT_FOUND_ERR: + WebInspector.log(WebInspector.UIString('Timeline.loadFromFile: File "%s" not found.', file.name)); + break; + case e.target.error.NOT_READABLE_ERR: + WebInspector.log(WebInspector.UIString('Timeline.loadFromFile: File "%s" is not readable', file.name)); + break; + case e.target.error.ABORT_ERR: + break; + default: + WebInspector.log(WebInspector.UIString('Timeline.loadFromFile: An error occurred while reading the file "%s"', file.name)); + } + } + + var reader = new FileReader(); + reader.onload = onLoad.bind(this); + reader.onerror = onError; + reader.readAsText(file); + }, + + _saveToFile: function() + { + var records = ['[' + JSON.stringify(new String(window.navigator.appVersion))]; + for (var i = 0; i < this._records.length; ++i) + records.push(JSON.stringify(this._records[i])); + + records[records.length - 1] = records[records.length - 1] + "]"; + + var now = new Date(); + var suggestedFileName = "TimelineRawData-" + now.toISO8601Compact() + ".json"; + InspectorFrontendHost.saveAs(suggestedFileName, records.join(",\n")); + }, + + _reset: function() + { + this._records = []; + } +} +/* TimelineOverviewPane.js */ + +/* + * Copyright (C) 2009 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.Object} + */ +WebInspector.TimelineOverviewPane = function(categories) +{ + this._categories = categories; + + this.statusBarFilters = document.createElement("div"); + this.statusBarFilters.className = "status-bar-items"; + for (var categoryName in this._categories) { + var category = this._categories[categoryName]; + this.statusBarFilters.appendChild(this._createTimelineCategoryStatusBarCheckbox(category, this._onCheckboxClicked.bind(this, category))); + } + + this._overviewGrid = new WebInspector.TimelineGrid(); + this._overviewGrid.element.id = "timeline-overview-grid"; + this._overviewGrid.itemsGraphsElement.id = "timeline-overview-timelines"; + this._overviewGrid.element.addEventListener("mousedown", this._dragWindow.bind(this), true); + this._overviewGrid.element.addEventListener("mousewheel", this.scrollWindow.bind(this), true); + this._overviewGrid.element.addEventListener("dblclick", this._resizeWindowMaximum.bind(this), true); + + this._heapGraph = new WebInspector.HeapGraph(); + this._heapGraph.element.id = "timeline-overview-memory"; + this._overviewGrid.element.insertBefore(this._heapGraph.element, this._overviewGrid.itemsGraphsElement); + + this.element = this._overviewGrid.element; + + this._categoryGraphs = {}; + var i = 0; + for (var category in this._categories) { + var categoryGraph = new WebInspector.TimelineCategoryGraph(this._categories[category], i++ % 2); + this._categoryGraphs[category] = categoryGraph; + this._overviewGrid.itemsGraphsElement.appendChild(categoryGraph.graphElement); + } + this._overviewGrid.setScrollAndDividerTop(0, 0); + + this._overviewWindowElement = document.createElement("div"); + this._overviewWindowElement.id = "timeline-overview-window"; + this._overviewGrid.element.appendChild(this._overviewWindowElement); + + this._overviewWindowBordersElement = document.createElement("div"); + this._overviewWindowBordersElement.className = "timeline-overview-window-rulers"; + this._overviewGrid.element.appendChild(this._overviewWindowBordersElement); + + var overviewDividersBackground = document.createElement("div"); + overviewDividersBackground.className = "timeline-overview-dividers-background"; + this._overviewGrid.element.appendChild(overviewDividersBackground); + + this._leftResizeElement = document.createElement("div"); + this._leftResizeElement.className = "timeline-window-resizer"; + this._leftResizeElement.style.left = 0; + this._overviewGrid.element.appendChild(this._leftResizeElement); + + this._rightResizeElement = document.createElement("div"); + this._rightResizeElement.className = "timeline-window-resizer timeline-window-resizer-right"; + this._rightResizeElement.style.right = 0; + this._overviewGrid.element.appendChild(this._rightResizeElement); + + this._overviewCalculator = new WebInspector.TimelineOverviewCalculator(); + + this.windowLeft = 0.0; + this.windowRight = 1.0; +} + +WebInspector.TimelineOverviewPane.MinSelectableSize = 12; + +WebInspector.TimelineOverviewPane.WindowScrollSpeedFactor = .3; + +WebInspector.TimelineOverviewPane.ResizerOffset = 3.5; // half pixel because offset values are not rounded but ceiled + +WebInspector.TimelineOverviewPane.prototype = { + showTimelines: function(event) { + this._heapGraph.hide(); + this._overviewGrid.itemsGraphsElement.removeStyleClass("hidden"); + }, + + showMemoryGraph: function(records) { + this._heapGraph.show(); + this._heapGraph.update(records); + this._overviewGrid.itemsGraphsElement.addStyleClass("hidden"); + }, + + _onCheckboxClicked: function (category, event) { + if (event.target.checked) + category.hidden = false; + else + category.hidden = true; + this._categoryGraphs[category.name].dimmed = !event.target.checked; + this.dispatchEventToListeners("filter changed"); + }, + + _forAllRecords: function(recordsArray, callback) + { + if (!recordsArray) + return; + for (var i = 0; i < recordsArray.length; ++i) { + callback(recordsArray[i]); + this._forAllRecords(recordsArray[i].children, callback); + } + }, + + update: function(records, showShortEvents) + { + this._showShortEvents = showShortEvents; + // Clear summary bars. + var timelines = {}; + for (var category in this._categories) { + timelines[category] = []; + this._categoryGraphs[category].clearChunks(); + } + + // Create sparse arrays with 101 cells each to fill with chunks for a given category. + this._overviewCalculator.reset(); + this._forAllRecords(records, this._overviewCalculator.updateBoundaries.bind(this._overviewCalculator)); + + function markPercentagesForRecord(record) + { + if (!(this._showShortEvents || record.isLong())) + return; + var percentages = this._overviewCalculator.computeBarGraphPercentages(record); + + var end = Math.round(percentages.end); + var categoryName = record.category.name; + for (var j = Math.round(percentages.start); j <= end; ++j) + timelines[categoryName][j] = true; + } + this._forAllRecords(records, markPercentagesForRecord.bind(this)); + + // Convert sparse arrays to continuous segments, render graphs for each. + for (var category in this._categories) { + var timeline = timelines[category]; + window.timelineSaved = timeline; + var chunkStart = -1; + for (var j = 0; j < 101; ++j) { + if (timeline[j]) { + if (chunkStart === -1) + chunkStart = j; + } else { + if (chunkStart !== -1) { + this._categoryGraphs[category].addChunk(chunkStart, j); + chunkStart = -1; + } + } + } + if (chunkStart !== -1) { + this._categoryGraphs[category].addChunk(chunkStart, 100); + chunkStart = -1; + } + } + + this._heapGraph.setSize(this._overviewGrid.element.offsetWidth, 60); + if (this._heapGraph.visible) + this._heapGraph.update(records); + + this._overviewGrid.updateDividers(true, this._overviewCalculator); + }, + + updateEventDividers: function(records, dividerConstructor) + { + this._overviewGrid.removeEventDividers(); + var dividers = []; + for (var i = 0; i < records.length; ++i) { + var record = records[i]; + var positions = this._overviewCalculator.computeBarGraphPercentages(record); + var dividerPosition = Math.round(positions.start * 10); + if (dividers[dividerPosition]) + continue; + var divider = dividerConstructor(record); + divider.style.left = positions.start + "%"; + dividers[dividerPosition] = divider; + } + this._overviewGrid.addEventDividers(dividers); + }, + + sidebarResized: function(width) + { + this._overviewGrid.element.style.left = width + "px"; + // Min width = * 31 + this.statusBarFilters.style.left = Math.max(7 * 31, width) + "px"; + }, + + reset: function() + { + this.windowLeft = 0.0; + this.windowRight = 1.0; + this._overviewWindowElement.style.left = "0%"; + this._overviewWindowElement.style.width = "100%"; + this._overviewWindowBordersElement.style.left = "0%"; + this._overviewWindowBordersElement.style.right = "0%"; + this._leftResizeElement.style.left = "0%"; + this._rightResizeElement.style.left = "100%"; + this._overviewCalculator.reset(); + this._overviewGrid.updateDividers(true, this._overviewCalculator); + }, + + _resizeWindow: function(resizeElement, event) + { + WebInspector.elementDragStart(resizeElement, this._windowResizeDragging.bind(this, resizeElement), this._endWindowDragging.bind(this), event, "ew-resize"); + }, + + _windowResizeDragging: function(resizeElement, event) + { + if (resizeElement === this._leftResizeElement) + this._resizeWindowLeft(event.pageX - this._overviewGrid.element.offsetLeft); + else + this._resizeWindowRight(event.pageX - this._overviewGrid.element.offsetLeft); + event.preventDefault(); + }, + + _dragWindow: function(event) + { + var node = event.target; + while (node) { + if (node === this._overviewGrid._dividersLabelBarElement) { + WebInspector.elementDragStart(this._overviewWindowElement, this._windowDragging.bind(this, event.pageX, + this._leftResizeElement.offsetLeft + WebInspector.TimelineOverviewPane.ResizerOffset, this._rightResizeElement.offsetLeft + WebInspector.TimelineOverviewPane.ResizerOffset), this._endWindowDragging.bind(this), event, "ew-resize"); + break; + } else if (node === this._overviewGrid.element) { + var position = event.pageX - this._overviewGrid.element.offsetLeft; + this._overviewWindowSelector = new WebInspector.TimelinePanel.WindowSelector(this._overviewGrid.element, position, event); + WebInspector.elementDragStart(null, this._windowSelectorDragging.bind(this), this._endWindowSelectorDragging.bind(this), event, "ew-resize"); + break; + } else if (node === this._leftResizeElement || node === this._rightResizeElement) { + this._resizeWindow(node, event); + break; + } + node = node.parentNode; + } + }, + + _windowSelectorDragging: function(event) + { + this._overviewWindowSelector._updatePosition(event.pageX - this._overviewGrid.element.offsetLeft); + event.preventDefault(); + }, + + _endWindowSelectorDragging: function(event) + { + WebInspector.elementDragEnd(event); + var window = this._overviewWindowSelector._close(event.pageX - this._overviewGrid.element.offsetLeft); + delete this._overviewWindowSelector; + if (window.end - window.start < WebInspector.TimelineOverviewPane.MinSelectableSize) + if (this._overviewGrid.itemsGraphsElement.offsetWidth - window.end > WebInspector.TimelineOverviewPane.MinSelectableSize) + window.end = window.start + WebInspector.TimelineOverviewPane.MinSelectableSize; + else + window.start = window.end - WebInspector.TimelineOverviewPane.MinSelectableSize; + this._setWindowPosition(window.start, window.end); + }, + + _windowDragging: function(startX, windowLeft, windowRight, event) + { + var delta = event.pageX - startX; + var start = windowLeft + delta; + var end = windowRight + delta; + var windowSize = windowRight - windowLeft; + + if (start < 0) { + start = 0; + end = windowSize; + } + + if (end > this._overviewGrid.element.clientWidth) { + end = this._overviewGrid.element.clientWidth; + start = end - windowSize; + } + this._setWindowPosition(start, end); + + event.preventDefault(); + }, + + _resizeWindowLeft: function(start) + { + // Glue to edge. + if (start < 10) + start = 0; + else if (start > this._rightResizeElement.offsetLeft - 4) + start = this._rightResizeElement.offsetLeft - 4; + this._setWindowPosition(start, null); + }, + + _resizeWindowRight: function(end) + { + // Glue to edge. + if (end > this._overviewGrid.element.clientWidth - 10) + end = this._overviewGrid.element.clientWidth; + else if (end < this._leftResizeElement.offsetLeft + WebInspector.TimelineOverviewPane.MinSelectableSize) + end = this._leftResizeElement.offsetLeft + WebInspector.TimelineOverviewPane.MinSelectableSize; + this._setWindowPosition(null, end); + }, + + _resizeWindowMaximum: function() + { + this._setWindowPosition(0, this._overviewGrid.element.clientWidth); + }, + + _setWindowPosition: function(start, end) + { + const rulerAdjustment = 1 / this._overviewGrid.element.clientWidth; + if (typeof start === "number") { + this.windowLeft = start / this._overviewGrid.element.clientWidth; + this._leftResizeElement.style.left = this.windowLeft * 100 + "%"; + this._overviewWindowElement.style.left = this.windowLeft * 100 + "%"; + this._overviewWindowBordersElement.style.left = (this.windowLeft - rulerAdjustment) * 100 + "%"; + } + if (typeof end === "number") { + this.windowRight = end / this._overviewGrid.element.clientWidth; + this._rightResizeElement.style.left = this.windowRight * 100 + "%"; + } + this._overviewWindowElement.style.width = (this.windowRight - this.windowLeft) * 100 + "%"; + this._overviewWindowBordersElement.style.right = (1 - this.windowRight + 2 * rulerAdjustment) * 100 + "%"; + this.dispatchEventToListeners("window changed"); + }, + + _endWindowDragging: function(event) + { + WebInspector.elementDragEnd(event); + }, + + scrollWindow: function(event) + { + if (typeof event.wheelDeltaX === "number" && event.wheelDeltaX !== 0) + this._windowDragging(event.pageX + Math.round(event.wheelDeltaX * WebInspector.TimelineOverviewPane.WindowScrollSpeedFactor), this._leftResizeElement.offsetLeft + WebInspector.TimelineOverviewPane.ResizerOffset, this._rightResizeElement.offsetLeft + WebInspector.TimelineOverviewPane.ResizerOffset, event); + }, + + _createTimelineCategoryStatusBarCheckbox: function(category, onCheckboxClicked) + { + var labelContainer = document.createElement("div"); + labelContainer.addStyleClass("timeline-category-statusbar-item"); + labelContainer.addStyleClass("timeline-category-" + category.name); + labelContainer.addStyleClass("status-bar-item"); + + var label = document.createElement("label"); + var checkElement = document.createElement("input"); + checkElement.type = "checkbox"; + checkElement.className = "timeline-category-checkbox"; + checkElement.checked = true; + checkElement.addEventListener("click", onCheckboxClicked, false); + label.appendChild(checkElement); + + var typeElement = document.createElement("span"); + typeElement.className = "type"; + typeElement.textContent = category.title; + label.appendChild(typeElement); + + labelContainer.appendChild(label); + return labelContainer; + } + +} + +WebInspector.TimelineOverviewPane.prototype.__proto__ = WebInspector.Object.prototype; + +/** + * @constructor + */ +WebInspector.TimelineOverviewCalculator = function() +{ +} + +WebInspector.TimelineOverviewCalculator.prototype = { + computeBarGraphPercentages: function(record) + { + var start = (record.startTime - this.minimumBoundary) / this.boundarySpan * 100; + var end = (record.endTime - this.minimumBoundary) / this.boundarySpan * 100; + return {start: start, end: end}; + }, + + reset: function() + { + delete this.minimumBoundary; + delete this.maximumBoundary; + }, + + updateBoundaries: function(record) + { + if (typeof this.minimumBoundary === "undefined" || record.startTime < this.minimumBoundary) { + this.minimumBoundary = record.startTime; + return true; + } + if (typeof this.maximumBoundary === "undefined" || record.endTime > this.maximumBoundary) { + this.maximumBoundary = record.endTime; + return true; + } + return false; + }, + + get boundarySpan() + { + return this.maximumBoundary - this.minimumBoundary; + }, + + formatValue: function(value) + { + return Number.secondsToString(value); + } +} + +/** + * @constructor + */ +WebInspector.TimelineCategoryGraph = function(category, isEven) +{ + this._category = category; + + this._graphElement = document.createElement("div"); + this._graphElement.className = "timeline-graph-side timeline-overview-graph-side" + (isEven ? " even" : ""); + + this._barAreaElement = document.createElement("div"); + this._barAreaElement.className = "timeline-graph-bar-area timeline-category-" + category.name; + this._graphElement.appendChild(this._barAreaElement); +} + +WebInspector.TimelineCategoryGraph.prototype = { + get graphElement() + { + return this._graphElement; + }, + + addChunk: function(start, end) + { + var chunk = document.createElement("div"); + chunk.className = "timeline-graph-bar"; + this._barAreaElement.appendChild(chunk); + chunk.style.setProperty("left", start + "%"); + chunk.style.setProperty("width", (end - start) + "%"); + }, + + clearChunks: function() + { + this._barAreaElement.removeChildren(); + }, + + set dimmed(dimmed) + { + if (dimmed) + this._barAreaElement.removeStyleClass("timeline-category-" + this._category.name); + else + this._barAreaElement.addStyleClass("timeline-category-" + this._category.name); + } +} + +/** + * @constructor + */ +WebInspector.TimelinePanel.WindowSelector = function(parent, position, event) +{ + this._startPosition = position; + this._width = parent.offsetWidth; + this._windowSelector = document.createElement("div"); + this._windowSelector.className = "timeline-window-selector"; + this._windowSelector.style.left = this._startPosition + "px"; + this._windowSelector.style.right = this._width - this._startPosition + + "px"; + parent.appendChild(this._windowSelector); +} + +WebInspector.TimelinePanel.WindowSelector.prototype = { + _createSelectorElement: function(parent, left, width, height) + { + var selectorElement = document.createElement("div"); + selectorElement.className = "timeline-window-selector"; + selectorElement.style.left = left + "px"; + selectorElement.style.width = width + "px"; + selectorElement.style.top = "0px"; + selectorElement.style.height = height + "px"; + parent.appendChild(selectorElement); + return selectorElement; + }, + + _close: function(position) + { + position = Math.max(0, Math.min(position, this._width)); + this._windowSelector.parentNode.removeChild(this._windowSelector); + return this._startPosition < position ? {start: this._startPosition, end: position} : {start: position, end: this._startPosition}; + }, + + _updatePosition: function(position) + { + position = Math.max(0, Math.min(position, this._width)); + if (position < this._startPosition) { + this._windowSelector.style.left = position + "px"; + this._windowSelector.style.right = this._width - this._startPosition + "px"; + } else { + this._windowSelector.style.left = this._startPosition + "px"; + this._windowSelector.style.right = this._width - position + "px"; + } + } +} + +/** + * @constructor + */ +WebInspector.HeapGraph = function() { + this._canvas = document.createElement("canvas"); + + this._maxHeapSizeLabel = document.createElement("div"); + this._maxHeapSizeLabel.addStyleClass("memory-graph-label"); + + this._element = document.createElement("div"); + this._element.addStyleClass("hidden"); + this._element.appendChild(this._canvas); + this._element.appendChild(this._maxHeapSizeLabel); +} + +WebInspector.HeapGraph.prototype = { + get element() { + // return this._canvas; + return this._element; + }, + + get visible() { + return !this.element.hasStyleClass("hidden"); + }, + + show: function() { + this.element.removeStyleClass("hidden"); + }, + + hide: function() { + this.element.addStyleClass("hidden"); + }, + + setSize: function(w, h) { + this._canvas.width = w; + this._canvas.height = h - 5; + }, + + update: function(records) + { + if (!records.length) + return; + + var maxTotalHeapSize = 0; + var minTime; + var maxTime; + this._forAllRecords(records, function(r) { + if (r.totalHeapSize && r.totalHeapSize > maxTotalHeapSize) + maxTotalHeapSize = r.totalHeapSize; + + if (typeof minTime === "undefined" || r.startTime < minTime) + minTime = r.startTime; + if (typeof maxTime === "undefined" || r.endTime > maxTime) + maxTime = r.endTime; + }); + + var width = this._canvas.width; + var height = this._canvas.height; + var xFactor = width / (maxTime - minTime); + var yFactor = height / maxTotalHeapSize; + + var histogram = new Array(width); + this._forAllRecords(records, function(r) { + if (!r.usedHeapSize) + return; + var x = Math.round((r.endTime - minTime) * xFactor); + var y = Math.round(r.usedHeapSize * yFactor); + histogram[x] = Math.max(histogram[x] || 0, y); + }); + + var ctx = this._canvas.getContext("2d"); + this._clear(ctx); + + // +1 so that the border always fit into the canvas area. + height = height + 1; + + ctx.beginPath(); + var initialY = 0; + for (var k = 0; k < histogram.length; k++) { + if (histogram[k]) { + initialY = histogram[k]; + break; + } + } + ctx.moveTo(0, height - initialY); + + for (var x = 0; x < histogram.length; x++) { + if (!histogram[x]) + continue; + ctx.lineTo(x, height - histogram[x]); + } + + ctx.lineWidth = 0.5; + ctx.strokeStyle = "rgba(20,0,0,0.8)"; + ctx.stroke(); + + ctx.fillStyle = "rgba(214,225,254, 0.8);"; + ctx.lineTo(width, 60); + ctx.lineTo(0, 60); + ctx.lineTo(0, height - initialY); + ctx.fill(); + ctx.closePath(); + + this._maxHeapSizeLabel.textContent = Number.bytesToString(maxTotalHeapSize); + }, + + _clear: function(ctx) { + ctx.fillStyle = "rgba(255,255,255,0.8)"; + ctx.fillRect(0, 0, this._canvas.width, this._canvas.height); + }, + + _forAllRecords: WebInspector.TimelineOverviewPane.prototype._forAllRecords +} +/* TestController.js */ + +/* + * Copyright (C) 2009 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + */ +WebInspector.TestController = function() +{ +} + +WebInspector.TestController.prototype = { + notifyDone: function(callId, result) + { + var message = typeof result === "undefined" ? "\"\"" : JSON.stringify(result); + RuntimeAgent.evaluate("didEvaluateForTestInFrontend(" + callId + ", " + message + ")", "test"); + } +} + +WebInspector.evaluateForTestInFrontend = function(callId, script) +{ + function invokeMethod() + { + try { + var result = window.eval(script); + WebInspector.TestController.prototype.notifyDone(callId, result); + } catch (e) { + WebInspector.TestController.prototype.notifyDone(callId, e.toString()); + } + } + InspectorBackend.runAfterPendingDispatches(invokeMethod); +} +/* HelpScreen.js */ + +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + */ +WebInspector.HelpScreen = function(title) +{ + this._element = document.createElement("div"); + this._element.className = "help-window-outer"; + this._element.addEventListener("keydown", this._onKeyDown.bind(this), false); + this._element.tabIndex = 0; + this._element.addEventListener("focus", this._onBlur.bind(this), false); + + var mainWindow = this._element.createChild("div", "help-window-main"); + var captionWindow = mainWindow.createChild("div", "help-window-caption"); + var closeButton = captionWindow.createChild("button", "help-close-button"); + this.contentElement = mainWindow.createChild("div", "help-content"); + captionWindow.createChild("h1", "help-window-title").textContent = title; + + closeButton.textContent = "\u2716"; // Code stands for HEAVY MULTIPLICATION X. + closeButton.addEventListener("click", this.hide.bind(this), false); + this._closeKeys = [ + WebInspector.KeyboardShortcut.Keys.Enter.code, + WebInspector.KeyboardShortcut.Keys.Esc.code, + WebInspector.KeyboardShortcut.Keys.Space.code, + ]; +} + +WebInspector.HelpScreen.visibleScreen_ = null; + +WebInspector.HelpScreen.prototype = { + show: function(onHide) + { + if (this._isShown) + return; + + if (WebInspector.HelpScreen.visibleScreen_) + WebInspector.HelpScreen.visibleScreen_.hide(); + WebInspector.HelpScreen.visibleScreen_ = this; + + document.body.appendChild(this._element); + this._isShown = true; + this._onHide = onHide; + this._previousFocusElement = WebInspector.currentFocusElement(); + WebInspector.setCurrentFocusElement(this._element); + }, + + hide: function() + { + if (!this._isShown) + return; + + this._isShown = false; + document.body.removeChild(this._element); + WebInspector.setCurrentFocusElement(this._previousFocusElement); + WebInspector.HelpScreen.visibleScreen_ = null; + if (this._onHide) { + this._onHide(); + delete this._onHide; + } + }, + + _onKeyDown: function(event) + { + if (this._isShown && this._closeKeys.indexOf(event.keyCode) >= 0) { + this.hide(); + event.stopPropagation(); + } + }, + + _onBlur: function(event) + { + // Pretend we're modal, grab focus back if we're still shown. + if (this._isShown && !this._element.isSelfOrAncestor(event.target)) + WebInspector.setCurrentFocusElement(this._element); + } +} +/* Dialog.js */ + +/* + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @param {Element} relativeToElement + * @param {WebInspector.DialogDelegate} delegate + */ +WebInspector.Dialog = function(relativeToElement, delegate) +{ + this._delegate = delegate; + this._relativeToElement = relativeToElement; + + // Install glass pane capturing events. + this._glassPaneElement = document.body.createChild("div"); + this._glassPaneElement.className = "dialog-glass-pane"; + this._glassPaneElement.tabIndex = 0; + this._glassPaneElement.addEventListener("focus", this._onGlassPaneFocus.bind(this), false); + + this._element = this._glassPaneElement.createChild("div"); + this._element.className = "dialog"; + this._element.tabIndex = 0; + this._element.addEventListener("focus", this._onFocus.bind(this), false); + this._element.addEventListener("keydown", this._onKeyDown.bind(this), false); + this._closeKeys = [ + WebInspector.KeyboardShortcut.Keys.Enter.code, + WebInspector.KeyboardShortcut.Keys.Esc.code, + ]; + + delegate.element.addStyleClass("dialog-contents"); + this._element.appendChild(delegate.element); + + this._position(); + this._windowResizeHandler = this._position.bind(this); + window.addEventListener("resize", this._windowResizeHandler, true); + + this._previousFocusElement = WebInspector.currentFocusElement(); + this._delegate.focus(); +} + +/** + * @return {WebInspector.Dialog} + */ +WebInspector.Dialog.currentInstance = function() +{ + return WebInspector.Dialog._instance; +} + +/** + * @param {Element} relativeToElement + * @param {WebInspector.DialogDelegate} delegate + */ +WebInspector.Dialog.show = function(relativeToElement, delegate) +{ + if (WebInspector.Dialog._instance) + return; + WebInspector.Dialog._instance = new WebInspector.Dialog(relativeToElement, delegate); +} + +WebInspector.Dialog.hide = function() +{ + if (!WebInspector.Dialog._instance) + return; + WebInspector.Dialog._instance._hide(); +} + +WebInspector.Dialog.prototype = { + _hide: function() + { + if (this._isHiding) + return; + this._isHiding = true; + + this._delegate.willHide(); + + if (this._element.isSelfOrAncestor(document.activeElement)) + WebInspector.setCurrentFocusElement(this._previousFocusElement); + delete WebInspector.Dialog._instance; + document.body.removeChild(this._glassPaneElement); + window.removeEventListener("resize", this._windowResizeHandler, true); + }, + + _onGlassPaneFocus: function(event) + { + this._hide(); + }, + + _onFocus: function(event) + { + this._delegate.focus(); + }, + + _position: function() + { + this._delegate.position(this._element, this._relativeToElement); + }, + + _onKeyDown: function(event) + { + if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Tab.code) { + event.preventDefault(); + return; + } + + if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Enter.code) + this._delegate.onEnter(); + + if (this._closeKeys.indexOf(event.keyCode) >= 0) { + this._hide(); + event.preventDefault(); + event.stopPropagation(); + } + } +}; + +/** + * @constructor + */ +WebInspector.DialogDelegate = function() +{ +} + +WebInspector.DialogDelegate.prototype = { + /** + * @param {Element} element + * @param {Element} relativeToElement + */ + position: function(element, relativeToElement) + { + var offset = relativeToElement.offsetRelativeToWindow(window); + + var positionX = offset.x + (relativeToElement.offsetWidth - element.offsetWidth) / 2; + positionX = Number.constrain(positionX, 0, window.innerWidth - element.offsetWidth); + + var positionY = offset.y + (relativeToElement.offsetHeight - element.offsetHeight) / 2; + positionY = Number.constrain(positionY, 0, window.innerHeight - element.offsetHeight); + + element.style.left = positionX + "px"; + element.style.top = positionY + "px"; + }, + + focus: function() { }, + + onEnter: function() { }, + + willHide: function() { } +}; +/* GoToLineDialog.js */ + +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.DialogDelegate} + */ +WebInspector.GoToLineDialog = function(view) +{ + WebInspector.DialogDelegate.call(this); + + this.element = document.createElement("div"); + this.element.className = "go-to-line-dialog"; + + this.element.createChild("label").textContent = WebInspector.UIString("Go to line: "); + + this._input = this.element.createChild("input"); + this._input.setAttribute("type", "text"); + this._input.setAttribute("size", 6); + + this._goButton = this.element.createChild("button"); + this._goButton.textContent = WebInspector.UIString("Go"); + this._goButton.addEventListener("click", this._onGoClick.bind(this), false); + + this._view = view; +} + +/** + * @param {WebInspector.Panel} panel + */ +WebInspector.GoToLineDialog.install = function(panel, viewGetter) +{ + function showGoToLineDialog() + { + var view = viewGetter(); + if (view) + WebInspector.GoToLineDialog._show(view); + } + + var goToLineShortcut = WebInspector.GoToLineDialog.createShortcut(); + panel.registerShortcut(goToLineShortcut.key, showGoToLineDialog); +} + +WebInspector.GoToLineDialog._show = function(sourceView) +{ + if (!sourceView || !sourceView.canHighlightLine()) + return; + WebInspector.Dialog.show(sourceView.element, new WebInspector.GoToLineDialog(sourceView)); +} + +WebInspector.GoToLineDialog.createShortcut = function() +{ + var isMac = WebInspector.isMac(); + var shortcut; + if (isMac) + return WebInspector.KeyboardShortcut.makeDescriptor("l", WebInspector.KeyboardShortcut.Modifiers.Meta); + return WebInspector.KeyboardShortcut.makeDescriptor("g", WebInspector.KeyboardShortcut.Modifiers.Ctrl); +} + +WebInspector.GoToLineDialog.prototype = { + focus: function() + { + WebInspector.setCurrentFocusElement(this._input); + this._input.select(); + }, + + _onGoClick: function() + { + this._applyLineNumber(); + WebInspector.Dialog.hide(); + }, + + _applyLineNumber: function() + { + var value = this._input.value; + var lineNumber = parseInt(value, 10) - 1; + if (!isNaN(lineNumber) && lineNumber >= 0) + this._view.highlightLine(lineNumber); + }, + + onEnter: function() + { + this._applyLineNumber(); + } +} + +WebInspector.GoToLineDialog.prototype.__proto__ = WebInspector.DialogDelegate.prototype; +/* FilteredItemSelectionDialog.js */ + +/* + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.DialogDelegate} + * @param {WebInspector.SelectionDialogContentProvider} delegate + */ +WebInspector.FilteredItemSelectionDialog = function(delegate) +{ + WebInspector.DialogDelegate.call(this); + + var xhr = new XMLHttpRequest(); + xhr.open("GET", "filteredItemSelectionDialog.css", false); + xhr.send(null); + + this.element = document.createElement("div"); + this.element.className = "js-outline-dialog"; + this.element.addEventListener("keydown", this._onKeyDown.bind(this), false); + this.element.addEventListener("mousemove", this._onMouseMove.bind(this), false); + this.element.addEventListener("click", this._onClick.bind(this), false); + var styleElement = this.element.createChild("style"); + styleElement.type = "text/css"; + styleElement.textContent = xhr.responseText; + + this._previousInputLength = 0; + this._itemElements = []; + this._elementIndexes = new Map(); + this._elementHighlightChanges = new Map(); + + this._promptElement = this.element.createChild("input", "monospace"); + this._promptElement.type = "text"; + this._promptElement.setAttribute("spellcheck", "false"); + + this._progressElement = this.element.createChild("div", "progress"); + + this._itemElementsContainer = document.createElement("div"); + this._itemElementsContainer.className = "container monospace"; + this._itemElementsContainer.addEventListener("scroll", this._onScroll.bind(this), false); + this.element.appendChild(this._itemElementsContainer); + + this._delegate = delegate; + + this._delegate.requestItems(this._itemsLoaded.bind(this)); +} + +WebInspector.FilteredItemSelectionDialog.prototype = { + /** + * @param {Element} element + * @param {Element} relativeToElement + */ + position: function(element, relativeToElement) + { + const minWidth = 500; + const minHeight = 204; + var width = Math.max(relativeToElement.offsetWidth * 2 / 3, minWidth); + var height = Math.max(relativeToElement.offsetHeight * 2 / 3, minHeight); + + this.element.style.width = width + "px"; + this.element.style.height = height + "px"; + + const shadowPadding = 10; + element.positionAt( + relativeToElement.totalOffsetLeft() + Math.max(relativeToElement.offsetWidth - width - shadowPadding, shadowPadding), + relativeToElement.totalOffsetTop() + Math.max((relativeToElement.offsetHeight - height) / 2 + shadowPadding, shadowPadding)); + }, + + focus: function() + { + WebInspector.setCurrentFocusElement(this._promptElement); + }, + + willHide: function() + { + if (this._isHiding) + return; + this._isHiding = true; + if (this._filterTimer) + clearTimeout(this._filterTimer); + }, + + onEnter: function() + { + if (!this._selectedElement) + return; + this._delegate.selectItem(this._elementIndexes.get(this._selectedElement)); + }, + + /** + * @param {number} index + * @param {number} chunkLength + * @param {number} chunkIndex + * @param {number} chunkCount + */ + _itemsLoaded: function(index, chunkLength, chunkIndex, chunkCount) + { + var fragment = document.createDocumentFragment(); + var candidateItem = this._selectedElement; + for (var i = index; i < index + chunkLength; ++i) { + var itemElement = this._createItemElement(i, this._delegate.itemTitleAt(i)); + if (this._checkItemAt(i, this._promptElement.value)) { + if (!candidateItem) + candidateItem = itemElement; + } else + this._hideItemElement(itemElement); + fragment.appendChild(itemElement); + } + this._itemElementsContainer.appendChild(fragment); + this._updateSelection(candidateItem); + + if (chunkIndex === chunkCount) + this._progressElement.style.backgroundImage = ""; + else { + const color = "rgb(66, 129, 235)"; + const percent = ((chunkIndex / chunkCount) * 100) + "%"; + this._progressElement.style.backgroundImage = "-webkit-linear-gradient(left, " + color + ", " + color + " " + percent + ", transparent " + percent + ")"; + } + }, + + /** + * @param {number} index + * @param {string} title + */ + _createItemElement: function(index, title) + { + if (this._itemElements[index]) + return this._itemElements[index]; + + var itemElement = document.createElement("div"); + itemElement.className = "item"; + itemElement.textContent = title; + this._elementIndexes.put(itemElement, index); + this._itemElements.push(itemElement); + + return itemElement; + }, + + /** + * @param {Element} itemElement + */ + _hideItemElement: function(itemElement) + { + itemElement.style.display = "none"; + }, + + /** + * @param {Element} itemElement + */ + _itemElementVisible: function(itemElement) + { + return itemElement.style.display !== "none"; + }, + + /** + * @param {Element} itemElement + */ + _showItemElement: function(itemElement) + { + itemElement.style.display = ""; + }, + + /** + * @param {number} index + */ + _checkItemAt: function(index, query) + { + if (!query) + return true; + var regExp = this._createSearchRegExp(query); + var key = this._delegate.itemKeyAt(index); + return regExp.test(key); + }, + + /** + * @param {string=} query + * @param {boolean=} isGlobal + */ + _createSearchRegExp: function(query, isGlobal) + { + var trimmedQuery = query.trim(); + var regExpString = trimmedQuery.escapeForRegExp().replace(/\\\*/g, ".*").replace(/(?!^)([A-Z])/g, "[^A-Z]*$1"); + var isSuffix = (query.charAt(query.length - 1) === " "); + if (isSuffix) + regExpString += "$"; + return new RegExp(regExpString, (trimmedQuery === trimmedQuery.toLowerCase() ? "i" : "") + (isGlobal ? "g" : "")); + }, + + _filterItems: function() + { + delete this._filterTimer; + + var query = this._promptElement.value; + var charsAdded = this._previousInputLength < query.length; + this._previousInputLength = query.length; + query = query.trim(); + + var firstElement; + for (var i = 0; i < this._itemElements.length; ++i) { + var itemElement = this._itemElements[i]; + + if (this._itemElementVisible(itemElement)) { + if (!this._checkItemAt(i, query)) + this._hideItemElement(itemElement); + } else if (!charsAdded && this._checkItemAt(i, query)) + this._showItemElement(itemElement); + + if (!firstElement && this._itemElementVisible(itemElement)) + firstElement = itemElement; + } + + this._updateSelection(firstElement); + if (query) { + this._highlightItems(query); + this._query = query; + } else { + this._clearHighlight(); + delete this._query; + } + }, + + _onKeyDown: function(event) + { + function nextItem(itemElement, isPageScroll, forward) + { + var scrollItemsLeft = isPageScroll && this._rowsPerViewport ? this._rowsPerViewport : 1; + var candidate = itemElement; + var lastVisibleCandidate = candidate; + do { + candidate = forward ? candidate.nextSibling : candidate.previousSibling; + if (!candidate) { + if (isPageScroll) + return lastVisibleCandidate; + else + candidate = forward ? this._itemElementsContainer.firstChild : this._itemElementsContainer.lastChild; + } + if (!this._itemElementVisible(candidate)) + continue; + lastVisibleCandidate = candidate; + --scrollItemsLeft; + } while (scrollItemsLeft && candidate !== this._selectedElement); + + return candidate; + } + + var isPageScroll = false; + + if (this._selectedElement) { + var candidate; + switch (event.keyCode) { + case WebInspector.KeyboardShortcut.Keys.Down.code: + candidate = nextItem.call(this, this._selectedElement, false, true); + break; + case WebInspector.KeyboardShortcut.Keys.Up.code: + candidate = nextItem.call(this, this._selectedElement, false, false); + break; + case WebInspector.KeyboardShortcut.Keys.PageDown.code: + candidate = nextItem.call(this, this._selectedElement, true, true); + break; + case WebInspector.KeyboardShortcut.Keys.PageUp.code: + candidate = nextItem.call(this, this._selectedElement, true, false); + break; + } + + if (candidate) { + this._updateSelection(candidate); + event.preventDefault(); + return; + } + } + + if (event.keyIdentifier !== "Shift" && event.keyIdentifier !== "Ctrl" && event.keyIdentifier !== "Meta" && event.keyIdentifier !== "Left" && event.keyIdentifier !== "Right") + this._scheduleFilter(); + }, + + _scheduleFilter: function() + { + if (this._filterTimer) + return; + this._filterTimer = setTimeout(this._filterItems.bind(this), 0); + }, + + /** + * @param {Element} newSelectedElement + */ + _updateSelection: function(newSelectedElement) + { + if (this._selectedElement === newSelectedElement) + return; + if (this._selectedElement) + this._selectedElement.removeStyleClass("selected"); + + this._selectedElement = newSelectedElement; + if (newSelectedElement) { + newSelectedElement.addStyleClass("selected"); + newSelectedElement.scrollIntoViewIfNeeded(false); + if (!this._itemHeight) { + this._itemHeight = newSelectedElement.offsetHeight; + this._rowsPerViewport = Math.floor(this._itemElementsContainer.offsetHeight / this._itemHeight); + } + } + }, + + _onClick: function(event) + { + var itemElement = event.target.enclosingNodeOrSelfWithClass("item"); + if (!itemElement) + return; + this._updateSelection(itemElement); + this._delegate.selectItem(this._elementIndexes.get(this._selectedElement)); + WebInspector.Dialog.hide(); + }, + + _onMouseMove: function(event) + { + var itemElement = event.target.enclosingNodeOrSelfWithClass("item"); + if (!itemElement) + return; + this._updateSelection(itemElement); + }, + + _onScroll: function() + { + if (this._query) + this._highlightItems(this._query); + else + this._clearHighlight(); + }, + + /** + * @param {string=} query + */ + _highlightItems: function(query) + { + var regex = this._createSearchRegExp(query, true); + for (var i = 0; i < this._delegate.itemsCount(); ++i) { + var itemElement = this._itemElements[i]; + if (this._itemElementVisible(itemElement) && this._itemElementInViewport(itemElement)) + this._highlightItem(itemElement, regex); + } + }, + + _clearHighlight: function() + { + for (var i = 0; i < this._delegate.itemsCount(); ++i) + this._clearElementHighlight(this._itemElements[i]); + }, + + /** + * @param {Element} itemElement + */ + _clearElementHighlight: function(itemElement) + { + var changes = this._elementHighlightChanges.get(itemElement) + if (changes) { + revertDomChanges(changes); + this._elementHighlightChanges.remove(itemElement); + } + }, + + /** + * @param {Element} itemElement + * @param {RegExp} regex + */ + _highlightItem: function(itemElement, regex) + { + this._clearElementHighlight(itemElement); + + var key = this._delegate.itemKeyAt(this._elementIndexes.get(itemElement)); + var ranges = []; + + var match; + while ((match = regex.exec(key)) !== null) { + ranges.push({ offset: match.index, length: regex.lastIndex - match.index }); + } + + var changes = []; + highlightRangesWithStyleClass(itemElement, ranges, "highlight", changes); + + if (changes.length) + this._elementHighlightChanges.put(itemElement, changes); + }, + + /** + * @param {Element} itemElement + * @return {boolean} + */ + _itemElementInViewport: function(itemElement) + { + if (itemElement.offsetTop + this._itemHeight < this._itemElementsContainer.scrollTop) + return false; + if (itemElement.offsetTop > this._itemElementsContainer.scrollTop + this._itemHeight * (this._rowsPerViewport + 1)) + return false; + return true; + } +} + +WebInspector.FilteredItemSelectionDialog.prototype.__proto__ = WebInspector.DialogDelegate.prototype; + +/** + * @interface + */ +WebInspector.SelectionDialogContentProvider = function() +{ +} + +WebInspector.SelectionDialogContentProvider.prototype = { + /** + * @param {number} itemIndex + * @return {string} + */ + itemTitleAt: function(itemIndex) { }, + + /** + * @param {number} itemIndex + * @return {string} + */ + itemKeyAt: function(itemIndex) { }, + + /** + * @return {number} + */ + itemsCount: function() { }, + + /** + * @param {function(number, number, number, number)} callback + */ + requestItems: function(callback) { }, + + /** + * @param {number} itemIndex + */ + selectItem: function(itemIndex) { } +}; + +/** + * @constructor + * @implements {WebInspector.SelectionDialogContentProvider} + */ +WebInspector.JavaScriptOutlineDialog = function(panel, view) +{ + WebInspector.SelectionDialogContentProvider.call(this); + + this._functionItems = []; + + this._panel = panel; + this._view = view; +} + +/** + * @param {{chunk, index, total, id}} data + */ +WebInspector.JavaScriptOutlineDialog.didAddChunk = function(data) +{ + var instance = WebInspector.JavaScriptOutlineDialog._instance; + if (!instance) + return; + + if (data.id !== instance._view.uiSourceCode.id) + return; + + instance._appendItemElements(data.chunk, data.index, data.total); +}, + +WebInspector.JavaScriptOutlineDialog.install = function(panel, viewGetter) +{ + function showJavaScriptOutlineDialog() + { + var view = viewGetter(); + if (view) + WebInspector.JavaScriptOutlineDialog._show(panel, view); + } + + var javaScriptOutlineShortcut = WebInspector.JavaScriptOutlineDialog.createShortcut(); + panel.registerShortcut(javaScriptOutlineShortcut.key, showJavaScriptOutlineDialog); +} + +WebInspector.JavaScriptOutlineDialog._show = function(panel, sourceView) +{ + if (WebInspector.Dialog.currentInstance()) + return; + if (!sourceView || !sourceView.canHighlightLine()) + return; + WebInspector.JavaScriptOutlineDialog._instance = new WebInspector.JavaScriptOutlineDialog(panel, sourceView); + + var filteredItemSelectionDialog = new WebInspector.FilteredItemSelectionDialog(WebInspector.JavaScriptOutlineDialog._instance); + WebInspector.Dialog.show(sourceView.element, filteredItemSelectionDialog); +} + +WebInspector.JavaScriptOutlineDialog.createShortcut = function() +{ + return WebInspector.KeyboardShortcut.makeDescriptor("o", WebInspector.KeyboardShortcut.Modifiers.CtrlOrMeta); +} + +WebInspector.JavaScriptOutlineDialog.prototype = { + /** + * @param {number} itemIndex + * @return {string} + */ + itemTitleAt: function(itemIndex) + { + var functionItem = this._functionItems[itemIndex]; + return functionItem.name + (functionItem.arguments ? functionItem.arguments : ""); + }, + + /** + * @param {number} itemIndex + * @return {string} + */ + itemKeyAt: function(itemIndex) + { + return this._functionItems[itemIndex].name; + }, + + /** + * @return {number} + */ + itemsCount: function() + { + return this._functionItems.length; + }, + + /** + * @param {function(number, number, number, number)} callback + */ + requestItems: function(callback) + { + this._itemsAddedCallback = callback; + this._panel.requestVisibleScriptOutline(); + }, + + /** + * @param {number} itemIndex + */ + selectItem: function(itemIndex) + { + var lineNumber = this._functionItems[itemIndex].line; + if (!isNaN(lineNumber) && lineNumber >= 0) + this._view.highlightLine(lineNumber); + this._view.focus(); + delete WebInspector.JavaScriptOutlineDialog._instance; + }, + + /** + * @param {Array.} chunk + * @param {number} chunkIndex + * @param {number} chunkCount + */ + _appendItemElements: function(chunk, chunkIndex, chunkCount) + { + var index = this._functionItems.length; + for (var i = 0; i < chunk.length; ++i) { + this._functionItems.push(chunk[i]); + } + this._itemsAddedCallback(index, chunk.length, chunkIndex, chunkCount); + } +} + +WebInspector.JavaScriptOutlineDialog.prototype.__proto__ = WebInspector.SelectionDialogContentProvider.prototype; + +/** + * @constructor + * @implements {WebInspector.SelectionDialogContentProvider} + * @param {WebInspector.ScriptsPanel} panel + * @param {WebInspector.DebuggerPresentationModel} presentationModel + */ +WebInspector.OpenResourceDialog = function(panel, presentationModel) +{ + WebInspector.SelectionDialogContentProvider.call(this); + + this._panel = panel; + this._uiSourceCodes = presentationModel.uiSourceCodes(); + + function filterOutEmptyURLs(uiSourceCode) + { + return !!uiSourceCode.fileName; + } + + this._uiSourceCodes = this._uiSourceCodes.filter(filterOutEmptyURLs); +} + +/** + * @param {WebInspector.ScriptsPanel} panel + * @param {WebInspector.DebuggerPresentationModel} presentationModel + */ +WebInspector.OpenResourceDialog.install = function(panel, presentationModel, relativeToElement) +{ + function showOpenResourceDialog() + { + WebInspector.OpenResourceDialog._show(panel, presentationModel, relativeToElement); + } + + var openResourceShortcut = WebInspector.OpenResourceDialog.createShortcut(); + panel.registerShortcut(openResourceShortcut.key, showOpenResourceDialog); +} + +/** + * @param {WebInspector.ScriptsPanel} panel + * @param {WebInspector.DebuggerPresentationModel} presentationModel + * @param {Element} relativeToElement + */ +WebInspector.OpenResourceDialog._show = function(panel, presentationModel, relativeToElement) +{ + if (WebInspector.Dialog.currentInstance()) + return; + + var filteredItemSelectionDialog = new WebInspector.FilteredItemSelectionDialog(new WebInspector.OpenResourceDialog(panel, presentationModel)); + WebInspector.Dialog.show(relativeToElement, filteredItemSelectionDialog); +} + +WebInspector.OpenResourceDialog.createShortcut = function() +{ + return WebInspector.KeyboardShortcut.makeDescriptor("t", WebInspector.KeyboardShortcut.Modifiers.CtrlOrMeta | WebInspector.KeyboardShortcut.Modifiers.Shift); +} + +WebInspector.OpenResourceDialog.prototype = { + /** + * @param {number} itemIndex + * @return {string} + */ + itemTitleAt: function(itemIndex) + { + return this._uiSourceCodes[itemIndex].fileName; + }, + + /** + * @param {number} itemIndex + * @return {string} + */ + itemKeyAt: function(itemIndex) + { + return this._uiSourceCodes[itemIndex].fileName; + }, + + /** + * @return {number} + */ + itemsCount: function() + { + return this._uiSourceCodes.length; + }, + + /** + * @param {function(number, number, number, number)} callback + */ + requestItems: function(callback) + { + callback(0, this._uiSourceCodes.length, 1, 1); + }, + + /** + * @param {number} itemIndex + */ + selectItem: function(itemIndex) + { + this._panel.showUISourceCode(this._uiSourceCodes[itemIndex]); + } +} + +WebInspector.OpenResourceDialog.prototype.__proto__ = WebInspector.SelectionDialogContentProvider.prototype; +/* SettingsScreen.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.HelpScreen} + */ +WebInspector.SettingsScreen = function() +{ + WebInspector.HelpScreen.call(this, WebInspector.UIString("Settings")); + + this._leftColumnElement = document.createElement("td"); + this._rightColumnElement = document.createElement("td"); + + if (Preferences.showDockToRight) { + var p = this._appendSection(WebInspector.UIString("General")); + p.appendChild(this._createCheckboxSetting(WebInspector.UIString("Dock to right"), WebInspector.settings.dockToRight)); + } + + var p = this._appendSection(WebInspector.UIString("Elements")); + p.appendChild(this._createCheckboxSetting(WebInspector.UIString("Word wrap"), WebInspector.settings.domWordWrap)); + + p = this._appendSection(WebInspector.UIString("Styles")); + p.appendChild(this._createRadioSetting(WebInspector.UIString("Color format"), [ + [ WebInspector.StylesSidebarPane.ColorFormat.Original, WebInspector.UIString("As authored") ], + [ WebInspector.StylesSidebarPane.ColorFormat.HEX, "HEX: #DAC0DE" ], + [ WebInspector.StylesSidebarPane.ColorFormat.RGB, "RGB: rgb(128, 255, 255)" ], + [ WebInspector.StylesSidebarPane.ColorFormat.HSL, "HSL: hsl(300, 80%, 90%)" ] ], WebInspector.settings.colorFormat)); + p.appendChild(this._createCheckboxSetting(WebInspector.UIString("Show user agent styles"), WebInspector.settings.showUserAgentStyles)); + + p = this._appendSection(WebInspector.UIString("Text editor")); + p.appendChild(this._createSelectSetting(WebInspector.UIString("Indent"), [ + [ WebInspector.UIString("2 spaces"), WebInspector.TextEditorModel.Indent.TwoSpaces ], + [ WebInspector.UIString("4 spaces"), WebInspector.TextEditorModel.Indent.FourSpaces ], + [ WebInspector.UIString("8 spaces"), WebInspector.TextEditorModel.Indent.EightSpaces ], + [ WebInspector.UIString("Tab character"), WebInspector.TextEditorModel.Indent.TabCharacter ] + ], WebInspector.settings.textEditorIndent)); + + p = this._appendSection(WebInspector.UIString("Network"), true); + if (Preferences.exposeDisableCache) + p.appendChild(this._createCheckboxSetting(WebInspector.UIString("Disable cache"), WebInspector.settings.cacheDisabled)); + p.appendChild(this._createUserActionControl()); + + p = this._appendSection(WebInspector.UIString("Scripts"), true); + p.appendChild(this._createCheckboxSetting(WebInspector.UIString("Show script folders"), WebInspector.settings.showScriptFolders)); + p.appendChild(this._createCheckboxSetting(WebInspector.UIString("Search in content scripts"), WebInspector.settings.searchInContentScripts)); + p.appendChild(this._createCheckboxSetting(WebInspector.UIString("Enable source maps"), WebInspector.settings.sourceMapsEnabled)); + + p = this._appendSection(WebInspector.UIString("Profiler"), true); + p.appendChild(this._createCheckboxSetting(WebInspector.UIString("Show objects' hidden properties"), WebInspector.settings.showHeapSnapshotObjectsHiddenProperties)); + + p = this._appendSection(WebInspector.UIString("Console"), true); + p.appendChild(this._createCheckboxSetting(WebInspector.UIString("Log XMLHttpRequests"), WebInspector.settings.monitoringXHREnabled)); + p.appendChild(this._createCheckboxSetting(WebInspector.UIString("Preserve log upon navigation"), WebInspector.settings.preserveConsoleLog)); + + if (WebInspector.extensionServer.hasExtensions()) { + var handlerSelector = new WebInspector.HandlerSelector(WebInspector.openAnchorLocationRegistry); + p = this._appendSection(WebInspector.UIString("Extensions"), true); + p.appendChild(this._createCustomSetting(WebInspector.UIString("Open links in"), handlerSelector.element)); + } + + var experiments = WebInspector.experimentsSettings.experiments; + if (WebInspector.experimentsSettings.experimentsEnabled && experiments.length) { + var experimentsSection = this._appendSection(WebInspector.UIString("Experiments"), true); + experimentsSection.appendChild(this._createExperimentsWarningSubsection()); + for (var i = 0; i < experiments.length; ++i) + experimentsSection.appendChild(this._createExperimentCheckbox(experiments[i])); + } + + var table = document.createElement("table"); + table.className = "help-table"; + var tr = document.createElement("tr"); + tr.appendChild(this._leftColumnElement); + tr.appendChild(this._rightColumnElement); + table.appendChild(tr); + this.contentElement.appendChild(table); +} + +WebInspector.SettingsScreen.prototype = { + /** + * @param {string} name + * @param {boolean=} right + */ + _appendSection: function(name, right) + { + var p = document.createElement("p"); + p.className = "help-section"; + var title = document.createElement("div"); + title.className = "help-section-title"; + title.textContent = name; + p.appendChild(title); + this._columnElement(right).appendChild(p); + return p; + }, + + /** + * @return {Element} element + */ + _createExperimentsWarningSubsection: function() + { + var subsection = document.createElement("div"); + var warning = subsection.createChild("span", "settings-experiments-warning-subsection-warning"); + warning.textContent = WebInspector.UIString("WARNING:"); + subsection.appendChild(document.createTextNode(" ")); + var message = subsection.createChild("span", "settings-experiments-warning-subsection-message"); + message.textContent = WebInspector.UIString("These experiments could be dangerous and may require restart."); + return subsection; + }, + + _columnElement: function(right) + { + return right ? this._rightColumnElement : this._leftColumnElement; + }, + + _createCheckboxSetting: function(name, setting) + { + var input = document.createElement("input"); + input.type = "checkbox"; + input.name = name; + input.checked = setting.get(); + function listener() + { + setting.set(input.checked); + } + input.addEventListener("click", listener, false); + + var p = document.createElement("p"); + var label = document.createElement("label"); + label.appendChild(input); + label.appendChild(document.createTextNode(name)); + p.appendChild(label); + return p; + }, + + _createExperimentCheckbox: function(experiment) + { + var input = document.createElement("input"); + input.type = "checkbox"; + input.name = experiment.name; + input.checked = experiment.isEnabled(); + function listener() + { + experiment.setEnabled(input.checked); + } + input.addEventListener("click", listener, false); + + var p = document.createElement("p"); + var label = document.createElement("label"); + label.appendChild(input); + label.appendChild(document.createTextNode(WebInspector.UIString(experiment.title))); + p.appendChild(label); + return p; + }, + + _createSelectSetting: function(name, options, setting) + { + var fieldsetElement = document.createElement("fieldset"); + fieldsetElement.createChild("label").textContent = name; + + var select = document.createElement("select"); + var settingValue = setting.get(); + + for (var i = 0; i < options.length; ++i) { + var option = options[i]; + select.add(new Option(option[0], option[1])); + if (settingValue === option[1]) + select.selectedIndex = i; + } + + function changeListener(e) + { + setting.set(e.target.value); + } + + select.addEventListener("change", changeListener, false); + fieldsetElement.appendChild(select); + + var p = document.createElement("p"); + p.appendChild(fieldsetElement); + return p; + }, + + _createRadioSetting: function(name, options, setting) + { + var pp = document.createElement("p"); + var fieldsetElement = document.createElement("fieldset"); + var legendElement = document.createElement("legend"); + legendElement.textContent = name; + fieldsetElement.appendChild(legendElement); + + function clickListener(e) + { + setting.set(e.target.value); + } + + var settingValue = setting.get(); + for (var i = 0; i < options.length; ++i) { + var p = document.createElement("p"); + var label = document.createElement("label"); + p.appendChild(label); + + var input = document.createElement("input"); + input.type = "radio"; + input.name = setting.name; + input.value = options[i][0]; + input.addEventListener("click", clickListener, false); + if (settingValue == input.value) + input.checked = true; + + label.appendChild(input); + label.appendChild(document.createTextNode(options[i][1])); + + fieldsetElement.appendChild(p); + } + + pp.appendChild(fieldsetElement); + return pp; + }, + + _createCustomSetting: function(name, element) + { + var p = document.createElement("p"); + var fieldsetElement = document.createElement("fieldset"); + fieldsetElement.createChild("label").textContent = name; + fieldsetElement.appendChild(element); + p.appendChild(fieldsetElement); + return p; + }, + + _createUserActionControl: function() + { + var userAgent = WebInspector.settings.userAgent.get(); + + var p = document.createElement("p"); + var labelElement = p.createChild("label"); + var checkboxElement = labelElement.createChild("input"); + checkboxElement.type = "checkbox"; + checkboxElement.checked = !!userAgent; + checkboxElement.addEventListener("click", checkboxClicked.bind(this), false); + labelElement.appendChild(document.createTextNode("Override User Agent")); + + var selectSectionElement; + function checkboxClicked() + { + if (checkboxElement.checked) { + selectSectionElement = this._createUserAgentSelectRowElement(); + p.appendChild(selectSectionElement); + } else { + if (selectSectionElement) { + p.removeChild(selectSectionElement); + selectSectionElement = null; + } + WebInspector.settings.userAgent.set(""); + } + } + + checkboxClicked.call(this); + return p; + }, + + _createUserAgentSelectRowElement: function() + { + var userAgent = WebInspector.settings.userAgent.get(); + const userAgents = [ + ["Internet Explorer 9", "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)"], + ["Internet Explorer 8", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)"], + ["Internet Explorer 7", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)"], + + ["Firefox 7 \u2014 Windows", "Mozilla/5.0 (Windows NT 6.1; Intel Mac OS X 10.6; rv:7.0.1) Gecko/20100101 Firefox/7.0.1"], + ["Firefox 7 \u2014 Mac", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:7.0.1) Gecko/20100101 Firefox/7.0.1"], + ["Firefox 4 \u2014 Windows", "Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1"], + ["Firefox 4 \u2014 Mac", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:2.0.1) Gecko/20100101 Firefox/4.0.1"], + + ["iPhone \u2014 iOS 5", "Mozilla/5.0 (iPhone; CPU iPhone OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3"], + ["iPhone \u2014 iOS 4", "Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_3_2 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8H7 Safari/6533.18.5"], + ["iPad \u2014 iOS 5", "Mozilla/5.0 (iPad; CPU OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3"], + ["iPad \u2014 iOS 4", "Mozilla/5.0 (iPad; CPU OS 4_3_2 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8H7 Safari/6533.18.5"], + + ["Android 2.3 \u2014 Nexus S", "Mozilla/5.0 (Linux; U; Android 2.3.6; en-us; Nexus S Build/GRK39F) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1"], + [WebInspector.UIString("Other..."), "Other"] + ]; + + var fieldsetElement = document.createElement("fieldset"); + var selectElement = fieldsetElement.createChild("select"); + var otherUserAgentElement = fieldsetElement.createChild("input"); + otherUserAgentElement.value = userAgent; + otherUserAgentElement.title = userAgent; + + var selectionRestored = false; + for (var i = 0; i < userAgents.length; ++i) { + var agent = userAgents[i]; + selectElement.add(new Option(agent[0], agent[1])); + if (userAgent === agent[1]) { + selectElement.selectedIndex = i; + selectionRestored = true; + } + } + + if (!selectionRestored) { + if (!userAgent) + selectElement.selectedIndex = 0; + else + selectElement.selectedIndex = userAgents.length - 1; + } + + selectElement.addEventListener("change", selectionChanged.bind(this), false); + + function selectionChanged() + { + var value = selectElement.options[selectElement.selectedIndex].value; + if (value !== "Other") { + WebInspector.settings.userAgent.set(value); + otherUserAgentElement.value = value; + otherUserAgentElement.title = value; + otherUserAgentElement.disabled = true; + } else { + otherUserAgentElement.disabled = false; + otherUserAgentElement.focus(); + } + } + + fieldsetElement.addEventListener("dblclick", textDoubleClicked.bind(this), false); + otherUserAgentElement.addEventListener("blur", textChanged.bind(this), false); + + function textDoubleClicked() + { + selectElement.selectedIndex = userAgents.length - 1; + selectionChanged.call(this); + } + + function textChanged() + { + WebInspector.settings.userAgent.set(otherUserAgentElement.value); + } + + selectionChanged.call(this); + return fieldsetElement; + } +} + +WebInspector.SettingsScreen.prototype.__proto__ = WebInspector.HelpScreen.prototype; +/* ShortcutsScreen.js */ + +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.HelpScreen} + */ +WebInspector.ShortcutsScreen = function() +{ + WebInspector.HelpScreen.call(this, WebInspector.UIString("Keyboard Shortcuts")); + this._sections = {}; + this._tableReady = false; +} + +WebInspector.ShortcutsScreen.prototype = { + section: function(name) + { + var section = this._sections[name]; + if (!section) + this._sections[name] = section = new WebInspector.ShortcutsSection(name); + return section; + }, + + show: function(onHide) + { + this._buildTable(this.contentElement, 2); + WebInspector.HelpScreen.prototype.show.call(this, onHide); + }, + + _buildTable: function(parent, nColumns) + { + if (this._tableReady) + return; + this._tableReady = true; + + var height = 0; + var orderedSections = []; + for (var section in this._sections) { + height += this._sections[section]._height; + orderedSections.push(this._sections[section]) + } + function compareSections(a, b) + { + return a.order - b.order; + } + orderedSections = orderedSections.sort(compareSections); + + const wrapAfter = height / nColumns; + var table = document.createElement("table"); + table.className = "help-table"; + var row = table.createChild("tr"); + + // This manual layout ugliness should be gone once WebKit implements + // pagination hints for CSS columns (break-inside etc). + for (var section = 0; section < orderedSections.length;) { + var td = row.createChild("td"); + td.style.width = (100 / nColumns) + "%"; + var column = td.createChild("table"); + for (var columnHeight = 0; + columnHeight < wrapAfter && section < orderedSections.length; + columnHeight += orderedSections[section]._height, section++) { + orderedSections[section].renderSection(column); + } + } + parent.appendChild(table); + } +} + +WebInspector.ShortcutsScreen.prototype.__proto__ = WebInspector.HelpScreen.prototype; + +/** + * We cannot initialize it here as localized strings are not loaded yet. + * @type {?WebInspector.ShortcutsScreen} + */ +WebInspector.shortcutsScreen = null; + +/** + * @constructor + */ +WebInspector.ShortcutsSection = function(name) +{ + this.name = name; + this._lines = []; + this.order = ++WebInspector.ShortcutsSection._sequenceNumber; +}; + +WebInspector.ShortcutsSection._sequenceNumber = 0; + +WebInspector.ShortcutsSection.prototype = { + addKey: function(key, description) + { + this._addLine(this._renderKey(key), description); + }, + + addRelatedKeys: function(keys, description) + { + this._addLine(this._renderSequence(keys, "/"), description); + }, + + addAlternateKeys: function(keys, description) + { + this._addLine(this._renderSequence(keys, WebInspector.UIString("or")), description); + }, + + _addLine: function(keyElement, description) + { + this._lines.push({ key: keyElement, text: description }) + }, + + renderSection: function(parent) + { + this._renderHeader(parent); + + for (var line = 0; line < this._lines.length; ++line) { + var tr = parent.createChild("tr"); + var td = tr.createChild("td", "help-key-cell"); + td.appendChild(this._lines[line].key); + td.appendChild(document.createTextNode(" : ")); + tr.createChild("td").textContent = this._lines[line].text; + } + }, + + _renderHeader: function(parent) + { + var trHead = parent.createChild("tr"); + + trHead.createChild("th"); + trHead.createChild("th").textContent = this.name; + }, + + _renderSequence: function(sequence, delimiter) + { + var delimiterSpan = this._createSpan("help-key-delimiter", delimiter); + return this._joinNodes(sequence.map(this._renderKey.bind(this)), delimiterSpan); + }, + + _renderKey: function(key) + { + var plus = this._createSpan("help-combine-keys", "+"); + return this._joinNodes(key.split(" + ").map(this._createSpan.bind(this, "help-key monospace")), plus); + }, + + get _height() + { + return this._lines.length + 2; // add some space for header + }, + + _createSpan: function(className, textContent) + { + var node = document.createElement("span"); + node.className = className; + node.textContent = textContent; + return node; + }, + + _joinNodes: function(nodes, delimiter) + { + var result = document.createDocumentFragment(); + for (var i = 0; i < nodes.length; ++i) { + if (i > 0) + result.appendChild(delimiter.cloneNode(true)); + result.appendChild(nodes[i]); + } + return result; + } +} +/* HAREntry.js */ + +/* + * Copyright (C) 2012 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// See http://groups.google.com/group/http-archive-specification/web/har-1-2-spec +// for HAR specification. + +// FIXME: Some fields are not yet supported due to back-end limitations. +// See https://bugs.webkit.org/show_bug.cgi?id=58127 for details. + +/** + * @constructor + * @param {WebInspector.Resource} resource + */ +WebInspector.HAREntry = function(resource) +{ + this._resource = resource; +} + +WebInspector.HAREntry.prototype = { + /** + * @return {Object} + */ + build: function() + { + var entry = { + startedDateTime: new Date(this._resource.startTime * 1000), + time: WebInspector.HAREntry._toMilliseconds(this._resource.duration), + request: this._buildRequest(), + response: this._buildResponse(), + cache: { }, // Not supported yet. + timings: this._buildTimings() + }; + var page = WebInspector.networkLog.pageLoadForResource(this._resource); + if (page) + entry.pageref = "page_" + page.id; + return entry; + }, + + /** + * @return {Object} + */ + _buildRequest: function() + { + var res = { + method: this._resource.requestMethod, + url: this._buildRequestURL(this._resource.url), + httpVersion: this._resource.requestHttpVersion, + headers: this._buildHeaders(this._resource.requestHeaders), + queryString: this._buildParameters(this._resource.queryParameters || []), + cookies: this._buildCookies(this._resource.requestCookies || []), + headersSize: this._resource.requestHeadersSize, + bodySize: this.requestBodySize + }; + if (this._resource.requestFormData) + res.postData = this._buildPostData(); + + return res; + }, + + /** + * @return {Object} + */ + _buildResponse: function() + { + return { + status: this._resource.statusCode, + statusText: this._resource.statusText, + httpVersion: this._resource.responseHttpVersion, + headers: this._buildHeaders(this._resource.responseHeaders), + cookies: this._buildCookies(this._resource.responseCookies || []), + content: this._buildContent(), + redirectURL: this._resource.responseHeaderValue("Location") || "", + headersSize: this._resource.responseHeadersSize, + bodySize: this.responseBodySize + }; + }, + + /** + * @return {Object} + */ + _buildContent: function() + { + var content = { + size: this._resource.resourceSize, + mimeType: this._resource.mimeType, + // text: this._resource.content // TODO: pull out into a boolean flag, as content can be huge (and needs to be requested with an async call) + }; + var compression = this.responseCompression; + if (typeof compression === "number") + content.compression = compression; + return content; + }, + + /** + * @return {Object} + */ + _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: blocked, + dns: dns, + connect: connect, + send: send, + wait: this._interval("sendEnd", "receiveHeadersEnd"), + receive: WebInspector.HAREntry._toMilliseconds(this._resource.receiveDuration), + ssl: ssl + }; + }, + + /** + * @return {Object} + */ + _buildHeaders: function(headers) + { + var result = []; + for (var name in headers) + result.push({ name: name, value: headers[name] }); + return result; + }, + + /** + * @return {Object} + */ + _buildPostData: function() + { + var res = { + mimeType: this._resource.requestHeaderValue("Content-Type"), + text: this._resource.requestFormData + }; + if (this._resource.formParameters) + res.params = this._buildParameters(this._resource.formParameters); + return res; + }, + + /** + * @param {Array.} parameters + * @return {Array.} + */ + _buildParameters: function(parameters) + { + return parameters.slice(); + }, + + /** + * @param {string} url + * @return {string} + */ + _buildRequestURL: function(url) + { + return url.split("#", 2)[0]; + }, + + /** + * @param {Array.} cookies + * @return {Array.} + */ + _buildCookies: function(cookies) + { + return cookies.map(this._buildCookie.bind(this)); + }, + + /** + * @param {WebInspector.Cookie} cookie + * @return {Object} + */ + _buildCookie: function(cookie) + { + return { + name: cookie.name, + value: cookie.value, + path: cookie.path, + domain: cookie.domain, + expires: cookie.expires(new Date(this._resource.startTime * 1000)), + httpOnly: cookie.httpOnly, + secure: cookie.secure + }; + }, + + /** + * @param {string} start + * @param {string} end + * @return {number} + */ + _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); + }, + + /** + * @return {number} + */ + get requestBodySize() + { + return !this._resource.requestFormData ? 0 : this._resource.requestFormData.length; + }, + + /** + * @return {number} + */ + get responseBodySize() + { + if (this._resource.cached || this._resource.statusCode === 304) + return 0; + return this._resource.transferSize - this._resource.responseHeadersSize + }, + + /** + * @return {number|undefined} + */ + get responseCompression() + { + if (this._resource.cached || this._resource.statusCode === 304) + return; + return this._resource.resourceSize - (this._resource.transferSize - this._resource.responseHeadersSize); + } +} + +/** + * @param {number} time + * @return {number} + */ +WebInspector.HAREntry._toMilliseconds = function(time) +{ + return time === -1 ? -1 : Math.round(time * 1000); +} + +/** + * @constructor + * @param {Array.} resources + */ +WebInspector.HARLog = function(resources) +{ + this._resources = resources; +} + +WebInspector.HARLog.prototype = { + /** + * @return {Object} + */ + 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: this._resources.map(this._convertResource.bind(this)) + } + }, + + /** + * @return {Array} + */ + _buildPages: function() + { + var seenIdentifiers = {}; + var pages = []; + for (var i = 0; i < this._resources.length; ++i) { + var page = WebInspector.networkLog.pageLoadForResource(this._resources[i]); + if (!page || seenIdentifiers[page.id]) + continue; + seenIdentifiers[page.id] = true; + pages.push(this._convertPage(page)); + } + return pages; + }, + + /** + * @param {WebInspector.PageLoad} page + * @return {Object} + */ + _convertPage: function(page) + { + return { + startedDateTime: new Date(page.startTime * 1000), + id: "page_" + page.id, + title: page.url, // We don't have actual page title here. URL is probably better than nothing. + pageTimings: { + onContentLoad: this._pageEventTime(page, page.contentLoadTime), + onLoad: this._pageEventTime(page, page.loadTime) + } + } + }, + + /** + * @param {WebInspector.Resource} resource + * @return {Object} + */ + _convertResource: function(resource) + { + return (new WebInspector.HAREntry(resource)).build(); + }, + + /** + * @param {WebInspector.PageLoad} page + * @param {number} time + * @return {number} + */ + _pageEventTime: function(page, time) + { + var startTime = page.startTime; + if (time === -1 || startTime === -1) + return -1; + return WebInspector.HAREntry._toMilliseconds(time - startTime); + } +} +/* CookieParser.js */ + +/* + * Copyright (C) 2010 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// Ideally, we would rely on platform support for parsing a cookie, since +// this would save us from any potential inconsistency. However, exposing +// platform cookie parsing logic would require quite a bit of additional +// plumbing, and at least some platforms lack support for parsing Cookie, +// which is in a format slightly different from Set-Cookie and is normally +// only required on the server side. + +/** + * @constructor + */ +WebInspector.CookieParser = function() +{ +} + +/** + * @constructor + * @param {string} key + * @param {string|undefined} value + * @param {number} position + */ +WebInspector.CookieParser.KeyValue = function(key, value, position) +{ + this.key = key; + this.value = value; + this.position = position; +} + +WebInspector.CookieParser.prototype = { + /** + * @return {Array.} + */ + get cookies() + { + return this._cookies; + }, + + /** + * @param {string|undefined} cookieHeader + * @return {?Array.} + */ + parseCookie: function(cookieHeader) + { + if (!this._initialize(cookieHeader)) + return null; + + for (var kv = this._extractKeyValue(); kv; kv = this._extractKeyValue()) { + if (kv.key.charAt(0) === "$" && this._lastCookie) + this._lastCookie.addAttribute(kv.key.slice(1), kv.value); + else if (kv.key.toLowerCase() !== "$version" && typeof kv.value === "string") + this._addCookie(kv, WebInspector.Cookie.Type.Request); + this._advanceAndCheckCookieDelimiter(); + } + this._flushCookie(); + return this._cookies; + }, + + /** + * @param {string|undefined} setCookieHeader + * @return {?Array.} + */ + parseSetCookie: function(setCookieHeader) + { + if (!this._initialize(setCookieHeader)) + return null; + for (var kv = this._extractKeyValue(); kv; kv = this._extractKeyValue()) { + if (this._lastCookie) + this._lastCookie.addAttribute(kv.key, kv.value); + else + this._addCookie(kv, WebInspector.Cookie.Type.Response); + if (this._advanceAndCheckCookieDelimiter()) + this._flushCookie(); + } + this._flushCookie(); + return this._cookies; + }, + + /** + * @param {string|undefined} headerValue + * @return {boolean} + */ + _initialize: function(headerValue) + { + this._input = headerValue; + if (typeof headerValue !== "string") + return false; + this._cookies = []; + this._lastCookie = null; + this._originalInputLength = this._input.length; + return true; + }, + + _flushCookie: function() + { + if (this._lastCookie) + this._lastCookie.size = this._originalInputLength - this._input.length - this._lastCookiePosition; + this._lastCookie = null; + }, + + /** + * @return {WebInspector.CookieParser.KeyValue} + */ + _extractKeyValue: function() + { + if (!this._input || !this._input.length) + return null; + // Note: RFCs offer an option for quoted values that may contain commas and semicolons. + // Many browsers/platforms do not support this, however (see http://webkit.org/b/16699 + // and http://crbug.com/12361). The logic below matches latest versions of IE, Firefox, + // Chrome and Safari on some old platforms. The latest version of Safari supports quoted + // cookie values, though. + var keyValueMatch = /^[ \t]*([^\s=;]+)[ \t]*(?:=[ \t]*([^;\n]*))?/.exec(this._input); + if (!keyValueMatch) { + console.log("Failed parsing cookie header before: " + this._input); + return null; + } + + var result = new WebInspector.CookieParser.KeyValue(keyValueMatch[1], keyValueMatch[2] && keyValueMatch[2].trim(), this._originalInputLength - this._input.length); + this._input = this._input.slice(keyValueMatch[0].length); + return result; + }, + + /** + * @return {boolean} + */ + _advanceAndCheckCookieDelimiter: function() + { + var match = /^\s*[\n;]\s*/.exec(this._input); + if (!match) + return false; + this._input = this._input.slice(match[0].length); + return match[0].match("\n") !== null; + }, + + /** + * @param {WebInspector.CookieParser.KeyValue} keyValue + * @param {number} type + */ + _addCookie: function(keyValue, type) + { + if (this._lastCookie) + this._lastCookie.size = keyValue.position - this._lastCookiePosition; + // Mozilla bug 169091: Mozilla, IE and Chrome treat single token (w/o "=") as + // specifying a value for a cookie with empty name. + this._lastCookie = keyValue.value ? new WebInspector.Cookie(keyValue.key, keyValue.value, type) : + new WebInspector.Cookie("", keyValue.key, type); + this._lastCookiePosition = keyValue.position; + this._cookies.push(this._lastCookie); + } +}; + +/** + * @param {string|undefined} header + * @return {?Array.} + */ +WebInspector.CookieParser.parseCookie = function(header) +{ + return (new WebInspector.CookieParser()).parseCookie(header); +} + +/** + * @param {string|undefined} header + * @return {?Array.} + */ +WebInspector.CookieParser.parseSetCookie = function(header) +{ + return (new WebInspector.CookieParser()).parseSetCookie(header); +} + +/** + * @constructor + */ +WebInspector.Cookie = function(name, value, type) +{ + this.name = name; + this.value = value; + this.type = type; + this._attributes = {}; +} + +WebInspector.Cookie.prototype = { + /** + * @return {boolean} + */ + get httpOnly() + { + return "httponly" in this._attributes; + }, + + /** + * @return {boolean} + */ + get secure() + { + return "secure" in this._attributes; + }, + + /** + * @return {boolean} + */ + get session() + { + // RFC 2965 suggests using Discard attribute to mark session cookies, but this does not seem to be widely used. + // Check for absence of explicity max-age or expiry date instead. + return !("expries" in this._attributes || "max-age" in this._attributes); + }, + + /** + * @return {string} + */ + get path() + { + return this._attributes["path"]; + }, + + /** + * @return {string} + */ + get domain() + { + return this._attributes["domain"]; + }, + + /** + * @return {Date} + */ + expires: function(requestDate) + { + return this._attributes["expires"] ? new Date(this._attributes["expires"]) : + (this._attributes["max-age"] ? new Date(requestDate.getTime() + 1000 * this._attributes["max-age"]) : null); + }, + + /** + * @return {Object} + */ + get attributes() + { + return this._attributes; + }, + + /** + * @param {string} key + * @param {string} value + */ + addAttribute: function(key, value) + { + this._attributes[key.toLowerCase()] = value; + } +} + +WebInspector.Cookie.Type = { + Request: 0, + Response: 1 +}; +/* Toolbar.js */ + + /* + * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2007 Matt Lilek (pewtermoose@gmail.com). + * Copyright (C) 2009 Joseph Pecoraro + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + */ +WebInspector.Toolbar = function() +{ + this.element = document.getElementById("toolbar"); + this.element.addEventListener("mousedown", this._toolbarDragStart.bind(this), true); + + this._dropdownButton = document.getElementById("toolbar-dropdown-arrow"); + this._dropdownButton.addEventListener("click", this._toggleDropdown.bind(this), false); + + document.getElementById("close-button-left").addEventListener("click", this._onClose, true); + document.getElementById("close-button-right").addEventListener("click", this._onClose, true); +} + +WebInspector.Toolbar.prototype = { + set compact(compact) + { + if (compact) + this.element.addStyleClass("toolbar-small"); + else + this.element.removeStyleClass("toolbar-small"); + this._updateDropdownButtonAndHideDropdown(); + }, + + resize: function() + { + this._updateDropdownButtonAndHideDropdown(); + }, + + addPanel: function(panel) + { + this.element.appendChild(panel.toolbarItem); + this.resize(); + }, + + _toolbarDragStart: function(event) + { + if ((!WebInspector.isCompactMode() && WebInspector.platformFlavor() !== WebInspector.PlatformFlavor.MacLeopard && WebInspector.platformFlavor() !== WebInspector.PlatformFlavor.MacSnowLeopard) || WebInspector.port() == "qt") + return; + + var target = event.target; + if (target.hasStyleClass("toolbar-item") && target.hasStyleClass("toggleable")) + return; + + if (target !== this.element && !target.hasStyleClass("toolbar-item")) + return; + + this.element.lastScreenX = event.screenX; + this.element.lastScreenY = event.screenY; + + WebInspector.elementDragStart(this.element, this._toolbarDrag.bind(this), this._toolbarDragEnd.bind(this), event, (WebInspector.isCompactMode() ? "row-resize" : "default")); + }, + + _toolbarDragEnd: function(event) + { + WebInspector.elementDragEnd(event); + + delete this.element.lastScreenX; + delete this.element.lastScreenY; + }, + + _toolbarDrag: function(event) + { + if (WebInspector.isCompactMode()) { + var height = window.innerHeight - (event.screenY - this.element.lastScreenY); + + InspectorFrontendHost.setAttachedWindowHeight(height); + } else { + var x = event.screenX - this.element.lastScreenX; + var y = event.screenY - this.element.lastScreenY; + + // We cannot call window.moveBy here because it restricts the movement + // of the window at the edges. + InspectorFrontendHost.moveWindowBy(x, y); + } + + this.element.lastScreenX = event.screenX; + this.element.lastScreenY = event.screenY; + + event.preventDefault(); + }, + + _onClose: function() + { + WebInspector.close(); + }, + + _setDropdownVisible: function(visible) + { + if (!this._dropdown) { + if (!visible) + return; + this._dropdown = new WebInspector.ToolbarDropdown(); + } + if (visible) + this._dropdown.show(); + else + this._dropdown.hide(); + }, + + _toggleDropdown: function() + { + this._setDropdownVisible(!this._dropdown || !this._dropdown.visible); + }, + + _updateDropdownButtonAndHideDropdown: function() + { + this._setDropdownVisible(false); + + var toolbar = document.getElementById("toolbar"); + if (this.element.scrollHeight > this.element.clientHeight) + this._dropdownButton.removeStyleClass("hidden"); + else + this._dropdownButton.addStyleClass("hidden"); + } +} + +WebInspector.Toolbar.createPanelToolbarItem = function(panel) +{ + var toolbarItem = document.createElement("button"); + toolbarItem.className = "toolbar-item toggleable"; + toolbarItem.panel = panel; + toolbarItem.addStyleClass(panel._panelName); + function onToolbarItemClicked() + { + WebInspector.toolbar._updateDropdownButtonAndHideDropdown(); + WebInspector.inspectorView.setCurrentPanel(panel); + } + toolbarItem.addEventListener("click", onToolbarItemClicked, false); + + var iconElement = toolbarItem.createChild("div", "toolbar-icon"); + + if ("toolbarItemLabel" in panel) + toolbarItem.createChild("div", "toolbar-label").textContent = panel.toolbarItemLabel; + + if (panel === WebInspector.inspectorView.currentPanel()) + toolbarItem.addStyleClass("toggled-on"); + + return toolbarItem; +} + +/** + * @constructor + */ +WebInspector.ToolbarDropdown = function() +{ + this._toolbar = document.getElementById("toolbar"); + this._arrow = document.getElementById("toolbar-dropdown-arrow"); + this.element = document.createElement("div"); + this.element.id = "toolbar-dropdown"; + this.element.className = "toolbar-small"; + this._contentElement = this.element.createChild("div", "scrollable-content"); + this._contentElement.tabIndex = 0; + this._contentElement.addEventListener("keydown", this._onKeyDown.bind(this), true); +} + +WebInspector.ToolbarDropdown.prototype = { + show: function() + { + if (this.visible) + return; + var style = this.element.style; + this._populate(); + var top = this._arrow.totalOffsetTop() + this._arrow.clientHeight; + this._arrow.addStyleClass("dropdown-visible"); + this.element.style.top = top + "px"; + this.element.style.left = this._arrow.totalOffsetLeft() + "px"; + this._contentElement.style.maxHeight = window.innerHeight - top - 20 + "px"; + this._toolbar.appendChild(this.element); + }, + + hide: function() + { + if (!this.visible) + return; + this._arrow.removeStyleClass("dropdown-visible"); + this.element.parentNode.removeChild(this.element); + this._contentElement.removeChildren(); + }, + + get visible() + { + return !!this.element.parentNode; + }, + + _populate: function() + { + var toolbarItems = this._toolbar.querySelectorAll(".toolbar-item.toggleable"); + + for (var i = 0; i < toolbarItems.length; ++i) { + if (toolbarItems[i].offsetTop > 0) + this._contentElement.appendChild(WebInspector.Toolbar.createPanelToolbarItem(toolbarItems[i].panel)); + } + }, + + _onKeyDown: function(event) + { + if (event.keyCode !== WebInspector.KeyboardShortcut.Keys.Esc.code) + return; + event.stopPropagation(); + this.hide(); + } +} + +/** + * @type {?WebInspector.Toolbar} + */ +WebInspector.toolbar = null; +/* SearchController.js */ + +/* + * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2007 Matt Lilek (pewtermoose@gmail.com). + * Copyright (C) 2009 Joseph Pecoraro + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + */ +WebInspector.SearchController = function() +{ + this.element = document.getElementById("search"); + this._matchesElement = document.getElementById("search-results-matches"); + this._toolbarLabelElement = document.getElementById("search-toolbar-label"); + + this.element.addEventListener("search", this._onSearch.bind(this), false); // when the search is emptied + this.element.addEventListener("mousedown", this._onSearchFieldManualFocus.bind(this), false); // when the search field is manually selected + this.element.addEventListener("keydown", this._onKeyDown.bind(this), true); +} + +WebInspector.SearchController.prototype = { + updateSearchMatchesCount: function(matches, panel) + { + if (!panel) + panel = WebInspector.inspectorView.currentPanel(); + + panel.currentSearchMatches = matches; + + if (panel === WebInspector.inspectorView.currentPanel()) + this._updateSearchMatchesCountAndCurrentMatchIndex(WebInspector.inspectorView.currentPanel().currentQuery && matches); + }, + + updateCurrentMatchIndex: function(currentMatchIndex, panel) + { + if (panel === WebInspector.inspectorView.currentPanel()) + this._updateSearchMatchesCountAndCurrentMatchIndex(panel.currentSearchMatches, currentMatchIndex); + }, + + updateSearchLabel: function() + { + var panelName = WebInspector.inspectorView.currentPanel() && WebInspector.inspectorView.currentPanel().toolbarItemLabel; + if (!panelName) + return; + var newLabel = WebInspector.UIString("Search %s", panelName); + if (WebInspector.isCompactMode()) + this.element.setAttribute("placeholder", newLabel); + else { + this.element.removeAttribute("placeholder"); + this._toolbarLabelElement.textContent = newLabel; + } + }, + + cancelSearch: function() + { + this.element.value = ""; + this._performSearch(""); + }, + + disableSearchUntilExplicitAction: function(event) + { + this._performSearch(""); + }, + + handleShortcut: function(event) + { + var isMac = WebInspector.isMac(); + + switch (event.keyIdentifier) { + case "U+0046": // F key + if (isMac) + var isFindKey = event.metaKey && !event.ctrlKey && !event.altKey && !event.shiftKey; + else + var isFindKey = event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey; + + if (isFindKey) { + this.focusSearchField(); + event.handled = true; + } + break; + + + case "F3": + if (!isMac) { + this.focusSearchField(); + event.handled = true; + } + break; + + case "U+0047": // G key + var currentPanel = WebInspector.inspectorView.currentPanel(); + + if (isMac && event.metaKey && !event.ctrlKey && !event.altKey) { + if (event.shiftKey) { + if (currentPanel.jumpToPreviousSearchResult) + currentPanel.jumpToPreviousSearchResult(); + } else if (currentPanel.jumpToNextSearchResult) + currentPanel.jumpToNextSearchResult(); + event.handled = true; + } + break; + } + }, + + activePanelChanged: function() + { + this.updateSearchLabel(); + + if (!this._currentQuery) + return; + + var panel = WebInspector.inspectorView.currentPanel(); + if (panel.performSearch) { + function performPanelSearch() + { + this._updateSearchMatchesCountAndCurrentMatchIndex(); + + panel.currentQuery = this._currentQuery; + panel.performSearch(this._currentQuery); + } + + // Perform the search on a timeout so the panel switches fast. + setTimeout(performPanelSearch.bind(this), 0); + } else { + // Update to show Not found for panels that can't be searched. + this._updateSearchMatchesCountAndCurrentMatchIndex(); + } + }, + + /** + * @param {?number=} matches + * @param {number=} currentMatchIndex + */ + _updateSearchMatchesCountAndCurrentMatchIndex: function(matches, currentMatchIndex) + { + if (matches == null) { + this._matchesElement.addStyleClass("hidden"); + return; + } + + if (matches) { + if (matches === 1) { + if (currentMatchIndex === 0) + var matchesString = WebInspector.UIString("1 of 1 match"); + else + var matchesString = WebInspector.UIString("1 match"); + } else { + if (typeof currentMatchIndex === "number") + var matchesString = WebInspector.UIString("%d of %d matches", currentMatchIndex + 1, matches); + else + var matchesString = WebInspector.UIString("%d matches", matches); + } + } else + var matchesString = WebInspector.UIString("Not Found"); + + this._matchesElement.removeStyleClass("hidden"); + this._matchesElement.textContent = matchesString; + WebInspector.toolbar.resize(); + }, + + focusSearchField: function() + { + this.element.focus(); + this.element.select(); + }, + + _onSearchFieldManualFocus: function(event) + { + WebInspector.setCurrentFocusElement(event.target); + }, + + _onKeyDown: function(event) + { + // Escape Key will clear the field and clear the search results + if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code) { + // If focus belongs here and text is empty - nothing to do, return unhandled. + // When search was selected manually and is currently blank, we'd like Esc stay unhandled + // and hit console drawer handler. + if (event.target.value === "" && WebInspector.currentFocusElement() === WebInspector.previousFocusElement()) + return; + event.preventDefault(); + event.stopPropagation(); + + this.cancelSearch(); + WebInspector.setCurrentFocusElement(WebInspector.previousFocusElement()); + if (WebInspector.currentFocusElement() === event.target) + WebInspector.currentFocusElement().select(); + return false; + } + + if (!isEnterKey(event)) + return false; + + // Select all of the text so the user can easily type an entirely new query. + event.target.select(); + + // Only call performSearch if the Enter key was pressed. Otherwise the search + // performance is poor because of searching on every key. The search field has + // the incremental attribute set, so we still get incremental searches. + this._onSearch(event); + + // Call preventDefault since this was the Enter key. This prevents a "search" event + // from firing for key down. This stops performSearch from being called twice in a row. + event.preventDefault(); + }, + + _onSearch: function(event) + { + var forceSearch = event.keyIdentifier === "Enter"; + this._performSearch(event.target.value, forceSearch, event.shiftKey, false); + }, + + /** + * @param {boolean=} forceSearch + * @param {boolean=} isBackwardSearch + * @param {boolean=} repeatSearch + */ + _performSearch: function(query, forceSearch, isBackwardSearch, repeatSearch) + { + var isShortSearch = (query.length < 3); + + // Clear a leftover short search flag due to a non-conflicting forced search. + if (isShortSearch && this._shortSearchWasForcedByKeyEvent && this._currentQuery !== query) + delete this._shortSearchWasForcedByKeyEvent; + + // Indicate this was a forced search on a short query. + if (isShortSearch && forceSearch) + this._shortSearchWasForcedByKeyEvent = true; + + if (!query || !query.length || (!forceSearch && isShortSearch)) { + // Prevent clobbering a short search forced by the user. + if (this._shortSearchWasForcedByKeyEvent) { + delete this._shortSearchWasForcedByKeyEvent; + return; + } + + delete this._currentQuery; + + for (var panelName in WebInspector.panels) { + var panel = WebInspector.panels[panelName]; + var hadCurrentQuery = !!panel.currentQuery; + delete panel.currentQuery; + if (hadCurrentQuery && panel.searchCanceled) + panel.searchCanceled(); + } + + this._updateSearchMatchesCountAndCurrentMatchIndex(); + + return; + } + + var currentPanel = WebInspector.inspectorView.currentPanel(); + if (!repeatSearch && query === currentPanel.currentQuery && currentPanel.currentQuery === this._currentQuery) { + // When this is the same query and a forced search, jump to the next + // search result for a good user experience. + if (forceSearch) { + if (!isBackwardSearch && currentPanel.jumpToNextSearchResult) + currentPanel.jumpToNextSearchResult(); + else if (isBackwardSearch && currentPanel.jumpToPreviousSearchResult) + currentPanel.jumpToPreviousSearchResult(); + } + return; + } + + this._currentQuery = query; + + this._updateSearchMatchesCountAndCurrentMatchIndex(); + + if (!currentPanel.performSearch) + return; + + currentPanel.currentQuery = query; + currentPanel.performSearch(query); + } +} + +/** + * @type {?WebInspector.SearchController} + */ +WebInspector.searchController = null; +/* WorkerManager.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.Object} + */ +WebInspector.WorkerManager = function() +{ + this._workerIdToWindow = {}; + InspectorBackend.registerWorkerDispatcher(new WebInspector.WorkerDispatcher(this)); +} + +WebInspector.WorkerManager.isWorkerFrontend = function() +{ + return !!WebInspector.queryParamsObject["dedicatedWorkerId"] || + !!WebInspector.queryParamsObject["isSharedWorker"]; +} + +WebInspector.WorkerManager.loaded = function() +{ + var workerId = WebInspector.queryParamsObject["dedicatedWorkerId"]; + if (workerId) + WebInspector.WorkerManager._initializeDedicatedWorkerFrontend(workerId); + else + WebInspector.workerManager = new WebInspector.WorkerManager(); +} + +WebInspector.WorkerManager.loadCompleted = function() +{ + // Make sure script execution of dedicated worker is resumed and then paused + // on the first script statement in case we autoattached to it. + if (WebInspector.queryParamsObject["workerPaused"]) { + DebuggerAgent.pause(); + RuntimeAgent.run(calculateTitle); + } else if (WebInspector.WorkerManager.isWorkerFrontend()) + calculateTitle(); + + function calculateTitle() + { + WebInspector.WorkerManager._calculateWorkerInspectorTitle(); + } + + if (WebInspector.workerManager) + WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.MainFrameNavigated, WebInspector.workerManager._mainFrameNavigated, WebInspector.workerManager); +} + +WebInspector.WorkerManager._initializeDedicatedWorkerFrontend = function(workerId) +{ + function receiveMessage(event) + { + var message = event.data; + InspectorBackend.dispatch(message); + } + window.addEventListener("message", receiveMessage, true); + + + InspectorBackend.sendMessageObjectToBackend = function(message) + { + window.opener.postMessage({workerId: workerId, command: "sendMessageToBackend", message: message}, "*"); + } + + InspectorFrontendHost.loaded = function() + { + window.opener.postMessage({workerId: workerId, command: "loaded"}, "*"); + } +} + +WebInspector.WorkerManager._calculateWorkerInspectorTitle = function() +{ + var expression = "location.href"; + if (WebInspector.queryParamsObject["isSharedWorker"]) + expression += " + (this.name ? ' (' + this.name + ')' : '')"; + RuntimeAgent.evaluate.invoke({expression:expression, doNotPauseOnExceptions:true, returnByValue: true}, evalCallback.bind(this)); + function evalCallback(error, result, wasThrown) + { + if (error || wasThrown) { + console.error(error); + return; + } + InspectorFrontendHost.inspectedURLChanged(result.value); + } +} + +WebInspector.WorkerManager.Events = { + WorkerAdded: "worker-added", + WorkerRemoved: "worker-removed", + WorkersCleared: "workers-cleared", +} + +WebInspector.WorkerManager.prototype = { + _workerCreated: function(workerId, url, inspectorConnected) + { + if (inspectorConnected) + this._openInspectorWindow(workerId, true); + this.dispatchEventToListeners(WebInspector.WorkerManager.Events.WorkerAdded, {workerId: workerId, url: url, inspectorConnected: inspectorConnected}); + }, + + _workerTerminated: function(workerId) + { + this.closeWorkerInspector(workerId); + this.dispatchEventToListeners(WebInspector.WorkerManager.Events.WorkerRemoved, workerId); + }, + + _sendMessageToWorkerInspector: function(workerId, message) + { + var workerInspectorWindow = this._workerIdToWindow[workerId]; + if (workerInspectorWindow) + workerInspectorWindow.postMessage(message, "*"); + }, + + openWorkerInspector: function(workerId) + { + var existingInspector = this._workerIdToWindow[workerId]; + if (existingInspector) { + existingInspector.focus(); + return; + } + + this._openInspectorWindow(workerId, false); + WorkerAgent.connectToWorker(workerId); + }, + + _openInspectorWindow: function(workerId, workerIsPaused) + { + var url = window.location.href + "&dedicatedWorkerId=" + workerId; + if (workerIsPaused) + url += "&workerPaused=true"; + url = url.replace("docked=true&", ""); + // Set location=0 just to make sure the front-end will be opened in a separate window, not in new tab. + var workerInspectorWindow = window.open(url, undefined, "location=0"); + this._workerIdToWindow[workerId] = workerInspectorWindow; + workerInspectorWindow.addEventListener("beforeunload", this._workerInspectorClosing.bind(this, workerId), true); + + // Listen to beforeunload in detached state and to the InspectorClosing event in case of attached inspector. + window.addEventListener("beforeunload", this._pageInspectorClosing.bind(this), true); + WebInspector.notifications.addEventListener(WebInspector.Events.InspectorClosing, this._pageInspectorClosing, this); + }, + + closeWorkerInspector: function(workerId) + { + var workerInspectorWindow = this._workerIdToWindow[workerId]; + if (workerInspectorWindow) + workerInspectorWindow.close(); + }, + + _mainFrameNavigated: function(event) + { + for (var workerId in this._workerIdToWindow) + this.closeWorkerInspector(workerId); + this.dispatchEventToListeners(WebInspector.WorkerManager.Events.WorkersCleared); + }, + + _pageInspectorClosing: function() + { + this._ignoreWorkerInspectorClosing = true; + for (var workerId in this._workerIdToWindow) { + this._workerIdToWindow[workerId].close(); + WorkerAgent.disconnectFromWorker(parseInt(workerId, 10)); + } + }, + + _workerInspectorClosing: function(workerId, event) + { + if (this._ignoreWorkerInspectorClosing) + return; + delete this._workerIdToWindow[workerId]; + WorkerAgent.disconnectFromWorker(workerId); + }, + + _disconnectedFromWorker: function() + { + function onHide() + { + WebInspector.debuggerModel.removeEventListener(WebInspector.DebuggerModel.Events.GlobalObjectCleared, screen.hide, screen); + } + var screen = new WebInspector.WorkerTerminatedScreen(); + WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.GlobalObjectCleared, screen.hide, screen); + screen.show(onHide.bind(this)); + } +} + +WebInspector.WorkerManager.prototype.__proto__ = WebInspector.Object.prototype; + +/** + * @constructor + * @implements {WorkerAgent.Dispatcher} + */ +WebInspector.WorkerDispatcher = function(workerManager) +{ + this._workerManager = workerManager; + window.addEventListener("message", this._receiveMessage.bind(this), true); +} + +WebInspector.WorkerDispatcher.prototype = { + _receiveMessage: function(event) + { + var workerId = event.data["workerId"]; + workerId = parseInt(workerId, 10); + var command = event.data.command; + var message = event.data.message; + + if (command == "sendMessageToBackend") + WorkerAgent.sendMessageToWorker(workerId, message); + }, + + workerCreated: function(workerId, url, inspectorConnected) + { + this._workerManager._workerCreated(workerId, url, inspectorConnected); + }, + + workerTerminated: function(workerId) + { + this._workerManager._workerTerminated(workerId); + }, + + dispatchMessageFromWorker: function(workerId, message) + { + this._workerManager._sendMessageToWorkerInspector(workerId, message); + }, + + disconnectedFromWorker: function() + { + this._workerManager._disconnectedFromWorker(); + } +} + +/** + * @constructor + * @extends {WebInspector.HelpScreen} + */ +WebInspector.WorkerTerminatedScreen = function() +{ + WebInspector.HelpScreen.call(this, WebInspector.UIString("Inspected worker terminated")); + var p = this.contentElement.createChild("p"); + p.addStyleClass("help-section"); + p.textContent = WebInspector.UIString("Inspected worker has terminated. Once it restarts we will attach to it automatically."); +} + +WebInspector.WorkerTerminatedScreen.prototype.__proto__ = WebInspector.HelpScreen.prototype; +/* UserMetrics.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + */ +WebInspector.UserMetrics = function() +{ + for (var actionName in WebInspector.UserMetrics._ActionCodes) { + var actionCode = WebInspector.UserMetrics._ActionCodes[actionName]; + this[actionName] = new WebInspector.UserMetrics._Recorder(actionCode); + } + + function settingChanged(trueCode, falseCode, event) + { + if (event.data) + InspectorFrontendHost.recordSettingChanged(trueCode); + else + InspectorFrontendHost.recordSettingChanged(falseCode); + } + + WebInspector.settings.domWordWrap.addChangeListener(settingChanged.bind(this, WebInspector.UserMetrics._SettingCodes.ElementsDOMWrapOn, WebInspector.UserMetrics._SettingCodes.ElementsDOMWrapOff)); + WebInspector.settings.monitoringXHREnabled.addChangeListener(settingChanged.bind(this, WebInspector.UserMetrics._SettingCodes.ConsoleMonitorXHROn, WebInspector.UserMetrics._SettingCodes.ConsoleMonitorXHROff)); + WebInspector.settings.preserveConsoleLog.addChangeListener(settingChanged.bind(this, WebInspector.UserMetrics._SettingCodes.ConsolePreserveLogOn, WebInspector.UserMetrics._SettingCodes.ConsolePreserveLogOff)); + WebInspector.settings.resourcesLargeRows.addChangeListener(settingChanged.bind(this, WebInspector.UserMetrics._SettingCodes.NetworkShowLargeRowsOn, WebInspector.UserMetrics._SettingCodes.NetworkShowLargeRowsOff)); +} + +// Codes below are used to collect UMA histograms in the Chromium port. +// Do not change the values below, additional actions are needed on the Chromium side +// in order to add more codes. + +WebInspector.UserMetrics._ActionCodes = { + WindowDocked: 1, + WindowUndocked: 2, + ScriptsBreakpointSet: 3, + TimelineStarted: 4, + ProfilesCPUProfileTaken: 5, + ProfilesHeapProfileTaken: 6, + AuditsStarted: 7, + ConsoleEvaluated: 8 +} + +WebInspector.UserMetrics._SettingCodes = { + ElementsDOMWrapOn: 1, + ElementsDOMWrapOff: 2, + ConsoleMonitorXHROn: 3, + ConsoleMonitorXHROff: 4, + ConsolePreserveLogOn: 5, + ConsolePreserveLogOff: 6, + NetworkShowLargeRowsOn: 7, + NetworkShowLargeRowsOff: 8 +} + +WebInspector.UserMetrics._PanelCodes = { + elements: 1, + resources: 2, + network: 3, + scripts: 4, + timeline: 5, + profiles: 6, + audits: 7, + console: 8 +} + +WebInspector.UserMetrics.prototype = { + panelShown: function(panelName) + { + InspectorFrontendHost.recordPanelShown(WebInspector.UserMetrics._PanelCodes[panelName] || 0); + } +} + +/** + * @constructor + */ +WebInspector.UserMetrics._Recorder = function(actionCode) +{ + this._actionCode = actionCode; +} + +WebInspector.UserMetrics._Recorder.prototype = { + record: function() + { + InspectorFrontendHost.recordActionTaken(this._actionCode); + } +} + +WebInspector.userMetrics = new WebInspector.UserMetrics(); +/* JavaScriptContextManager.js */ + +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @constructor + * @extends {WebInspector.Object} + */ +WebInspector.JavaScriptContextManager = function(resourceTreeModel, consoleView) +{ + resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameAdded, this._frameAdded, this); + resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameNavigated, this._frameNavigated, this); + resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameDetached, this._frameDetached, this); + this._consoleView = consoleView; + this._frameIdToContext = {}; +} + +WebInspector.JavaScriptContextManager.prototype = { + _frameAdded: function(event) + { + var frame = event.data; + var context = new WebInspector.FrameEvaluationContext(frame); + this._frameIdToContext[frame.id] = context; + this._consoleView.addContext(context); + }, + + _frameNavigated: function(event) + { + var frame = event.data; + var context = this._frameIdToContext[frame.id]; + if (context) + context._frameNavigated(frame); + }, + + _frameDetached: function(event) + { + var frame = event.data; + var context = this._frameIdToContext[frame.id]; + if (!context) + return; + this._consoleView.removeContext(context); + delete this._frameIdToContext[frame.id]; + }, +} + +WebInspector.JavaScriptContextManager.prototype.__proto__ = WebInspector.Object.prototype; + +/** + * @constructor + * @extends {WebInspector.Object} + */ +WebInspector.FrameEvaluationContext = function(frame) +{ + this._frame = frame; +} + +WebInspector.FrameEvaluationContext.EventTypes = { + Updated: "updated" +} + +WebInspector.FrameEvaluationContext.prototype = +{ + _frameNavigated: function(frame) + { + this._frame = frame; + this.dispatchEventToListeners(WebInspector.FrameEvaluationContext.EventTypes.Updated, this); + }, + + get frameId() + { + return this._frame.id; + }, + + get url() + { + return this._frame.url; + }, + + get displayName() + { + if (!this._frame.parentFrame) + return ""; + var name = this._frame.name || ""; + var subtitle = WebInspector.Resource.displayName(this._frame.url); + if (subtitle) { + if (!name) + return subtitle; + return name + "( " + subtitle + " )"; + } + return "