diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
commit | 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch) | |
tree | 46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebInspectorUI/UserInterface/Models | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/WebInspectorUI/UserInterface/Models')
113 files changed, 22547 insertions, 0 deletions
diff --git a/Source/WebInspectorUI/UserInterface/Models/AnalyzerMessage.js b/Source/WebInspectorUI/UserInterface/Models/AnalyzerMessage.js new file mode 100644 index 000000000..490bb7909 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/AnalyzerMessage.js @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2014 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. + */ + +WebInspector.AnalyzerMessage = class AnalyzerMessage extends WebInspector.Object +{ + constructor(sourceCodeLocation, text, ruleIdentifier) + { + super(); + + console.assert(sourceCodeLocation instanceof WebInspector.SourceCodeLocation); + console.assert(typeof text === "string"); + + this._sourceCodeLocation = sourceCodeLocation; + this._text = text; + this._ruleIdentifier = ruleIdentifier; + } + + // Public + + get sourceCodeLocation() { return this._sourceCodeLocation; } + get sourceCode() { return this._sourceCodeLocation.sourceCode; } + get text() { return this._text; } + get ruleIdentifier() { return this._ruleIdentifier; } +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/ApplicationCacheFrame.js b/Source/WebInspectorUI/UserInterface/Models/ApplicationCacheFrame.js new file mode 100644 index 000000000..068202243 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/ApplicationCacheFrame.js @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2013 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. + */ + +WebInspector.ApplicationCacheFrame = class ApplicationCacheFrame extends WebInspector.Object +{ + constructor(frame, manifest, status) + { + super(); + + console.assert(frame instanceof WebInspector.Frame); + console.assert(manifest instanceof WebInspector.ApplicationCacheManifest); + + this._frame = frame; + this._manifest = manifest; + this._status = status; + } + + // Public + + get frame() { return this._frame; } + get manifest() { return this._manifest; } + get status() { return this._status; } + set status(status) { this._status = status; } + + saveIdentityToCookie(cookie) + { + cookie[WebInspector.ApplicationCacheFrame.FrameURLCookieKey] = this.frame.url; + cookie[WebInspector.ApplicationCacheFrame.ManifestURLCookieKey] = this.manifest.manifestURL; + } +}; + +WebInspector.ApplicationCacheFrame.TypeIdentifier = "application-cache-frame"; +WebInspector.ApplicationCacheFrame.FrameURLCookieKey = "application-cache-frame-url"; +WebInspector.ApplicationCacheFrame.ManifestURLCookieKey = "application-cache-frame-manifest-url"; diff --git a/Source/WebInspectorUI/UserInterface/Models/ApplicationCacheManifest.js b/Source/WebInspectorUI/UserInterface/Models/ApplicationCacheManifest.js new file mode 100644 index 000000000..000241d48 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/ApplicationCacheManifest.js @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2013 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. + */ + +WebInspector.ApplicationCacheManifest = class ApplicationCacheManifest extends WebInspector.Object +{ + constructor(manifestURL) + { + super(); + + this._manifestURL = manifestURL; + } + + // Public + + get manifestURL() { return this._manifestURL; } +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/BackForwardEntry.js b/Source/WebInspectorUI/UserInterface/Models/BackForwardEntry.js new file mode 100644 index 000000000..2c5ec3dab --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/BackForwardEntry.js @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2013 University of Washington. 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 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 + * HOLDER 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.BackForwardEntry = class BackForwardEntry extends WebInspector.Object +{ + constructor(contentView, cookie) + { + super(); + + this._contentView = contentView; + + // ContentViews may be shared across multiple ContentViewContainers. + // A BackForwardEntry containing a tombstone doesn't actually have + // the real ContentView, and so should not close it. + this._tombstone = false; + + // Cookies are compared with Object.shallowEqual, so should not store objects or arrays. + this._cookie = cookie || {}; + this._scrollPositions = []; + + contentView.saveToCookie(this._cookie); + } + + // Public + + makeCopy(newCookie) + { + let copy = new WebInspector.BackForwardEntry(this._contentView, newCookie || this.cookie); + copy._tombstone = this._tombstone; + copy._scrollPositions = this._scrollPositions.slice(); + return copy; + } + + get contentView() + { + return this._contentView; + } + + get cookie() + { + // Cookies are immutable; they represent a specific navigation action. + return Object.shallowCopy(this._cookie); + } + + get tombstone() + { + return this._tombstone; + } + + set tombstone(tombstone) + { + this._tombstone = tombstone; + } + + prepareToShow(shouldCallShown) + { + console.assert(!this._tombstone, "Should not be calling shown on a tombstone"); + + this._restoreFromCookie(); + + this.contentView.visible = true; + if (shouldCallShown) + this.contentView.shown(); + this.contentView.needsLayout(); + } + + prepareToHide() + { + console.assert(!this._tombstone, "Should not be calling hidden on a tombstone"); + + this.contentView.visible = false; + this.contentView.hidden(); + + this._saveScrollPositions(); + } + + isEqual(other) + { + if (!other) + return false; + return this._contentView === other._contentView && Object.shallowEqual(this._cookie, other._cookie); + } + + // Private + + _restoreFromCookie() + { + this._restoreScrollPositions(); + this.contentView.restoreFromCookie(this.cookie); + } + + _restoreScrollPositions() + { + // If no scroll positions are saved, do nothing. + if (!this._scrollPositions.length) + return; + + var scrollableElements = this.contentView.scrollableElements || []; + console.assert(this._scrollPositions.length === scrollableElements.length); + + for (var i = 0; i < scrollableElements.length; ++i) { + var position = this._scrollPositions[i]; + var element = scrollableElements[i]; + if (!element) + continue; + + // Restore the top scroll position by either scrolling to the bottom or to the saved position. + element.scrollTop = position.isScrolledToBottom ? element.scrollHeight : position.scrollTop; + + // Don't restore the left scroll position when scrolled to the bottom. This way the when content changes + // the user won't be left in a weird horizontal position. + element.scrollLeft = position.isScrolledToBottom ? 0 : position.scrollLeft; + } + } + + _saveScrollPositions() + { + var scrollableElements = this.contentView.scrollableElements || []; + var scrollPositions = []; + for (var i = 0; i < scrollableElements.length; ++i) { + var element = scrollableElements[i]; + if (!element) + continue; + + let position = {scrollTop: element.scrollTop, scrollLeft: element.scrollLeft}; + if (this.contentView.shouldKeepElementsScrolledToBottom) + position.isScrolledToBottom = element.isScrolledToBottom(); + + scrollPositions.push(position); + } + + this._scrollPositions = scrollPositions; + } +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/Branch.js b/Source/WebInspectorUI/UserInterface/Models/Branch.js new file mode 100644 index 000000000..829764baa --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/Branch.js @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2013 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. + */ + +WebInspector.Branch = class Branch extends WebInspector.Object +{ + constructor(displayName, revisions, locked) + { + super(); + + console.assert(displayName); + + this._displayName = displayName; + this._revisions = revisions instanceof Array ? revisions.slice() : []; + this._locked = locked || false; + } + + // Public + + get displayName() + { + return this._displayName; + } + + set displayName(displayName) + { + console.assert(displayName); + if (!displayName) + return; + + this._displayName = displayName; + } + + get revisions() + { + return this._revisions; + } + + get locked() + { + return this._locked; + } + + revisionForRepresentedObject(representedObject, doNotCreateIfNeeded) + { + for (var i = 0; i < this._revisions.length; ++i) { + var revision = this._revisions[i]; + if (revision instanceof WebInspector.SourceCodeRevision && revision.sourceCode === representedObject) + return revision; + } + + if (doNotCreateIfNeeded) + return null; + + if (representedObject instanceof WebInspector.SourceCode) { + var revision = representedObject.originalRevision.copy(); + representedObject.currentRevision = revision; + this.addRevision(revision); + return revision; + } + + return null; + } + + addRevision(revision) + { + console.assert(revision instanceof WebInspector.Revision); + + if (this._locked) + return; + + if (this._revisions.includes(revision)) + return; + + this._revisions.push(revision); + } + + removeRevision(revision) + { + console.assert(revision instanceof WebInspector.Revision); + + if (this._locked) + return; + + this._revisions.remove(revision); + } + + reset() + { + if (this._locked) + return; + + this._revisions = []; + } + + fork(displayName) + { + var copiedRevisions = this._revisions.map(function(revision) { return revision.copy(); }); + return new WebInspector.Branch(displayName, copiedRevisions); + } + + apply() + { + for (var i = 0; i < this._revisions.length; ++i) + this._revisions[i].apply(); + } + + revert() + { + for (var i = this._revisions.length - 1; i >= 0; --i) + this._revisions[i].revert(); + } + + lock() + { + console.assert(!this._locked); + this._locked = true; + } + + unlock() + { + console.assert(this._locked); + this._locked = false; + } +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/Breakpoint.js b/Source/WebInspectorUI/UserInterface/Models/Breakpoint.js new file mode 100644 index 000000000..03f85def1 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/Breakpoint.js @@ -0,0 +1,364 @@ +/* + * Copyright (C) 2013, 2014 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. + */ + +WebInspector.Breakpoint = class Breakpoint extends WebInspector.Object +{ + constructor(sourceCodeLocationOrInfo, disabled, condition) + { + super(); + + if (sourceCodeLocationOrInfo instanceof WebInspector.SourceCodeLocation) { + var sourceCode = sourceCodeLocationOrInfo.sourceCode; + var contentIdentifier = sourceCode ? sourceCode.contentIdentifier : null; + var scriptIdentifier = sourceCode instanceof WebInspector.Script ? sourceCode.id : null; + var target = sourceCode instanceof WebInspector.Script ? sourceCode.target : null; + var location = sourceCodeLocationOrInfo; + } else if (sourceCodeLocationOrInfo && typeof sourceCodeLocationOrInfo === "object") { + // The 'url' fallback is for transitioning from older frontends and should be removed. + var contentIdentifier = sourceCodeLocationOrInfo.contentIdentifier || sourceCodeLocationOrInfo.url; + var lineNumber = sourceCodeLocationOrInfo.lineNumber || 0; + var columnNumber = sourceCodeLocationOrInfo.columnNumber || 0; + var location = new WebInspector.SourceCodeLocation(null, lineNumber, columnNumber); + var ignoreCount = sourceCodeLocationOrInfo.ignoreCount || 0; + var autoContinue = sourceCodeLocationOrInfo.autoContinue || false; + var actions = sourceCodeLocationOrInfo.actions || []; + for (var i = 0; i < actions.length; ++i) + actions[i] = new WebInspector.BreakpointAction(this, actions[i]); + disabled = sourceCodeLocationOrInfo.disabled; + condition = sourceCodeLocationOrInfo.condition; + } else + console.error("Unexpected type passed to WebInspector.Breakpoint", sourceCodeLocationOrInfo); + + this._id = null; + this._contentIdentifier = contentIdentifier || null; + this._scriptIdentifier = scriptIdentifier || null; + this._target = target || null; + this._disabled = disabled || false; + this._condition = condition || ""; + this._ignoreCount = ignoreCount || 0; + this._autoContinue = autoContinue || false; + this._actions = actions || []; + this._resolved = false; + + this._sourceCodeLocation = location; + this._sourceCodeLocation.addEventListener(WebInspector.SourceCodeLocation.Event.LocationChanged, this._sourceCodeLocationLocationChanged, this); + this._sourceCodeLocation.addEventListener(WebInspector.SourceCodeLocation.Event.DisplayLocationChanged, this._sourceCodeLocationDisplayLocationChanged, this); + } + + // Public + + get identifier() + { + return this._id; + } + + set identifier(id) + { + this._id = id || null; + } + + get contentIdentifier() + { + return this._contentIdentifier; + } + + get scriptIdentifier() + { + return this._scriptIdentifier; + } + + get target() + { + return this._target; + } + + get sourceCodeLocation() + { + return this._sourceCodeLocation; + } + + get resolved() + { + return this._resolved; + } + + set resolved(resolved) + { + if (this._resolved === resolved) + return; + + function isSpecialBreakpoint() + { + return this._sourceCodeLocation.isEqual(new WebInspector.SourceCodeLocation(null, Infinity, Infinity)); + } + + console.assert(!resolved || this._sourceCodeLocation.sourceCode || isSpecialBreakpoint.call(this), "Breakpoints must have a SourceCode to be resolved.", this); + + this._resolved = resolved || false; + + this.dispatchEventToListeners(WebInspector.Breakpoint.Event.ResolvedStateDidChange); + } + + get disabled() + { + return this._disabled; + } + + set disabled(disabled) + { + if (this._disabled === disabled) + return; + + this._disabled = disabled || false; + + this.dispatchEventToListeners(WebInspector.Breakpoint.Event.DisabledStateDidChange); + } + + get condition() + { + return this._condition; + } + + set condition(condition) + { + if (this._condition === condition) + return; + + this._condition = condition; + + this.dispatchEventToListeners(WebInspector.Breakpoint.Event.ConditionDidChange); + } + + get ignoreCount() + { + return this._ignoreCount; + } + + set ignoreCount(ignoreCount) + { + console.assert(ignoreCount >= 0, "Ignore count cannot be negative."); + if (ignoreCount < 0) + return; + + if (this._ignoreCount === ignoreCount) + return; + + this._ignoreCount = ignoreCount; + + this.dispatchEventToListeners(WebInspector.Breakpoint.Event.IgnoreCountDidChange); + } + + get autoContinue() + { + return this._autoContinue; + } + + set autoContinue(cont) + { + if (this._autoContinue === cont) + return; + + this._autoContinue = cont; + + this.dispatchEventToListeners(WebInspector.Breakpoint.Event.AutoContinueDidChange); + } + + get actions() + { + return this._actions; + } + + get options() + { + return { + condition: this._condition, + ignoreCount: this._ignoreCount, + actions: this._serializableActions(), + autoContinue: this._autoContinue + }; + } + + get info() + { + // The id, scriptIdentifier, target, and resolved state are tied to the current session, so don't include them for serialization. + return { + contentIdentifier: this._contentIdentifier, + lineNumber: this._sourceCodeLocation.lineNumber, + columnNumber: this._sourceCodeLocation.columnNumber, + disabled: this._disabled, + condition: this._condition, + ignoreCount: this._ignoreCount, + actions: this._serializableActions(), + autoContinue: this._autoContinue + }; + } + + get probeActions() + { + return this._actions.filter(function(action) { + return action.type === WebInspector.BreakpointAction.Type.Probe; + }); + } + + cycleToNextMode() + { + if (this.disabled) { + // When cycling, clear auto-continue when going from disabled to enabled. + this.autoContinue = false; + this.disabled = false; + return; + } + + if (this.autoContinue) { + this.disabled = true; + return; + } + + if (this.actions.length) { + this.autoContinue = true; + return; + } + + this.disabled = true; + } + + createAction(type, precedingAction, data) + { + var newAction = new WebInspector.BreakpointAction(this, type, data || null); + + if (!precedingAction) + this._actions.push(newAction); + else { + var index = this._actions.indexOf(precedingAction); + console.assert(index !== -1); + if (index === -1) + this._actions.push(newAction); + else + this._actions.splice(index + 1, 0, newAction); + } + + this.dispatchEventToListeners(WebInspector.Breakpoint.Event.ActionsDidChange); + + return newAction; + } + + recreateAction(type, actionToReplace) + { + var newAction = new WebInspector.BreakpointAction(this, type, null); + + var index = this._actions.indexOf(actionToReplace); + console.assert(index !== -1); + if (index === -1) + return null; + + this._actions[index] = newAction; + + this.dispatchEventToListeners(WebInspector.Breakpoint.Event.ActionsDidChange); + + return newAction; + } + + removeAction(action) + { + var index = this._actions.indexOf(action); + console.assert(index !== -1); + if (index === -1) + return; + + this._actions.splice(index, 1); + + if (!this._actions.length) + this.autoContinue = false; + + this.dispatchEventToListeners(WebInspector.Breakpoint.Event.ActionsDidChange); + } + + clearActions(type) + { + if (!type) + this._actions = []; + else + this._actions = this._actions.filter(function(action) { return action.type !== type; }); + + this.dispatchEventToListeners(WebInspector.Breakpoint.Event.ActionsDidChange); + } + + saveIdentityToCookie(cookie) + { + cookie[WebInspector.Breakpoint.ContentIdentifierCookieKey] = this.contentIdentifier; + cookie[WebInspector.Breakpoint.LineNumberCookieKey] = this.sourceCodeLocation.lineNumber; + cookie[WebInspector.Breakpoint.ColumnNumberCookieKey] = this.sourceCodeLocation.columnNumber; + } + + // Protected (Called by BreakpointAction) + + breakpointActionDidChange(action) + { + var index = this._actions.indexOf(action); + console.assert(index !== -1); + if (index === -1) + return; + + this.dispatchEventToListeners(WebInspector.Breakpoint.Event.ActionsDidChange); + } + + // Private + + _serializableActions() + { + var actions = []; + for (var i = 0; i < this._actions.length; ++i) + actions.push(this._actions[i].info); + return actions; + } + + _sourceCodeLocationLocationChanged(event) + { + this.dispatchEventToListeners(WebInspector.Breakpoint.Event.LocationDidChange, event.data); + } + + _sourceCodeLocationDisplayLocationChanged(event) + { + this.dispatchEventToListeners(WebInspector.Breakpoint.Event.DisplayLocationDidChange, event.data); + } +}; + +WebInspector.Breakpoint.DefaultBreakpointActionType = WebInspector.BreakpointAction.Type.Log; + +WebInspector.Breakpoint.TypeIdentifier = "breakpoint"; +WebInspector.Breakpoint.ContentIdentifierCookieKey = "breakpoint-content-identifier"; +WebInspector.Breakpoint.LineNumberCookieKey = "breakpoint-line-number"; +WebInspector.Breakpoint.ColumnNumberCookieKey = "breakpoint-column-number"; + +WebInspector.Breakpoint.Event = { + DisabledStateDidChange: "breakpoint-disabled-state-did-change", + ResolvedStateDidChange: "breakpoint-resolved-state-did-change", + ConditionDidChange: "breakpoint-condition-did-change", + IgnoreCountDidChange: "breakpoint-ignore-count-did-change", + ActionsDidChange: "breakpoint-actions-did-change", + AutoContinueDidChange: "breakpoint-auto-continue-did-change", + LocationDidChange: "breakpoint-location-did-change", + DisplayLocationDidChange: "breakpoint-display-location-did-change", +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/BreakpointAction.js b/Source/WebInspectorUI/UserInterface/Models/BreakpointAction.js new file mode 100644 index 000000000..71249a435 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/BreakpointAction.js @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2013, 2014 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. + */ + +WebInspector.BreakpointAction = class BreakpointAction extends WebInspector.Object +{ + constructor(breakpoint, typeOrInfo, data) + { + super(); + + console.assert(breakpoint); + console.assert(typeOrInfo); + + this._breakpoint = breakpoint; + + if (typeof typeOrInfo === "string") { + this._type = typeOrInfo; + this._data = data || null; + } else if (typeof typeOrInfo === "object") { + this._type = typeOrInfo.type; + this._data = typeOrInfo.data || null; + } else + console.error("Unexpected type passed to WebInspector.BreakpointAction"); + + console.assert(typeof this._type === "string"); + this._id = WebInspector.debuggerManager.nextBreakpointActionIdentifier(); + } + + // Public + + get breakpoint() { return this._breakpoint; } + get id() { return this._id; } + get type() { return this._type; } + + get data() + { + return this._data; + } + + set data(data) + { + if (this._data === data) + return; + + this._data = data; + + this._breakpoint.breakpointActionDidChange(this); + } + + get info() + { + var obj = {type: this._type, id: this._id}; + if (this._data) + obj.data = this._data; + return obj; + } +}; + +WebInspector.BreakpointAction.Type = { + Log: "log", + Evaluate: "evaluate", + Sound: "sound", + Probe: "probe" +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/CSSCompletions.js b/Source/WebInspectorUI/UserInterface/Models/CSSCompletions.js new file mode 100644 index 000000000..8cab966b6 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/CSSCompletions.js @@ -0,0 +1,298 @@ +/* + * Copyright (C) 2010 Nikita Vasilyev. All rights reserved. + * Copyright (C) 2010 Joseph Pecoraro. All rights reserved. + * Copyright (C) 2010 Google Inc. All rights reserved. + * Copyright (C) 2013 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. + */ + +WebInspector.CSSCompletions = class CSSCompletions +{ + constructor(properties, acceptEmptyPrefix) + { + this._values = []; + this._longhands = {}; + this._shorthands = {}; + + // The `properties` parameter can be either a list of objects with 'name' / 'longhand' + // properties when initialized from the protocol for CSSCompletions.cssNameCompletions. + // Or it may just a list of strings when quickly initialized for other completion purposes. + if (properties.length && typeof properties[0] === "string") + this._values = this._values.concat(properties); + else { + for (var property of properties) { + var propertyName = property.name; + console.assert(propertyName); + + this._values.push(propertyName); + + var longhands = property.longhands; + if (longhands) { + this._longhands[propertyName] = longhands; + + for (var j = 0; j < longhands.length; ++j) { + var longhandName = longhands[j]; + + var shorthands = this._shorthands[longhandName]; + if (!shorthands) { + shorthands = []; + this._shorthands[longhandName] = shorthands; + } + + shorthands.push(propertyName); + } + } + } + } + + this._values.sort(); + + this._acceptEmptyPrefix = acceptEmptyPrefix; + } + + // Static + + static requestCSSCompletions() + { + if (WebInspector.CSSCompletions.cssNameCompletions) + return; + + function propertyNamesCallback(error, names) + { + if (error) + return; + + WebInspector.CSSCompletions.cssNameCompletions = new WebInspector.CSSCompletions(names, false); + + WebInspector.CSSKeywordCompletions.addCustomCompletions(names); + + // CodeMirror is not included by tests so we shouldn't assume it always exists. + // If it isn't available we skip MIME type associations. + if (!window.CodeMirror) + return; + + var propertyNamesForCodeMirror = {}; + var valueKeywordsForCodeMirror = {"inherit": true, "initial": true, "unset": true, "revert": true, "var": true}; + var colorKeywordsForCodeMirror = {}; + + function nameForCodeMirror(name) + { + // CodeMirror parses the vendor prefix separate from the property or keyword name, + // so we need to strip vendor prefixes from our names. Also strip function parenthesis. + return name.replace(/^-[^-]+-/, "").replace(/\(\)$/, "").toLowerCase(); + } + + function collectPropertyNameForCodeMirror(propertyName) + { + // Properties can also be value keywords, like when used in a transition. + // So we add them to both lists. + var codeMirrorPropertyName = nameForCodeMirror(propertyName); + propertyNamesForCodeMirror[codeMirrorPropertyName] = true; + valueKeywordsForCodeMirror[codeMirrorPropertyName] = true; + } + + for (var property of names) + collectPropertyNameForCodeMirror(property.name); + + for (var propertyName in WebInspector.CSSKeywordCompletions._propertyKeywordMap) { + var keywords = WebInspector.CSSKeywordCompletions._propertyKeywordMap[propertyName]; + for (var i = 0; i < keywords.length; ++i) { + // Skip numbers, like the ones defined for font-weight. + if (!isNaN(Number(keywords[i]))) + continue; + valueKeywordsForCodeMirror[nameForCodeMirror(keywords[i])] = true; + } + } + + WebInspector.CSSKeywordCompletions._colors.forEach(function(colorName) { + colorKeywordsForCodeMirror[nameForCodeMirror(colorName)] = true; + }); + + function updateCodeMirrorCSSMode(mimeType) + { + var modeSpec = CodeMirror.resolveMode(mimeType); + + console.assert(modeSpec.propertyKeywords); + console.assert(modeSpec.valueKeywords); + console.assert(modeSpec.colorKeywords); + + modeSpec.propertyKeywords = propertyNamesForCodeMirror; + modeSpec.valueKeywords = valueKeywordsForCodeMirror; + modeSpec.colorKeywords = colorKeywordsForCodeMirror; + + CodeMirror.defineMIME(mimeType, modeSpec); + } + + updateCodeMirrorCSSMode("text/css"); + updateCodeMirrorCSSMode("text/x-scss"); + } + + function fontFamilyNamesCallback(error, fontFamilyNames) + { + if (error) + return; + + WebInspector.CSSKeywordCompletions.addPropertyCompletionValues("font-family", fontFamilyNames); + WebInspector.CSSKeywordCompletions.addPropertyCompletionValues("font", fontFamilyNames); + } + + if (window.CSSAgent) { + CSSAgent.getSupportedCSSProperties(propertyNamesCallback); + + // COMPATIBILITY (iOS 9): CSS.getSupportedSystemFontFamilyNames did not exist. + if (CSSAgent.getSupportedSystemFontFamilyNames) + CSSAgent.getSupportedSystemFontFamilyNames(fontFamilyNamesCallback); + } + } + + // Public + + get values() + { + return this._values; + } + + startsWith(prefix) + { + var firstIndex = this._firstIndexOfPrefix(prefix); + if (firstIndex === -1) + return []; + + var results = []; + while (firstIndex < this._values.length && this._values[firstIndex].startsWith(prefix)) + results.push(this._values[firstIndex++]); + return results; + } + + _firstIndexOfPrefix(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].startsWith(prefix)) { + 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].startsWith(prefix)) + foundIndex--; + + return foundIndex; + } + + keySet() + { + if (!this._keySet) + this._keySet = this._values.keySet(); + return this._keySet; + } + + next(str, prefix) + { + return this._closest(str, prefix, 1); + } + + previous(str, prefix) + { + return this._closest(str, prefix, -1); + } + + _closest(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]; + } + + isShorthandPropertyName(shorthand) + { + return shorthand in this._longhands; + } + + shorthandsForLonghand(longhand) + { + return this._shorthands[longhand] || []; + } + + isValidPropertyName(name) + { + return this._values.includes(name); + } + + propertyRequiresWebkitPrefix(name) + { + return this._values.includes("-webkit-" + name) && !this._values.includes(name); + } + + getClosestPropertyName(name) + { + var bestMatches = [{distance: Infinity, name: null}]; + + for (var property of this._values) { + var distance = name.levenshteinDistance(property); + + if (distance < bestMatches[0].distance) + bestMatches = [{distance, name: property}]; + else if (distance === bestMatches[0].distance) + bestMatches.push({distance, name: property}); + } + + return bestMatches.length < 3 ? bestMatches[0].name : false; + } +}; + +WebInspector.CSSCompletions.cssNameCompletions = null; diff --git a/Source/WebInspectorUI/UserInterface/Models/CSSKeywordCompletions.js b/Source/WebInspectorUI/UserInterface/Models/CSSKeywordCompletions.js new file mode 100644 index 000000000..395e4d28b --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/CSSKeywordCompletions.js @@ -0,0 +1,1035 @@ +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * Copyright (C) 2013 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. + */ + +WebInspector.CSSKeywordCompletions = {}; + +WebInspector.CSSKeywordCompletions.forProperty = function(propertyName) +{ + let acceptedKeywords = ["initial", "unset", "revert", "var()"]; + let isNotPrefixed = propertyName.charAt(0) !== "-"; + + if (propertyName in WebInspector.CSSKeywordCompletions._propertyKeywordMap) + acceptedKeywords = acceptedKeywords.concat(WebInspector.CSSKeywordCompletions._propertyKeywordMap[propertyName]); + else if (isNotPrefixed && ("-webkit-" + propertyName) in WebInspector.CSSKeywordCompletions._propertyKeywordMap) + acceptedKeywords = acceptedKeywords.concat(WebInspector.CSSKeywordCompletions._propertyKeywordMap["-webkit-" + propertyName]); + + if (propertyName in WebInspector.CSSKeywordCompletions._colorAwareProperties) + acceptedKeywords = acceptedKeywords.concat(WebInspector.CSSKeywordCompletions._colors); + else if (isNotPrefixed && ("-webkit-" + propertyName) in WebInspector.CSSKeywordCompletions._colorAwareProperties) + acceptedKeywords = acceptedKeywords.concat(WebInspector.CSSKeywordCompletions._colors); + else if (propertyName.endsWith("color")) + acceptedKeywords = acceptedKeywords.concat(WebInspector.CSSKeywordCompletions._colors); + + // Only suggest "inherit" on inheritable properties even though it is valid on all properties. + if (propertyName in WebInspector.CSSKeywordCompletions.InheritedProperties) + acceptedKeywords.push("inherit"); + else if (isNotPrefixed && ("-webkit-" + propertyName) in WebInspector.CSSKeywordCompletions.InheritedProperties) + acceptedKeywords.push("inherit"); + + if (acceptedKeywords.includes(WebInspector.CSSKeywordCompletions.AllPropertyNamesPlaceholder) && WebInspector.CSSCompletions.cssNameCompletions) { + acceptedKeywords.remove(WebInspector.CSSKeywordCompletions.AllPropertyNamesPlaceholder); + acceptedKeywords = acceptedKeywords.concat(WebInspector.CSSCompletions.cssNameCompletions.values); + } + + return new WebInspector.CSSCompletions(acceptedKeywords, true); +}; + +WebInspector.CSSKeywordCompletions.addCustomCompletions = function(properties) +{ + for (var property of properties) { + if (property.values) + WebInspector.CSSKeywordCompletions.addPropertyCompletionValues(property.name, property.values); + } +}; + +WebInspector.CSSKeywordCompletions.addPropertyCompletionValues = function(propertyName, newValues) +{ + var existingValues = WebInspector.CSSKeywordCompletions._propertyKeywordMap[propertyName]; + if (!existingValues) { + WebInspector.CSSKeywordCompletions._propertyKeywordMap[propertyName] = newValues; + return; + } + + var union = new Set; + for (var value of existingValues) + union.add(value); + for (var value of newValues) + union.add(value); + + WebInspector.CSSKeywordCompletions._propertyKeywordMap[propertyName] = [...union.values()]; +}; + +WebInspector.CSSKeywordCompletions.AllPropertyNamesPlaceholder = "__all-properties__"; + +WebInspector.CSSKeywordCompletions.InheritedProperties = [ + "azimuth", "border-collapse", "border-spacing", "caption-side", "clip-rule", "color", "color-interpolation", + "color-interpolation-filters", "color-rendering", "cursor", "direction", "elevation", "empty-cells", "fill", + "fill-opacity", "fill-rule", "font", "font-family", "font-size", "font-style", "font-variant", "font-variant-numeric", "font-weight", + "glyph-orientation-horizontal", "glyph-orientation-vertical", "hanging-punctuation", "image-rendering", "kerning", "letter-spacing", + "line-height", "list-style", "list-style-image", "list-style-position", "list-style-type", "marker", "marker-end", + "marker-mid", "marker-start", "orphans", "pitch", "pitch-range", "pointer-events", "quotes", "resize", "richness", + "shape-rendering", "speak", "speak-header", "speak-numeral", "speak-punctuation", "speech-rate", "stress", "stroke", + "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", + "stroke-width", "tab-size", "text-align", "text-anchor", "text-decoration", "text-indent", "text-rendering", + "text-shadow", "text-transform", "visibility", "voice-family", "volume", "white-space", "widows", "word-break", + "word-spacing", "word-wrap", "writing-mode", "-webkit-aspect-ratio", "-webkit-border-horizontal-spacing", + "-webkit-border-vertical-spacing", "-webkit-box-direction", "-webkit-color-correction", "font-feature-settings", + "-webkit-font-kerning", "-webkit-font-smoothing", "-webkit-font-variant-ligatures", + "-webkit-hyphenate-character", "-webkit-hyphenate-limit-after", "-webkit-hyphenate-limit-before", + "-webkit-hyphenate-limit-lines", "-webkit-hyphens", "-webkit-line-align", "-webkit-line-box-contain", + "-webkit-line-break", "-webkit-line-grid", "-webkit-line-snap", "-webkit-locale", "-webkit-nbsp-mode", + "-webkit-print-color-adjust", "-webkit-rtl-ordering", "-webkit-text-combine", "-webkit-text-decorations-in-effect", + "-webkit-text-emphasis", "-webkit-text-emphasis-color", "-webkit-text-emphasis-position", "-webkit-text-emphasis-style", + "-webkit-text-fill-color", "-webkit-text-orientation", "-webkit-text-security", "-webkit-text-size-adjust", + "-webkit-text-stroke", "-webkit-text-stroke-color", "-webkit-text-stroke-width", "-webkit-user-modify", + "-webkit-user-select", "-webkit-writing-mode", "-webkit-cursor-visibility", "image-orientation", "image-resolution", + "overflow-wrap", "-webkit-text-align-last", "-webkit-text-justify", "-webkit-ruby-position", "-webkit-text-decoration-line", + "font-synthesis", + + // iOS Properties + "-webkit-overflow-scrolling", "-webkit-touch-callout", "-webkit-tap-highlight-color" +].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", "rebeccapurple", "rosybrown", + "royalblue", "saddlebrown", "salmon", "sandybrown", "seagreen", "seashell", "sienna", "skyblue", "slateblue", + "slategray", "slategrey", "snow", "springgreen", "steelblue", "tan", "thistle", "tomato", "turquoise", "violet", + "wheat", "whitesmoke", "yellowgreen", "rgb()", "rgba()", "hsl()", "hsla()" +]; + +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", "box-shadow", "color", + "fill", "outline", "outline-color", "stroke", "text-line-through", "text-line-through-color", "text-overline", "text-overline-color", + "text-shadow", "text-underline", "text-underline-color", "-webkit-box-shadow", "-webkit-column-rule", "-webkit-column-rule-color", + "-webkit-text-emphasis", "-webkit-text-emphasis-color", "-webkit-text-fill-color", "-webkit-text-stroke", "-webkit-text-stroke-color", + "-webkit-text-decoration-color", + + // iOS Properties + "-webkit-tap-highlight-color" +].keySet(); + +WebInspector.CSSKeywordCompletions._propertyKeywordMap = { + "table-layout": [ + "auto", "fixed" + ], + "visibility": [ + "hidden", "visible", "collapse" + ], + "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", "attr()", "counter()", "counters()", "url()", "linear-gradient()", "radial-gradient()", "repeating-linear-gradient()", "repeating-radial-gradient()", "-webkit-canvas()", "cross-fade()", "image-set()" + ], + "list-style-image": [ + "none", "url()", "linear-gradient()", "radial-gradient()", "repeating-linear-gradient()", "repeating-radial-gradient()", "-webkit-canvas()", "cross-fade()", "image-set()" + ], + "clear": [ + "none", "left", "right", "both" + ], + "fill-rule": [ + "nonzero", "evenodd" + ], + "stroke-linecap": [ + "butt", "round", "square" + ], + "stroke-linejoin": [ + "round", "miter", "bevel" + ], + "baseline-shift": [ + "baseline", "sub", "super" + ], + "border-bottom-width": [ + "medium", "thick", "thin", "calc()" + ], + "margin-top-collapse": [ + "collapse", "separate", "discard" + ], + "-webkit-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" + ], + "border-left-width": [ + "medium", "thick", "thin", "calc()" + ], + "-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" + ], + "text-overline-mode": [ + "continuous", "skip-white-space" + ], + "text-underline-mode": [ + "continuous", "skip-white-space" + ], + "text-line-through-style": [ + "none", "dotted", "dashed", "solid", "double", "dot-dash", "dot-dot-dash", "wave" + ], + "text-overline-style": [ + "none", "dotted", "dashed", "solid", "double", "dot-dash", "dot-dot-dash", "wave" + ], + "text-underline-style": [ + "none", "dotted", "dashed", "solid", "double", "dot-dash", "dot-dot-dash", "wave" + ], + "border-collapse": [ + "collapse", "separate" + ], + "border-top-width": [ + "medium", "thick", "thin", "calc()" + ], + "outline-color": [ + "invert", "-webkit-focus-ring-color" + ], + "outline-style": [ + "none", "hidden", "inset", "groove", "ridge", "outset", "dotted", "dashed", "solid", "double", "auto" + ], + "cursor": [ + "none", "copy", "auto", "crosshair", "default", "pointer", "move", "vertical-text", "cell", "context-menu", + "alias", "progress", "no-drop", "not-allowed", "zoom-in", "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-zoom-in", "-webkit-zoom-out", + "-webkit-grabbing", "url()", "image-set()" + ], + "border-width": [ + "medium", "thick", "thin", "calc()" + ], + "size": [ + "a3", "a4", "a5", "b4", "b5", "landscape", "ledger", "legal", "letter", "portrait" + ], + "background": [ + "none", "url()", "linear-gradient()", "radial-gradient()", "repeating-linear-gradient()", "repeating-radial-gradient()", "-webkit-canvas()", "cross-fade()", "image-set()", + "repeat", "repeat-x", "repeat-y", "no-repeat", "space", "round", + "scroll", "fixed", "local", + "auto", "contain", "cover", + "top", "right", "left", "bottom", "center", + "border-box", "padding-box", "content-box" + ], + "background-image": [ + "none", "url()", "linear-gradient()", "radial-gradient()", "repeating-linear-gradient()", "repeating-radial-gradient()", "-webkit-canvas()", "cross-fade()", "image-set()" + ], + "background-size": [ + "auto", "contain", "cover" + ], + "background-attachment": [ + "scroll", "fixed", "local" + ], + "background-repeat": [ + "repeat", "repeat-x", "repeat-y", "no-repeat", "space", "round" + ], + "background-blend-mode": [ + "normal", "multiply", "screen", "overlay", "darken", "lighten", "color-dodge", "color-burn", "hard-light", "soft-light", "difference", "exclusion", "hue", "saturation", "color", "luminosity" + ], + "background-position": [ + "top", "right", "left", "bottom", "center" + ], + "background-origin": [ + "border-box", "padding-box", "content-box" + ], + "background-clip": [ + "border-box", "padding-box", "content-box" + ], + "direction": [ + "ltr", "rtl" + ], + "enable-background": [ + "accumulate", "new" + ], + "float": [ + "none", "left", "right" + ], + "hanging-punctuation": [ + "none", "first", "last", "allow-end", "force-end" + ], + "overflow-x": [ + "hidden", "auto", "visible", "overlay", "scroll", "marquee" + ], + "overflow-y": [ + "hidden", "auto", "visible", "overlay", "scroll", "marquee", "-webkit-paged-x", "-webkit-paged-y" + ], + "overflow": [ + "hidden", "auto", "visible", "overlay", "scroll", "marquee", "-webkit-paged-x", "-webkit-paged-y" + ], + "margin-bottom-collapse": [ + "collapse", "separate", "discard" + ], + "-webkit-box-reflect": [ + "none", "left", "right", "above", "below" + ], + "text-rendering": [ + "auto", "optimizeSpeed", "optimizeLegibility", "geometricPrecision" + ], + "text-align": [ + "-webkit-auto", "left", "right", "center", "justify", "-webkit-left", "-webkit-right", "-webkit-center", "-webkit-match-parent", "start", "end" + ], + "list-style-position": [ + "outside", "inside" + ], + "margin-bottom": [ + "auto" + ], + "color-interpolation": [ + "linearrgb" + ], + "word-wrap": [ + "normal", "break-word" + ], + "font-weight": [ + "normal", "bold", "bolder", "lighter", "100", "200", "300", "400", "500", "600", "700", "800", "900" + ], + "font-synthesis": [ + "none", "weight", "style" + ], + "margin-before-collapse": [ + "collapse", "separate", "discard" + ], + "text-overline-width": [ + "normal", "medium", "auto", "thick", "thin", "calc()" + ], + "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" + ], + "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", "-apple-system", + "-apple-system-headline", "-apple-system-body", "-apple-system-subheadline", "-apple-system-footnote", + "-apple-system-caption1", "-apple-system-caption2", "-apple-system-short-headline", "-apple-system-short-body", + "-apple-system-short-subheadline", "-apple-system-short-footnote", "-apple-system-short-caption1", + "-apple-system-tall-body", "-apple-system-title1", "-apple-system-title2", "-apple-system-title3" + ], + "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", "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", + "flex", "inline-flex", "grid", "inline-grid" + ], + "image-rendering": [ + "auto", "optimizeSpeed", "optimizeQuality", "-webkit-crisp-edges", "-webkit-optimize-contrast", "crisp-edges", "pixelated" + ], + "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", "calc()" + ], + "text-line-through-width": [ + "normal", "medium", "auto", "thick", "thin" + ], + "box-align": [ + "baseline", "center", "stretch", "start", "end" + ], + "box-shadow": [ + "none" + ], + "text-shadow": [ + "none" + ], + "-webkit-box-shadow": [ + "none" + ], + "border-right-width": [ + "medium", "thick", "thin" + ], + "border-top-style": [ + "none", "hidden", "inset", "groove", "ridge", "outset", "dotted", "dashed", "solid", "double" + ], + "line-height": [ + "normal" + ], + "counter-increment": [ + "none" + ], + "counter-reset": [ + "none" + ], + "text-overflow": [ + "clip", "ellipsis" + ], + "-webkit-box-direction": [ + "normal", "reverse" + ], + "margin-after-collapse": [ + "collapse", "separate", "discard" + ], + "break-after": [ + "left", "right", "recto", "verso", "auto", "avoid", "page", "column", "region", "avoid-page", "avoid-column", "avoid-region" + ], + "break-before": [ + "left", "right", "recto", "verso", "auto", "avoid", "page", "column", "region", "avoid-page", "avoid-column", "avoid-region" + ], + "break-inside": [ + "auto", "avoid", "avoid-page", "avoid-column", "avoid-region" + ], + "page-break-after": [ + "left", "right", "auto", "always", "avoid" + ], + "page-break-before": [ + "left", "right", "auto", "always", "avoid" + ], + "page-break-inside": [ + "auto", "avoid" + ], + "-webkit-column-break-after": [ + "left", "right", "auto", "always", "avoid" + ], + "-webkit-column-break-before": [ + "left", "right", "auto", "always", "avoid" + ], + "-webkit-column-break-inside": [ + "auto", "avoid" + ], + "-webkit-hyphens": [ + "none", "auto", "manual" + ], + "border-image": [ + "repeat", "stretch", "url()", "linear-gradient()", "radial-gradient()", "repeating-linear-gradient()", "repeating-radial-gradient()", "-webkit-canvas()", "cross-fade()", "image-set()" + ], + "border-image-repeat": [ + "repeat", "stretch", "space", "round" + ], + "-webkit-mask-box-image-repeat": [ + "repeat", "stretch", "space", "round" + ], + "position": [ + "absolute", "fixed", "relative", "static", "-webkit-sticky" + ], + "font-family": [ + "serif", "sans-serif", "cursive", "fantasy", "monospace", "-webkit-body", "-webkit-pictograph", + "-apple-system", "-apple-system-headline", "-apple-system-body", + "-apple-system-subheadline", "-apple-system-footnote", "-apple-system-caption1", "-apple-system-caption2", + "-apple-system-short-headline", "-apple-system-short-body", "-apple-system-short-subheadline", + "-apple-system-short-footnote", "-apple-system-short-caption1", "-apple-system-tall-body", + "-apple-system-title1", "-apple-system-title2", "-apple-system-title3" + ], + "text-overflow-mode": [ + "clip", "ellipsis" + ], + "border-bottom-style": [ + "none", "hidden", "inset", "groove", "ridge", "outset", "dotted", "dashed", "solid", "double" + ], + "unicode-bidi": [ + "normal", "bidi-override", "embed", "-webkit-plaintext", "-webkit-isolate", "-webkit-isolate-override" + ], + "clip-rule": [ + "nonzero", "evenodd" + ], + "margin-left": [ + "auto" + ], + "margin-top": [ + "auto" + ], + "zoom": [ + "normal", "document", "reset" + ], + "z-index": [ + "auto" + ], + "width": [ + "intrinsic", "min-intrinsic", "-webkit-min-content", "-webkit-max-content", "-webkit-fill-available", "-webkit-fit-content", "calc()" + ], + "height": [ + "intrinsic", "min-intrinsic", "calc()" + ], + "max-width": [ + "none", "intrinsic", "min-intrinsic", "-webkit-min-content", "-webkit-max-content", "-webkit-fill-available", "-webkit-fit-content", "calc()" + ], + "min-width": [ + "intrinsic", "min-intrinsic", "-webkit-min-content", "-webkit-max-content", "-webkit-fill-available", "-webkit-fit-content", "calc()" + ], + "max-height": [ + "none", "intrinsic", "min-intrinsic", "calc()" + ], + "min-height": [ + "intrinsic", "min-intrinsic", "calc()" + ], + "-webkit-logical-width": [ + "intrinsic", "min-intrinsic", "-webkit-min-content", "-webkit-max-content", "-webkit-fill-available", "-webkit-fit-content", "calc()" + ], + "-webkit-logical-height": [ + "intrinsic", "min-intrinsic", "calc()" + ], + "-webkit-max-logical-width": [ + "none", "intrinsic", "min-intrinsic", "-webkit-min-content", "-webkit-max-content", "-webkit-fill-available", "-webkit-fit-content", "calc()" + ], + "-webkit-min-logical-width": [ + "intrinsic", "min-intrinsic", "-webkit-min-content", "-webkit-max-content", "-webkit-fill-available", "-webkit-fit-content", "calc()" + ], + "-webkit-max-logical-height": [ + "none", "intrinsic", "min-intrinsic", "calc()" + ], + "-webkit-min-logical-height": [ + "intrinsic", "min-intrinsic", "calc()" + ], + "empty-cells": [ + "hide", "show" + ], + "pointer-events": [ + "none", "all", "auto", "visible", "visiblepainted", "visiblefill", "visiblestroke", "painted", "fill", "stroke" + ], + "letter-spacing": [ + "normal", "calc()" + ], + "word-spacing": [ + "normal", "calc()" + ], + "-webkit-font-kerning": [ + "auto", "normal", "none" + ], + "-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" + ], + "font-variant-numeric": [ + "normal", "ordinal", "slashed-zero", "lining-nums", "oldstyle-nums", "proportional-nums", "tabular-nums", + "diagonal-fractions", "stacked-fractions" + ], + "vertical-align": [ + "baseline", "middle", "sub", "super", "text-top", "text-bottom", "top", "bottom", "-webkit-baseline-middle" + ], + "white-space": [ + "normal", "nowrap", "pre", "pre-line", "pre-wrap" + ], + "word-break": [ + "normal", "break-all", "break-word" + ], + "text-underline-width": [ + "normal", "medium", "auto", "thick", "thin", "calc()" + ], + "text-indent": [ + "-webkit-each-line", "-webkit-hanging" + ], + "-webkit-box-lines": [ + "single", "multiple" + ], + "clip": [ + "auto", "rect()" + ], + "clip-path": [ + "none", "url()", "circle()", "ellipse()", "inset()", "polygon()", "margin-box", "border-box", "padding-box", "content-box" + ], + "shape-outside": [ + "none", "url()", "circle()", "ellipse()", "inset()", "polygon()", "margin-box", "border-box", "padding-box", "content-box" + ], + "orphans": [ + "auto" + ], + "widows": [ + "auto" + ], + "margin": [ + "auto" + ], + "page": [ + "auto" + ], + "perspective": [ + "none" + ], + "perspective-origin": [ + "none", "left", "right", "bottom", "top", "center" + ], + "-webkit-marquee-increment": [ + "small", "large", "medium" + ], + "-webkit-marquee-direction": [ + "left", "right", "auto", "reverse", "forwards", "backwards", "ahead", "up", "down" + ], + "-webkit-marquee-style": [ + "none", "scroll", "slide", "alternate" + ], + "-webkit-marquee-repetition": [ + "infinite" + ], + "-webkit-marquee-speed": [ + "normal", "slow", "fast" + ], + "margin-right": [ + "auto" + ], + "marquee-speed": [ + "normal", "slow", "fast" + ], + "-webkit-text-emphasis": [ + "circle", "filled", "open", "dot", "double-circle", "triangle", "sesame" + ], + "-webkit-text-emphasis-style": [ + "circle", "filled", "open", "dot", "double-circle", "triangle", "sesame" + ], + "-webkit-text-emphasis-position": [ + "over", "under", "left", "right" + ], + "transform": [ + "none", + "scale()", "scaleX()", "scaleY()", "scale3d()", "rotate()", "rotateX()", "rotateY()", "rotateZ()", "rotate3d()", "skew()", "skewX()", "skewY()", + "translate()", "translateX()", "translateY()", "translateZ()", "translate3d()", "matrix()", "matrix3d()", "perspective()" + ], + "transform-style": [ + "flat", "preserve-3d" + ], + "-webkit-cursor-visibility": [ + "auto", "auto-hide" + ], + "text-decoration": [ + "none", "underline", "overline", "line-through", "blink" + ], + "-webkit-text-decorations-in-effect": [ + "none", "underline", "overline", "line-through", "blink" + ], + "-webkit-text-decoration-line": [ + "none", "underline", "overline", "line-through", "blink" + ], + "-webkit-text-decoration-style": [ + "solid", "double", "dotted", "dashed", "wavy" + ], + "-webkit-text-decoration-skip": [ + "auto", "none", "objects", "ink" + ], + "-webkit-text-underline-position": [ + "auto", "alphabetic", "under" + ], + "image-resolution": [ + "from-image", "snap" + ], + "-webkit-blend-mode": [ + "normal", "multiply", "screen", "overlay", "darken", "lighten", "color-dodge", "color-burn", "hard-light", "soft-light", "difference", "exclusion", "plus-darker", "plus-lighter", "hue", "saturation", "color", "luminosity", + ], + "mix-blend-mode": [ + "normal", "multiply", "screen", "overlay", "darken", "lighten", "color-dodge", "color-burn", "hard-light", "soft-light", "difference", "exclusion", "plus-darker", "plus-lighter", "hue", "saturation", "color", "luminosity", + ], + "mix": [ + "auto", + "normal", "multiply", "screen", "overlay", "darken", "lighten", "color-dodge", "color-burn", "hard-light", "soft-light", "difference", "exclusion", "plus-darker", "plus-lighter", "hue", "saturation", "color", "luminosity", + "clear", "copy", "destination", "source-over", "destination-over", "source-in", "destination-in", "source-out", "destination-out", "source-atop", "destination-atop", "xor" + ], + "geometry": [ + "detached", "attached", "grid()" + ], + "overflow-wrap": [ + "normal", "break-word" + ], + "transition": [ + "none", "ease", "linear", "ease-in", "ease-out", "ease-in-out", "step-start", "step-end", "steps()", "cubic-bezier()", "spring()", "all", WebInspector.CSSKeywordCompletions.AllPropertyNamesPlaceholder + ], + "transition-timing-function": [ + "ease", "linear", "ease-in", "ease-out", "ease-in-out", "step-start", "step-end", "steps()", "cubic-bezier()", "spring()" + ], + "transition-property": [ + "all", "none", WebInspector.CSSKeywordCompletions.AllPropertyNamesPlaceholder + ], + "-webkit-column-progression": [ + "normal", "reverse" + ], + "-webkit-box-decoration-break": [ + "slice", "clone" + ], + "align-content": [ + "auto", + "baseline", "last-baseline", + "space-between", "space-around", "space-evenly", "stretch", + "center", "start", "end", "flex-start", "flex-end", "left", "right", + "true", "safe" + ], + "justify-content": [ + "auto", + "baseline", "last-baseline", "space-between", "space-around", "space-evenly", "stretch", + "center", "start", "end", "flex-start", "flex-end", "left", "right", + "true", "safe" + ], + "align-items": [ + "auto", "stretch", + "baseline", "last-baseline", + "center", "start", "end", "self-start", "self-end", "flex-start", "flex-end", "left", "right", + "true", "safe" + ], + "align-self": [ + "auto", "stretch", + "baseline", "last-baseline", + "center", "start", "end", "self-start", "self-end", "flex-start", "flex-end", "left", "right", + "true", "safe" + ], + "justify-items": [ + "auto", "stretch", + "baseline", "last-baseline", + "center", "start", "end", "self-start", "self-end", "flex-start", "flex-end", "left", "right", + "true", "safe" + ], + "justify-self": [ + "auto", "stretch", + "baseline", "last-baseline", + "center", "start", "end", "self-start", "self-end", "flex-start", "flex-end", "left", "right", + "true", "safe" + ], + "flex-direction": [ + "row", "row-reverse", "column", "column-reverse" + ], + "flex-wrap": [ + "nowrap", "wrap", "wrap-reverse" + ], + "flex-flow": [ + "row", "row-reverse", "column", "column-reverse", + "nowrap", "wrap", "wrap-reverse" + ], + "flex": [ + "none" + ], + "flex-basis": [ + "auto" + ], + "grid": [ + "none" + ], + "grid-area": [ + "auto" + ], + "grid-auto-columns": [ + "auto", "-webkit-max-content", "-webkit-min-content", "minmax()", + ], + "grid-auto-flow": [ + "row", "column", "dense" + ], + "grid-auto-rows": [ + "auto", "-webkit-max-content", "-webkit-min-content", "minmax()", + ], + "grid-column": [ + "auto" + ], + "grid-column-start": [ + "auto" + ], + "grid-column-end": [ + "auto" + ], + "grid-row": [ + "auto" + ], + "grid-row-start": [ + "auto" + ], + "grid-row-end": [ + "auto" + ], + "grid-template": [ + "none" + ], + "grid-template-areas": [ + "none" + ], + "grid-template-columns": [ + "none", "auto", "-webkit-max-content", "-webkit-min-content", "minmax()", "repeat()" + ], + "grid-template-rows": [ + "none", "auto", "-webkit-max-content", "-webkit-min-content", "minmax()", "repeat()" + ], + "-webkit-ruby-position": [ + "after", "before", "inter-character" + ], + "-webkit-text-align-last": [ + "auto", "start", "end", "left", "right", "center", "justify" + ], + "-webkit-text-justify": [ + "auto", "none", "inter-word", "inter-ideograph", "inter-cluster", "distribute", "kashida" + ], + "max-zoom": [ + "auto" + ], + "min-zoom": [ + "auto" + ], + "orientation": [ + "auto", "portait", "landscape" + ], + "scroll-snap-align": [ + "none", "start", "center", "end" + ], + "scroll-snap-type": [ + "none", "mandatory", "proximity", "x", "y", "inline", "block", "both" + ], + "user-zoom": [ + "zoom", "fixed" + ], + "-webkit-app-region": [ + "drag", "no-drag" + ], + "-webkit-line-break": [ + "auto", "loose", "normal", "strict", "after-white-space" + ], + "-webkit-background-composite": [ + "clear", "copy", "source-over", "source-in", "source-out", "source-atop", "destination-over", "destination-in", "destination-out", "destination-atop", "xor", "plus-darker", "plus-lighter" + ], + "-webkit-mask-composite": [ + "clear", "copy", "source-over", "source-in", "source-out", "source-atop", "destination-over", "destination-in", "destination-out", "destination-atop", "xor", "plus-darker", "plus-lighter" + ], + "-webkit-animation-direction": [ + "normal", "alternate", "reverse", "alternate-reverse" + ], + "-webkit-animation-fill-mode": [ + "none", "forwards", "backwards", "both" + ], + "-webkit-animation-iteration-count": [ + "infinite" + ], + "-webkit-animation-play-state": [ + "paused", "running" + ], + "-webkit-animation-timing-function": [ + "ease", "linear", "ease-in", "ease-out", "ease-in-out", "step-start", "step-end", "steps()", "cubic-bezier()", "spring()" + ], + "-webkit-column-span": [ + "all", "none", "calc()" + ], + "-webkit-region-break-after": [ + "auto", "always", "avoid", "left", "right" + ], + "-webkit-region-break-before": [ + "auto", "always", "avoid", "left", "right" + ], + "-webkit-region-break-inside": [ + "auto", "avoid" + ], + "-webkit-region-overflow": [ + "auto", "break" + ], + "-webkit-backface-visibility": [ + "visible", "hidden" + ], + "resize": [ + "none", "both", "horizontal", "vertical", "auto" + ], + "caption-side": [ + "top", "bottom", "left", "right" + ], + "box-sizing": [ + "border-box", "content-box" + ], + "-webkit-alt": [ + "attr()" + ], + "-webkit-border-fit": [ + "border", "lines" + ], + "-webkit-line-align": [ + "none", "edges" + ], + "-webkit-line-snap": [ + "none", "baseline", "contain" + ], + "-webkit-nbsp-mode": [ + "normal", "space" + ], + "-webkit-print-color-adjust": [ + "exact", "economy" + ], + "-webkit-rtl-ordering": [ + "logical", "visual" + ], + "-webkit-text-security": [ + "disc", "circle", "square", "none" + ], + "-webkit-user-drag": [ + "auto", "none", "element" + ], + "-webkit-user-modify": [ + "read-only", "read-write", "read-write-plaintext-only" + ], + "-webkit-user-select": [ + "auto", "none", "text", "all" + ], + "-webkit-text-stroke-width": [ + "medium", "thick", "thin", "calc()" + ], + "-webkit-border-start-width": [ + "medium", "thick", "thin", "calc()" + ], + "-webkit-border-end-width": [ + "medium", "thick", "thin", "calc()" + ], + "-webkit-border-before-width": [ + "medium", "thick", "thin", "calc()" + ], + "-webkit-border-after-width": [ + "medium", "thick", "thin", "calc()" + ], + "-webkit-column-rule-width": [ + "medium", "thick", "thin", "calc()" + ], + "-webkit-aspect-ratio": [ + "auto", "from-dimensions", "from-intrinsic", "/" + ], + "filter": [ + "none", "grayscale()", "sepia()", "saturate()", "hue-rotate()", "invert()", "opacity()", "brightness()", "contrast()", "blur()", "drop-shadow()", "custom()" + ], + "-webkit-backdrop-filter": [ + "none", "grayscale()", "sepia()", "saturate()", "hue-rotate()", "invert()", "opacity()", "brightness()", "contrast()", "blur()", "drop-shadow()", "custom()" + ], + "-webkit-column-count": [ + "auto", "calc()" + ], + "-webkit-column-gap": [ + "normal", "calc()" + ], + "-webkit-column-axis": [ + "horizontal", "vertical", "auto" + ], + "-webkit-column-width": [ + "auto", "calc()" + ], + "-webkit-column-fill": [ + "auto", "balance" + ], + "-webkit-hyphenate-character": [ + "none" + ], + "-webkit-hyphenate-limit-after": [ + "auto" + ], + "-webkit-hyphenate-limit-before": [ + "auto" + ], + "-webkit-hyphenate-limit-lines": [ + "no-limit" + ], + "-webkit-line-grid": [ + "none" + ], + "-webkit-locale": [ + "auto" + ], + "-webkit-text-orientation": [ + "sideways", "sideways-right", "vertical-right", "upright" + ], + "-webkit-line-box-contain": [ + "block", "inline", "font", "glyphs", "replaced", "inline-box", "none" + ], + "font-feature-settings": [ + "normal" + ], + "-webkit-font-variant-ligatures": [ + "normal", "common-ligatures", "no-common-ligatures", "discretionary-ligatures", "no-discretionary-ligatures", "historical-ligatures", "no-historical-ligatures" + ], + /* + "-webkit-appearance": [ + "none", "checkbox", "radio", "push-button", "square-button", "button", "button-bevel", "default-button", "inner-spin-button", "listbox", "listitem", "media-enter-fullscreen-button", "media-exit-fullscreen-button", "media-fullscreen-volume-slider", "media-fullscreen-volume-slider-thumb", "media-mute-button", "media-play-button", "media-overlay-play-button", "media-seek-back-button", "media-seek-forward-button", "media-rewind-button", "media-return-to-realtime-button", "media-toggle-closed-captions-button", "media-slider", "media-sliderthumb", "media-volume-slider-container", "media-volume-slider", "media-volume-sliderthumb", "media-volume-slider-mute-button", "media-controls-background", "media-controls-fullscreen-background", "media-current-time-display", "media-time-remaining-display", "menulist", "menulist-button", "menulist-text", "menulist-textfield", "meter", "progress-bar", "progress-bar-value", "slider-horizontal", "slider-vertical", "sliderthumb-horizontal", "sliderthumb-vertical", "caret", "searchfield", "searchfield-decoration", "searchfield-results-decoration", "searchfield-results-button", "searchfield-cancel-button", "snapshotted-plugin-overlay", "textfield", "relevancy-level-indicator", "continuous-capacity-level-indicator", "discrete-capacity-level-indicator", "rating-level-indicator", "textarea", "attachment", "caps-lock-indicator" + ], + */ + "-webkit-animation-trigger": [ + "auto", "container-scroll()" + ], + + // iOS Properties + "-webkit-text-size-adjust": [ + "none", "auto" + ], + "-webkit-touch-callout": [ + "default", "none" + ], + "-webkit-overflow-scrolling": [ + "auto", "touch" + ] +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/CSSMedia.js b/Source/WebInspectorUI/UserInterface/Models/CSSMedia.js new file mode 100644 index 000000000..2128e8a63 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/CSSMedia.js @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2013 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. + */ + +WebInspector.CSSMedia = class CSSMedia extends WebInspector.Object +{ + constructor(type, text, sourceCodeLocation) + { + super(); + + console.assert(!sourceCodeLocation || sourceCodeLocation instanceof WebInspector.SourceCodeLocation); + + this._type = type || null; + this._text = text || ""; + this._sourceCodeLocation = sourceCodeLocation || null; + } + + // Public + + get type() { return this._type; } + get text() { return this._text; } + get sourceCodeLocation() { return this._sourceCodeLocation; } +}; + +WebInspector.CSSMedia.Type = { + MediaRule: "css-media-type-media-rule", + ImportRule: "css-media-type-import-rule", + LinkedStyleSheet: "css-media-type-linked-stylesheet", + InlineStyleSheet: "css-media-type-inline-stylesheet" +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/CSSProperty.js b/Source/WebInspectorUI/UserInterface/Models/CSSProperty.js new file mode 100644 index 000000000..860f1efff --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/CSSProperty.js @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2013 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. + */ + +WebInspector.CSSProperty = class CSSProperty extends WebInspector.Object +{ + constructor(index, text, name, value, priority, enabled, overridden, implicit, anonymous, valid, styleSheetTextRange) + { + super(); + + this._ownerStyle = null; + this._index = index; + + this.update(text, name, value, priority, enabled, overridden, implicit, anonymous, valid, styleSheetTextRange, true); + } + + // Static + + static isInheritedPropertyName(name) + { + console.assert(typeof name === "string"); + if (name in WebInspector.CSSKeywordCompletions.InheritedProperties) + return true; + // Check if the name is a CSS variable. + return name.startsWith("--"); + } + + // Public + + get ownerStyle() + { + return this._ownerStyle; + } + + set ownerStyle(ownerStyle) + { + this._ownerStyle = ownerStyle || null; + } + + get index() + { + return this._index; + } + + set index(index) + { + this._index = index; + } + + update(text, name, value, priority, enabled, overridden, implicit, anonymous, valid, styleSheetTextRange, dontFireEvents) + { + text = text || ""; + name = name || ""; + value = value || ""; + priority = priority || ""; + enabled = enabled || false; + overridden = overridden || false; + implicit = implicit || false; + anonymous = anonymous || false; + valid = valid || false; + + var changed = false; + + if (!dontFireEvents) { + changed = this._name !== name || this._value !== value || this._priority !== priority || + this._enabled !== enabled || this._implicit !== implicit || this._anonymous !== anonymous || this._valid !== valid; + } + + // Use the setter for overridden if we want to fire events since the + // OverriddenStatusChanged event coalesces changes before it fires. + if (!dontFireEvents) + this.overridden = overridden; + else + this._overridden = overridden; + + this._text = text; + this._name = name; + this._value = value; + this._priority = priority; + this._enabled = enabled; + this._implicit = implicit; + this._anonymous = anonymous; + this._inherited = WebInspector.CSSProperty.isInheritedPropertyName(name); + this._valid = valid; + this._variable = name.startsWith("--"); + this._styleSheetTextRange = styleSheetTextRange || null; + + this._relatedShorthandProperty = null; + this._relatedLonghandProperties = []; + + // Clear computed properties. + delete this._styleDeclarationTextRange; + delete this._canonicalName; + delete this._hasOtherVendorNameOrKeyword; + + if (changed) + this.dispatchEventToListeners(WebInspector.CSSProperty.Event.Changed); + } + + get synthesizedText() + { + var name = this.name; + if (!name) + return ""; + + var priority = this.priority; + return name + ": " + this.value.trim() + (priority ? " !" + priority : "") + ";"; + } + + get text() + { + return this._text || this.synthesizedText; + } + + get name() { return this._name; } + + get canonicalName() + { + if (this._canonicalName) + return this._canonicalName; + + this._canonicalName = WebInspector.cssStyleManager.canonicalNameForPropertyName(this.name); + + return this._canonicalName; + } + + get value() { return this._value; } + + get important() + { + return this.priority === "important"; + } + + get priority() { return this._priority; } + + get enabled() + { + return this._enabled && this._ownerStyle && (!isNaN(this._index) || this._ownerStyle.type === WebInspector.CSSStyleDeclaration.Type.Computed); + } + + get overridden() { return this._overridden; } + set overridden(overridden) + { + overridden = overridden || false; + + if (this._overridden === overridden) + return; + + var previousOverridden = this._overridden; + + this._overridden = overridden; + + if (this._overriddenStatusChangedTimeout) + return; + + function delayed() + { + delete this._overriddenStatusChangedTimeout; + + if (this._overridden === previousOverridden) + return; + + this.dispatchEventToListeners(WebInspector.CSSProperty.Event.OverriddenStatusChanged); + } + + this._overriddenStatusChangedTimeout = setTimeout(delayed.bind(this), 0); + } + + get implicit() { return this._implicit; } + set implicit(implicit) { this._implicit = implicit; } + + get anonymous() { return this._anonymous; } + get inherited() { return this._inherited; } + get valid() { return this._valid; } + get variable() { return this._variable; } + get styleSheetTextRange() { return this._styleSheetTextRange; } + + get styleDeclarationTextRange() + { + if ("_styleDeclarationTextRange" in this) + return this._styleDeclarationTextRange; + + if (!this._ownerStyle || !this._styleSheetTextRange) + return null; + + var styleTextRange = this._ownerStyle.styleSheetTextRange; + if (!styleTextRange) + return null; + + var startLine = this._styleSheetTextRange.startLine - styleTextRange.startLine; + var endLine = this._styleSheetTextRange.endLine - styleTextRange.startLine; + + var startColumn = this._styleSheetTextRange.startColumn; + if (!startLine) + startColumn -= styleTextRange.startColumn; + + var endColumn = this._styleSheetTextRange.endColumn; + if (!endLine) + endColumn -= styleTextRange.startColumn; + + this._styleDeclarationTextRange = new WebInspector.TextRange(startLine, startColumn, endLine, endColumn); + + return this._styleDeclarationTextRange; + } + + get relatedShorthandProperty() { return this._relatedShorthandProperty; } + set relatedShorthandProperty(property) + { + this._relatedShorthandProperty = property || null; + } + + get relatedLonghandProperties() { return this._relatedLonghandProperties; } + + addRelatedLonghandProperty(property) + { + this._relatedLonghandProperties.push(property); + } + + clearRelatedLonghandProperties(property) + { + this._relatedLonghandProperties = []; + } + + hasOtherVendorNameOrKeyword() + { + if ("_hasOtherVendorNameOrKeyword" in this) + return this._hasOtherVendorNameOrKeyword; + + this._hasOtherVendorNameOrKeyword = WebInspector.cssStyleManager.propertyNameHasOtherVendorPrefix(this.name) || WebInspector.cssStyleManager.propertyValueHasOtherVendorKeyword(this.value); + + return this._hasOtherVendorNameOrKeyword; + } +}; + +WebInspector.CSSProperty.Event = { + Changed: "css-property-changed", + OverriddenStatusChanged: "css-property-overridden-status-changed" +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/CSSRule.js b/Source/WebInspectorUI/UserInterface/Models/CSSRule.js new file mode 100644 index 000000000..b20a96598 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/CSSRule.js @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2013 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. + */ + +WebInspector.CSSRule = class CSSRule extends WebInspector.Object +{ + constructor(nodeStyles, ownerStyleSheet, id, type, sourceCodeLocation, selectorText, selectors, matchedSelectorIndices, style, mediaList) + { + super(); + + console.assert(nodeStyles); + this._nodeStyles = nodeStyles; + + this._ownerStyleSheet = ownerStyleSheet || null; + this._id = id || null; + this._type = type || null; + + this.update(sourceCodeLocation, selectorText, selectors, matchedSelectorIndices, style, mediaList, true); + } + + // Public + + get id() + { + return this._id; + } + + get ownerStyleSheet() + { + return this._ownerStyleSheet; + } + + get editable() + { + return !!this._id && (this._type === WebInspector.CSSStyleSheet.Type.Author || this._type === WebInspector.CSSStyleSheet.Type.Inspector); + } + + update(sourceCodeLocation, selectorText, selectors, matchedSelectorIndices, style, mediaList, dontFireEvents) + { + sourceCodeLocation = sourceCodeLocation || null; + selectorText = selectorText || ""; + selectors = selectors || []; + matchedSelectorIndices = matchedSelectorIndices || []; + style = style || null; + mediaList = mediaList || []; + + var changed = false; + if (!dontFireEvents) { + changed = this._selectorText !== selectorText || !Array.shallowEqual(this._selectors, selectors) || + !Array.shallowEqual(this._matchedSelectorIndices, matchedSelectorIndices) || this._style !== style || + !!this._sourceCodeLocation !== !!sourceCodeLocation || this._mediaList.length !== mediaList.length; + // FIXME: Look for differences in the media list arrays. + } + + if (this._style) + this._style.ownerRule = null; + + this._sourceCodeLocation = sourceCodeLocation; + this._selectorText = selectorText; + this._selectors = selectors; + this._matchedSelectorIndices = matchedSelectorIndices; + this._mostSpecificSelector = null; + this._style = style; + this._mediaList = mediaList; + + this._matchedSelectors = null; + this._matchedSelectorText = null; + + if (this._style) + this._style.ownerRule = this; + + if (changed) + this.dispatchEventToListeners(WebInspector.CSSRule.Event.Changed); + } + + get type() + { + return this._type; + } + + get sourceCodeLocation() + { + return this._sourceCodeLocation; + } + + get selectorText() + { + return this._selectorText; + } + + set selectorText(selectorText) + { + console.assert(this.editable); + if (!this.editable) + return; + + if (this._selectorText === selectorText) { + this._selectorResolved(true); + return; + } + + this._nodeStyles.changeRuleSelector(this, selectorText).then(this._selectorResolved.bind(this), this._selectorRejected.bind(this)); + } + + get selectors() + { + return this._selectors; + } + + get matchedSelectorIndices() + { + return this._matchedSelectorIndices; + } + + get matchedSelectors() + { + if (this._matchedSelectors) + return this._matchedSelectors; + + this._matchedSelectors = this._selectors.filter(function(element, index) { + return this._matchedSelectorIndices.includes(index); + }, this); + + return this._matchedSelectors; + } + + get matchedSelectorText() + { + if ("_matchedSelectorText" in this) + return this._matchedSelectorText; + + this._matchedSelectorText = this.matchedSelectors.map(function(x) { return x.text; }).join(", "); + + return this._matchedSelectorText; + } + + hasMatchedPseudoElementSelector() + { + if (this.nodeStyles && this.nodeStyles.node && this.nodeStyles.node.isPseudoElement()) + return true; + + return this.matchedSelectors.some((selector) => selector.isPseudoElementSelector()); + } + + get style() + { + return this._style; + } + + get mediaList() + { + return this._mediaList; + } + + get mediaText() + { + if (!this._mediaList.length) + return ""; + + let mediaText = ""; + for (let media of this._mediaList) + mediaText += media.text; + + return mediaText; + } + + isEqualTo(rule) + { + if (!rule) + return false; + + return Object.shallowEqual(this._id, rule.id); + } + + get mostSpecificSelector() + { + if (!this._mostSpecificSelector) + this._mostSpecificSelector = this._determineMostSpecificSelector(); + + return this._mostSpecificSelector; + } + + selectorIsGreater(otherSelector) + { + var mostSpecificSelector = this.mostSpecificSelector; + + if (!mostSpecificSelector) + return false; + + return mostSpecificSelector.isGreaterThan(otherSelector); + } + + // Protected + + get nodeStyles() + { + return this._nodeStyles; + } + + // Private + + _determineMostSpecificSelector() + { + if (!this._selectors || !this._selectors.length) + return null; + + var selectors = this.matchedSelectors; + + if (!selectors.length) + selectors = this._selectors; + + var specificSelector = selectors[0]; + + for (var selector of selectors) { + if (selector.isGreaterThan(specificSelector)) + specificSelector = selector; + } + + return specificSelector; + } + + _selectorRejected(error) + { + this.dispatchEventToListeners(WebInspector.CSSRule.Event.SelectorChanged, {valid: !error}); + } + + _selectorResolved(rulePayload) + { + this.dispatchEventToListeners(WebInspector.CSSRule.Event.SelectorChanged, {valid: !!rulePayload}); + } +}; + +WebInspector.CSSRule.Event = { + Changed: "css-rule-changed", + SelectorChanged: "css-rule-invalid-selector" +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/CSSSelector.js b/Source/WebInspectorUI/UserInterface/Models/CSSSelector.js new file mode 100644 index 000000000..892e446c3 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/CSSSelector.js @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2014 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. + */ + +WebInspector.CSSSelector = class CSSSelector extends WebInspector.Object +{ + constructor(text, specificity, dynamic) + { + super(); + + console.assert(text); + + this._text = text; + this._specificity = specificity || null; + this._dynamic = dynamic || false; + } + + // Public + + get text() { return this._text; } + get specificity() { return this._specificity; } + get dynamic() { return this._dynamic; } + + isGreaterThan(selector) + { + if (!selector || !selector.specificity) + return true; + + for (var i = 0; i < this._specificity.length; ++i) { + if (this._specificity[i] === selector.specificity[i]) + continue; + + return this._specificity[i] > selector.specificity[i]; + } + + return false; + } + + isPseudoElementSelector() + { + return WebInspector.CSSStyleManager.PseudoElementNames.some((name) => this._text.includes(`:${name}`)); + } +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/CSSStyleDeclaration.js b/Source/WebInspectorUI/UserInterface/Models/CSSStyleDeclaration.js new file mode 100644 index 000000000..8a7ba5f8f --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/CSSStyleDeclaration.js @@ -0,0 +1,362 @@ +/* + * Copyright (C) 2013 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. + */ + +WebInspector.CSSStyleDeclaration = class CSSStyleDeclaration extends WebInspector.Object +{ + constructor(nodeStyles, ownerStyleSheet, id, type, node, inherited, text, properties, styleSheetTextRange) + { + super(); + + console.assert(nodeStyles); + this._nodeStyles = nodeStyles; + + this._ownerRule = null; + + this._ownerStyleSheet = ownerStyleSheet || null; + this._id = id || null; + this._type = type || null; + this._node = node || null; + this._inherited = inherited || false; + + this._pendingProperties = []; + this._propertyNameMap = {}; + + this._initialText = text; + this._hasModifiedInitialText = false; + + this.update(text, properties, styleSheetTextRange, true); + } + + // Public + + get id() + { + return this._id; + } + + get ownerStyleSheet() + { + return this._ownerStyleSheet; + } + + get type() + { + return this._type; + } + + get inherited() + { + return this._inherited; + } + + get node() + { + return this._node; + } + + get editable() + { + if (!this._id) + return false; + + if (this._type === WebInspector.CSSStyleDeclaration.Type.Rule) + return this._ownerRule && this._ownerRule.editable; + + if (this._type === WebInspector.CSSStyleDeclaration.Type.Inline) + return !this._node.isInUserAgentShadowTree(); + + return false; + } + + update(text, properties, styleSheetTextRange, dontFireEvents) + { + text = text || ""; + properties = properties || []; + + var oldProperties = this._properties || []; + var oldText = this._text; + + this._text = text; + this._properties = properties; + this._styleSheetTextRange = styleSheetTextRange; + this._propertyNameMap = {}; + + delete this._visibleProperties; + + var editable = this.editable; + + for (var i = 0; i < this._properties.length; ++i) { + var property = this._properties[i]; + property.ownerStyle = this; + + // Store the property in a map if we arn't editable. This + // allows for quick lookup for computed style. Editable + // styles don't use the map since they need to account for + // overridden properties. + if (!editable) + this._propertyNameMap[property.name] = property; + else { + // Remove from pendingProperties (if it was pending). + this._pendingProperties.remove(property); + } + } + + var removedProperties = []; + for (var i = 0; i < oldProperties.length; ++i) { + var oldProperty = oldProperties[i]; + + if (!this._properties.includes(oldProperty)) { + // Clear the index, since it is no longer valid. + oldProperty.index = NaN; + + removedProperties.push(oldProperty); + + // Keep around old properties in pending in case they + // are needed again during editing. + if (editable) + this._pendingProperties.push(oldProperty); + } + } + + if (dontFireEvents) + return; + + var addedProperties = []; + for (var i = 0; i < this._properties.length; ++i) { + if (!oldProperties.includes(this._properties[i])) + addedProperties.push(this._properties[i]); + } + + // Don't fire the event if there is text and it hasn't changed. + if (oldText && this._text && oldText === this._text) { + // We shouldn't have any added or removed properties in this case. + console.assert(!addedProperties.length && !removedProperties.length); + if (!addedProperties.length && !removedProperties.length) + return; + } + + function delayed() + { + this.dispatchEventToListeners(WebInspector.CSSStyleDeclaration.Event.PropertiesChanged, {addedProperties, removedProperties}); + } + + // Delay firing the PropertiesChanged event so DOMNodeStyles has a chance to mark overridden and associated properties. + setTimeout(delayed.bind(this), 0); + } + + get ownerRule() + { + return this._ownerRule; + } + + set ownerRule(rule) + { + this._ownerRule = rule || null; + } + + get text() + { + return this._text; + } + + set text(text) + { + if (this._text === text) + return; + + let trimmedText = WebInspector.CSSStyleDeclarationTextEditor.PrefixWhitespace + text.trim(); + if (this._text === trimmedText) + return; + + if (trimmedText === WebInspector.CSSStyleDeclarationTextEditor.PrefixWhitespace || this._type === WebInspector.CSSStyleDeclaration.Type.Inline) + text = trimmedText; + + let modified = text !== this._initialText; + if (modified !== this._hasModifiedInitialText) { + this._hasModifiedInitialText = modified; + this.dispatchEventToListeners(WebInspector.CSSStyleDeclaration.Event.InitialTextModified); + } + + this._nodeStyles.changeStyleText(this, text); + } + + resetText() + { + this.text = this._initialText; + } + + get modified() + { + return this._hasModifiedInitialText; + } + + get properties() + { + return this._properties; + } + + get visibleProperties() + { + if (this._visibleProperties) + return this._visibleProperties; + + this._visibleProperties = this._properties.filter(function(property) { + return !!property.styleDeclarationTextRange; + }); + + return this._visibleProperties; + } + + get pendingProperties() + { + return this._pendingProperties; + } + + get styleSheetTextRange() + { + return this._styleSheetTextRange; + } + + get mediaList() + { + if (this._ownerRule) + return this._ownerRule.mediaList; + return []; + } + + get selectorText() + { + if (this._ownerRule) + return this._ownerRule.selectorText; + return this._node.appropriateSelectorFor(true); + } + + propertyForName(name, dontCreateIfMissing) + { + console.assert(name); + if (!name) + return null; + + if (!this.editable) + return this._propertyNameMap[name] || null; + + // Editable styles don't use the map since they need to + // account for overridden properties. + + function findMatch(properties) + { + for (var i = 0; i < properties.length; ++i) { + var property = properties[i]; + if (property.canonicalName !== name && property.name !== name) + continue; + if (bestMatchProperty && !bestMatchProperty.overridden && property.overridden) + continue; + bestMatchProperty = property; + } + } + + var bestMatchProperty = null; + + findMatch(this._properties); + + if (bestMatchProperty) + return bestMatchProperty; + + if (dontCreateIfMissing || !this.editable) + return null; + + findMatch(this._pendingProperties, true); + + if (bestMatchProperty) + return bestMatchProperty; + + var newProperty = new WebInspector.CSSProperty(NaN, null, name); + newProperty.ownerStyle = this; + + this._pendingProperties.push(newProperty); + + return newProperty; + } + + generateCSSRuleString() + { + let indentString = WebInspector.indentString(); + let styleText = ""; + let mediaList = this.mediaList; + let mediaQueriesCount = mediaList.length; + for (let i = mediaQueriesCount - 1; i >= 0; --i) + styleText += indentString.repeat(mediaQueriesCount - i - 1) + "@media " + mediaList[i].text + " {\n"; + + styleText += indentString.repeat(mediaQueriesCount) + this.selectorText + " {\n"; + + for (let property of this._properties) { + if (property.anonymous) + continue; + + styleText += indentString.repeat(mediaQueriesCount + 1) + property.text.trim(); + + if (!styleText.endsWith(";")) + styleText += ";"; + + styleText += "\n"; + } + + for (let i = mediaQueriesCount; i > 0; --i) + styleText += indentString.repeat(i) + "}\n"; + + styleText += "}"; + + return styleText; + } + + isInspectorRule() + { + return this._ownerRule && this._ownerRule.type === WebInspector.CSSStyleSheet.Type.Inspector; + } + + hasProperties() + { + return !!this._properties.length; + } + + // Protected + + get nodeStyles() + { + return this._nodeStyles; + } +}; + +WebInspector.CSSStyleDeclaration.Event = { + PropertiesChanged: "css-style-declaration-properties-changed", + InitialTextModified: "css-style-declaration-initial-text-modified" +}; + +WebInspector.CSSStyleDeclaration.Type = { + Rule: "css-style-declaration-type-rule", + Inline: "css-style-declaration-type-inline", + Attribute: "css-style-declaration-type-attribute", + Computed: "css-style-declaration-type-computed" +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/CSSStyleSheet.js b/Source/WebInspectorUI/UserInterface/Models/CSSStyleSheet.js new file mode 100644 index 000000000..4a64fe43b --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/CSSStyleSheet.js @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2013 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. + */ + +WebInspector.CSSStyleSheet = class CSSStyleSheet extends WebInspector.SourceCode +{ + constructor(id) + { + super(); + + console.assert(id); + + this._id = id || null; + this._url = null; + this._parentFrame = null; + this._origin = null; + this._startLineNumber = 0; + this._startColumnNumber = 0; + + this._inlineStyleAttribute = false; + this._inlineStyleTag = false; + + this._hasInfo = false; + } + + // Static + + static resetUniqueDisplayNameNumbers() + { + WebInspector.CSSStyleSheet._nextUniqueDisplayNameNumber = 1; + } + + // Public + + get id() + { + return this._id; + } + + get parentFrame() + { + return this._parentFrame; + } + + get origin() + { + return this._origin; + } + + get url() + { + return this._url; + } + + get urlComponents() + { + if (!this._urlComponents) + this._urlComponents = parseURL(this._url); + return this._urlComponents; + } + + get mimeType() + { + return "text/css"; + } + + get displayName() + { + if (this._url) + return WebInspector.displayNameForURL(this._url, this.urlComponents); + + // Assign a unique number to the StyleSheet object so it will stay the same. + if (!this._uniqueDisplayNameNumber) + this._uniqueDisplayNameNumber = this.constructor._nextUniqueDisplayNameNumber++; + + return WebInspector.UIString("Anonymous StyleSheet %d").format(this._uniqueDisplayNameNumber); + } + + get startLineNumber() + { + return this._startLineNumber; + } + + get startColumnNumber() + { + return this._startColumnNumber; + } + + hasInfo() + { + return this._hasInfo; + } + + isInspectorStyleSheet() + { + return this._origin === WebInspector.CSSStyleSheet.Type.Inspector; + } + + isInlineStyleTag() + { + return this._inlineStyleTag; + } + + isInlineStyleAttributeStyleSheet() + { + return this._inlineStyleAttribute; + } + + markAsInlineStyleAttributeStyleSheet() + { + this._inlineStyleAttribute = true; + } + + offsetSourceCodeLocation(sourceCodeLocation) + { + if (!sourceCodeLocation) + return null; + + if (!this._hasInfo) + return sourceCodeLocation; + + let sourceCode = sourceCodeLocation.sourceCode; + let lineNumber = this._startLineNumber + sourceCodeLocation.lineNumber; + let columnNumber = this._startColumnNumber + sourceCodeLocation.columnNumber; + return sourceCode.createSourceCodeLocation(lineNumber, columnNumber); + } + + // Protected + + updateInfo(url, parentFrame, origin, inlineStyle, startLineNumber, startColumnNumber) + { + this._hasInfo = true; + + this._url = url || null; + this._urlComponents = undefined; + + this._parentFrame = parentFrame || null; + this._origin = origin; + + this._inlineStyleTag = inlineStyle; + this._startLineNumber = startLineNumber; + this._startColumnNumber = startColumnNumber; + } + + get revisionForRequestedContent() + { + return this.currentRevision; + } + + handleCurrentRevisionContentChange() + { + if (!this._id) + return; + + function contentDidChange(error) + { + if (error) + return; + + DOMAgent.markUndoableState(); + + this.dispatchEventToListeners(WebInspector.CSSStyleSheet.Event.ContentDidChange); + } + + this._ignoreNextContentDidChangeNotification = true; + + CSSAgent.setStyleSheetText(this._id, this.currentRevision.content, contentDidChange.bind(this)); + } + + requestContentFromBackend() + { + if (!this._id) { + // There is no identifier to request content with. Reject the promise to cause the + // pending callbacks to get null content. + return Promise.reject(new Error("There is no identifier to request content with.")); + } + + return CSSAgent.getStyleSheetText(this._id); + } + + noteContentDidChange() + { + if (this._ignoreNextContentDidChangeNotification) { + this._ignoreNextContentDidChangeNotification = false; + return false; + } + + this.markContentAsStale(); + this.dispatchEventToListeners(WebInspector.CSSStyleSheet.Event.ContentDidChange); + return true; + } +}; + +WebInspector.CSSStyleSheet._nextUniqueDisplayNameNumber = 1; + +WebInspector.CSSStyleSheet.Event = { + ContentDidChange: "stylesheet-content-did-change" +}; + +WebInspector.CSSStyleSheet.Type = { + Author: "css-stylesheet-type-author", + User: "css-stylesheet-type-user", + UserAgent: "css-stylesheet-type-user-agent", + Inspector: "css-stylesheet-type-inspector" +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/CallFrame.js b/Source/WebInspectorUI/UserInterface/Models/CallFrame.js new file mode 100644 index 000000000..67778a6bc --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/CallFrame.js @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2013 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. + */ + +WebInspector.CallFrame = class CallFrame extends WebInspector.Object +{ + constructor(target, id, sourceCodeLocation, functionName, thisObject, scopeChain, nativeCode, programCode, isTailDeleted) + { + super(); + + console.assert(target instanceof WebInspector.Target); + console.assert(!sourceCodeLocation || sourceCodeLocation instanceof WebInspector.SourceCodeLocation); + console.assert(!thisObject || thisObject instanceof WebInspector.RemoteObject); + console.assert(!scopeChain || scopeChain instanceof Array); + + this._target = target; + this._id = id || null; + this._sourceCodeLocation = sourceCodeLocation || null; + this._functionName = functionName || ""; + this._thisObject = thisObject || null; + this._scopeChain = scopeChain || []; + this._nativeCode = nativeCode || false; + this._programCode = programCode || false; + this._isTailDeleted = isTailDeleted || false; + } + + // Public + + get target() { return this._target; } + get id() { return this._id; } + get sourceCodeLocation() { return this._sourceCodeLocation; } + get functionName() { return this._functionName; } + get nativeCode() { return this._nativeCode; } + get programCode() { return this._programCode; } + get thisObject() { return this._thisObject; } + get scopeChain() { return this._scopeChain; } + get isTailDeleted() { return this._isTailDeleted; } + + saveIdentityToCookie() + { + // Do nothing. The call frame is torn down when the inspector closes, and + // we shouldn't restore call frame content views across debugger pauses. + } + + collectScopeChainVariableNames(callback) + { + var result = {this: true, __proto__: null}; + + var pendingRequests = this._scopeChain.length; + + function propertiesCollected(properties) + { + for (var i = 0; properties && i < properties.length; ++i) + result[properties[i].name] = true; + + if (--pendingRequests) + return; + + callback(result); + } + + for (var i = 0; i < this._scopeChain.length; ++i) + this._scopeChain[i].objects[0].deprecatedGetAllProperties(propertiesCollected); + } + + mergedScopeChain() + { + let mergedScopes = []; + + // Scopes list goes from top/local (1) to bottom/global (5) + // [scope1, scope2, scope3, scope4, scope5] + let scopes = this._scopeChain.slice(); + + // Merge similiar scopes. Some function call frames may have multiple + // top level closure scopes (one for `var`s one for `let`s) that can be + // combined to a single scope of variables. Go in reverse order so we + // merge the first two closure scopes with the same name. Also mark + // the first time we see a new name, so we know the base for the name. + // [scope1&2, scope3, scope4, scope5] + // foo bar GLE global + let lastMarkedHash = null; + function markAsBaseIfNeeded(scope) { + if (!scope.hash) + return false; + if (scope.type !== WebInspector.ScopeChainNode.Type.Closure) + return false; + if (scope.hash === lastMarkedHash) + return false; + lastMarkedHash = scope.hash; + scope.__baseClosureScope = true; + return true; + } + + function shouldMergeClosureScopes(youngScope, oldScope, lastMerge) { + if (!youngScope || !oldScope) + return false; + + // Don't merge unknown locations. + if (!youngScope.hash || !oldScope.hash) + return false; + + // Only merge closure scopes. + if (youngScope.type !== WebInspector.ScopeChainNode.Type.Closure) + return false; + if (oldScope.type !== WebInspector.ScopeChainNode.Type.Closure) + return false; + + // Don't merge if they are not the same. + if (youngScope.hash !== oldScope.hash) + return false; + + // Don't merge if there was already a merge. + if (lastMerge && youngScope.hash === lastMerge.hash) + return false; + + return true; + } + + let lastScope = null; + let lastMerge = null; + for (let i = scopes.length - 1; i >= 0; --i) { + let scope = scopes[i]; + markAsBaseIfNeeded(scope); + if (shouldMergeClosureScopes(scope, lastScope, lastMerge)) { + console.assert(lastScope.__baseClosureScope); + let type = WebInspector.ScopeChainNode.Type.Closure; + let objects = lastScope.objects.concat(scope.objects); + let merged = new WebInspector.ScopeChainNode(type, objects, scope.name, scope.location); + merged.__baseClosureScope = true; + console.assert(objects.length === 2); + + mergedScopes.pop(); // Remove the last. + mergedScopes.push(merged); // Add the merged scope. + + lastMerge = merged; + lastScope = null; + } else { + mergedScopes.push(scope); + + lastMerge = null; + lastScope = scope; + } + } + + mergedScopes = mergedScopes.reverse(); + + // Mark the first Closure as Local if the name matches this call frame. + for (let scope of mergedScopes) { + if (scope.type === WebInspector.ScopeChainNode.Type.Closure) { + if (scope.name === this._functionName) + scope.convertToLocalScope(); + break; + } + } + + return mergedScopes; + } + + // Static + + static functionNameFromPayload(payload) + { + let functionName = payload.functionName; + if (functionName === "global code") + return WebInspector.UIString("Global Code"); + if (functionName === "eval code") + return WebInspector.UIString("Eval Code"); + if (functionName === "module code") + return WebInspector.UIString("Module Code"); + return functionName; + } + + static programCodeFromPayload(payload) + { + return payload.functionName.endsWith(" code"); + } + + static fromDebuggerPayload(target, payload, scopeChain, sourceCodeLocation) + { + let id = payload.callFrameId; + let thisObject = WebInspector.RemoteObject.fromPayload(payload.this, target); + let functionName = WebInspector.CallFrame.functionNameFromPayload(payload); + let nativeCode = false; + let programCode = WebInspector.CallFrame.programCodeFromPayload(payload); + let isTailDeleted = payload.isTailDeleted; + + if (sourceCodeLocation && isWebInspectorConsoleEvaluationScript(sourceCodeLocation.sourceCode.sourceURL)) { + functionName = WebInspector.UIString("Console Evaluation"); + programCode = true; + } + + return new WebInspector.CallFrame(target, id, sourceCodeLocation, functionName, thisObject, scopeChain, nativeCode, programCode, isTailDeleted); + } + + static fromPayload(target, payload) + { + console.assert(payload); + + let {url, scriptId} = payload; + let nativeCode = false; + let sourceCodeLocation = null; + let functionName = WebInspector.CallFrame.functionNameFromPayload(payload); + let programCode = WebInspector.CallFrame.programCodeFromPayload(payload); + + if (url === "[native code]") { + nativeCode = true; + url = null; + } else if (url || scriptId) { + let sourceCode = null; + if (scriptId) { + sourceCode = WebInspector.debuggerManager.scriptForIdentifier(scriptId, target); + if (sourceCode && sourceCode.resource) + sourceCode = sourceCode.resource; + } + if (!sourceCode) + sourceCode = WebInspector.frameResourceManager.resourceForURL(url); + if (!sourceCode) + sourceCode = WebInspector.debuggerManager.scriptsForURL(url, target)[0]; + + if (sourceCode) { + // The lineNumber is 1-based, but we expect 0-based. + let lineNumber = payload.lineNumber - 1; + sourceCodeLocation = sourceCode.createLazySourceCodeLocation(lineNumber, payload.columnNumber); + } else { + // Treat this as native code if we were unable to find a source. + console.assert(!url, "We should have detected source code for something with a url"); + nativeCode = true; + url = null; + } + } + + if (sourceCodeLocation && isWebInspectorConsoleEvaluationScript(sourceCodeLocation.sourceCode.sourceURL)) { + functionName = WebInspector.UIString("Console Evaluation"); + programCode = true; + } + + return new WebInspector.CallFrame(target, null, sourceCodeLocation, functionName, null, null, nativeCode, programCode); + } +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/CallingContextTree.js b/Source/WebInspectorUI/UserInterface/Models/CallingContextTree.js new file mode 100644 index 000000000..2fab051c0 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/CallingContextTree.js @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2016 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. + */ + +WebInspector.CallingContextTree = class CallingContextTree extends WebInspector.Object +{ + constructor(type) + { + super(); + + this._type = type || WebInspector.CallingContextTree.Type.TopDown; + + this.reset(); + } + + // Public + + get type() { return this._type; } + get totalNumberOfSamples() { return this._totalNumberOfSamples; } + + reset() + { + this._root = new WebInspector.CallingContextTreeNode(-1, -1, -1, "<root>", null); + this._totalNumberOfSamples = 0; + } + + totalDurationInTimeRange(startTime, endTime) + { + return this._root.filteredTimestampsAndDuration(startTime, endTime).duration; + } + + updateTreeWithStackTrace({timestamp, stackFrames}, duration) + { + this._totalNumberOfSamples++; + + let node = this._root; + node.addTimestampAndExpressionLocation(timestamp, duration, null); + + switch (this._type) { + case WebInspector.CallingContextTree.Type.TopDown: + for (let i = stackFrames.length; i--; ) { + let stackFrame = stackFrames[i]; + node = node.findOrMakeChild(stackFrame); + node.addTimestampAndExpressionLocation(timestamp, duration, stackFrame.expressionLocation || null, i === 0); + } + break; + case WebInspector.CallingContextTree.Type.BottomUp: + for (let i = 0; i < stackFrames.length; ++i) { + let stackFrame = stackFrames[i]; + node = node.findOrMakeChild(stackFrame); + node.addTimestampAndExpressionLocation(timestamp, duration, stackFrame.expressionLocation || null, i === 0); + } + break; + case WebInspector.CallingContextTree.Type.TopFunctionsTopDown: + for (let i = stackFrames.length; i--; ) { + node = this._root; + for (let j = i + 1; j--; ) { + let stackFrame = stackFrames[j]; + node = node.findOrMakeChild(stackFrame); + node.addTimestampAndExpressionLocation(timestamp, duration, stackFrame.expressionLocation || null, j === 0); + } + } + break; + case WebInspector.CallingContextTree.Type.TopFunctionsBottomUp: + for (let i = 0; i < stackFrames.length; i++) { + node = this._root; + for (let j = i; j < stackFrames.length; j++) { + let stackFrame = stackFrames[j]; + node = node.findOrMakeChild(stackFrame); + node.addTimestampAndExpressionLocation(timestamp, duration, stackFrame.expressionLocation || null, j === 0); + } + } + break; + default: + console.assert(false, "This should not be reached."); + break; + } + } + + toCPUProfilePayload(startTime, endTime) + { + let cpuProfile = {}; + let roots = []; + let numSamplesInTimeRange = this._root.filteredTimestampsAndDuration(startTime, endTime).timestamps.length; + + this._root.forEachChild((child) => { + if (child.hasStackTraceInTimeRange(startTime, endTime)) + roots.push(child.toCPUProfileNode(numSamplesInTimeRange, startTime, endTime)); + }); + + cpuProfile.rootNodes = roots; + return cpuProfile; + } + + forEachChild(callback) + { + this._root.forEachChild(callback); + } + + forEachNode(callback) + { + this._root.forEachNode(callback); + } + + // Testing. + + static __test_makeTreeFromProtocolMessageObject(messageObject) + { + let tree = new WebInspector.CallingContextTree; + let stackTraces = messageObject.params.samples.stackTraces; + for (let i = 0; i < stackTraces.length; i++) + tree.updateTreeWithStackTrace(stackTraces[i]); + return tree; + } + + __test_matchesStackTrace(stackTrace) + { + // StackTrace should have top frame first in the array and bottom frame last. + // We don't look for a match that traces down the tree from the root; instead, + // we match by looking at all the leafs, and matching while walking up the tree + // towards the root. If we successfully make the walk, we've got a match that + // suffices for a particular test. A successful match doesn't mean we actually + // walk all the way up to the root; it just means we didn't fail while walking + // in the direction of the root. + let leaves = this.__test_buildLeafLinkedLists(); + + outer: + for (let node of leaves) { + for (let stackNode of stackTrace) { + for (let propertyName of Object.getOwnPropertyNames(stackNode)) { + if (stackNode[propertyName] !== node[propertyName]) + continue outer; + } + node = node.parent; + } + return true; + } + return false; + } + + __test_buildLeafLinkedLists() + { + let result = []; + let parent = null; + this._root.__test_buildLeafLinkedLists(parent, result); + return result; + } +}; + +WebInspector.CallingContextTree.Type = { + TopDown: Symbol("TopDown"), + BottomUp: Symbol("BottomUp"), + TopFunctionsTopDown: Symbol("TopFunctionsTopDown"), + TopFunctionsBottomUp: Symbol("TopFunctionsBottomUp"), +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/CallingContextTreeNode.js b/Source/WebInspectorUI/UserInterface/Models/CallingContextTreeNode.js new file mode 100644 index 000000000..db8165641 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/CallingContextTreeNode.js @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2016 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. + */ + +WebInspector.CallingContextTreeNode = class CallingContextTreeNode extends WebInspector.Object +{ + constructor(sourceID, line, column, name, url, hash) + { + super(); + + this._children = {}; + this._sourceID = sourceID; + this._line = line; + this._column = column; + this._name = name; + this._url = url; + this._uid = WebInspector.CallingContextTreeNode.__uid++; + + this._timestamps = []; + this._durations = []; + this._leafTimestamps = []; + this._leafDurations = []; + this._expressionLocations = {}; // Keys are "line:column" strings. Values are arrays of timestamps in sorted order. + + this._hash = hash || WebInspector.CallingContextTreeNode._hash(this); + } + + // Static and Private + + static _hash(stackFrame) + { + return stackFrame.name + ":" + stackFrame.sourceID + ":" + stackFrame.line + ":" + stackFrame.column; + } + + // Public + + get sourceID() { return this._sourceID; } + get line() { return this._line; } + get column() { return this._column; } + get name() { return this._name; } + get uid() { return this._uid; } + get url() { return this._url; } + get hash() { return this._hash; } + + hasChildrenInTimeRange(startTime, endTime) + { + for (let propertyName of Object.getOwnPropertyNames(this._children)) { + let child = this._children[propertyName]; + if (child.hasStackTraceInTimeRange(startTime, endTime)) + return true; + } + return false; + } + + hasStackTraceInTimeRange(startTime, endTime) + { + console.assert(startTime <= endTime); + if (startTime > endTime) + return false; + + let timestamps = this._timestamps; + let length = timestamps.length; + if (!length) + return false; + + let index = timestamps.lowerBound(startTime); + if (index === length) + return false; + console.assert(startTime <= timestamps[index]); + + let hasTimestampInRange = timestamps[index] <= endTime; + return hasTimestampInRange; + } + + filteredTimestampsAndDuration(startTime, endTime) + { + let lowerIndex = this._timestamps.lowerBound(startTime); + let upperIndex = this._timestamps.upperBound(endTime); + + let totalDuration = 0; + for (let i = lowerIndex; i < upperIndex; ++i) + totalDuration += this._durations[i]; + + return { + timestamps: this._timestamps.slice(lowerIndex, upperIndex), + duration: totalDuration, + }; + } + + filteredLeafTimestampsAndDuration(startTime, endTime) + { + let lowerIndex = this._leafTimestamps.lowerBound(startTime); + let upperIndex = this._leafTimestamps.upperBound(endTime); + + let totalDuration = 0; + for (let i = lowerIndex; i < upperIndex; ++i) + totalDuration += this._leafDurations[i]; + + return { + leafTimestamps: this._leafTimestamps.slice(lowerIndex, upperIndex), + leafDuration: totalDuration, + }; + } + + hasChildren() + { + return !isEmptyObject(this._children); + } + + findOrMakeChild(stackFrame) + { + let hash = WebInspector.CallingContextTreeNode._hash(stackFrame); + let node = this._children[hash]; + if (node) + return node; + node = new WebInspector.CallingContextTreeNode(stackFrame.sourceID, stackFrame.line, stackFrame.column, stackFrame.name, stackFrame.url, hash); + this._children[hash] = node; + return node; + } + + addTimestampAndExpressionLocation(timestamp, duration, expressionLocation, leaf) + { + console.assert(!this._timestamps.length || this._timestamps.lastValue <= timestamp, "Expected timestamps to be added in sorted, increasing, order."); + this._timestamps.push(timestamp); + this._durations.push(duration); + + if (leaf) { + this._leafTimestamps.push(timestamp); + this._leafDurations.push(duration); + } + + if (!expressionLocation) + return; + + let {line, column} = expressionLocation; + let hashCons = line + ":" + column; + let timestamps = this._expressionLocations[hashCons]; + if (!timestamps) { + timestamps = []; + this._expressionLocations[hashCons] = timestamps; + } + console.assert(!timestamps.length || timestamps.lastValue <= timestamp, "Expected timestamps to be added in sorted, increasing, order."); + timestamps.push(timestamp); + } + + forEachChild(callback) + { + for (let propertyName of Object.getOwnPropertyNames(this._children)) + callback(this._children[propertyName]); + } + + forEachNode(callback) + { + callback(this); + this.forEachChild(function(child) { + child.forEachNode(callback); + }); + } + + equals(other) + { + return this._hash === other.hash; + } + + toCPUProfileNode(numSamples, startTime, endTime) + { + let children = []; + this.forEachChild((child) => { + if (child.hasStackTraceInTimeRange(startTime, endTime)) + children.push(child.toCPUProfileNode(numSamples, startTime, endTime)); + }); + let cpuProfileNode = { + id: this._uid, + functionName: this._name, + url: this._url, + lineNumber: this._line, + columnNumber: this._column, + children: children + }; + + let timestamps = []; + let frameStartTime = Number.MAX_VALUE; + let frameEndTime = Number.MIN_VALUE; + for (let i = 0; i < this._timestamps.length; i++) { + let timestamp = this._timestamps[i]; + if (startTime <= timestamp && timestamp <= endTime) { + timestamps.push(timestamp); + frameStartTime = Math.min(frameStartTime, timestamp); + frameEndTime = Math.max(frameEndTime, timestamp); + } + } + + cpuProfileNode.callInfo = { + callCount: timestamps.length, // Totally not callCount, but oh well, this makes life easier because of field names. + startTime: frameStartTime, + endTime: frameEndTime, + totalTime: (timestamps.length / numSamples) * (endTime - startTime) + }; + + return cpuProfileNode; + } + + // Testing. + + __test_buildLeafLinkedLists(parent, result) + { + let linkedListNode = { + name: this._name, + url: this._url, + parent: parent + }; + if (this.hasChildren()) { + this.forEachChild((child) => { + child.__test_buildLeafLinkedLists(linkedListNode, result); + }); + } else { + // We're a leaf. + result.push(linkedListNode); + } + } +}; + +WebInspector.CallingContextTreeNode.__uid = 0; diff --git a/Source/WebInspectorUI/UserInterface/Models/Collection.js b/Source/WebInspectorUI/UserInterface/Models/Collection.js new file mode 100644 index 000000000..8c7739f83 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/Collection.js @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2016 Devin Rousso <dcrousso+webkit@gmail.com>. 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. + */ + +WebInspector.Collection = class Collection extends WebInspector.Object +{ + constructor(typeVerifier) + { + super(); + + this._items = new Set; + + console.assert(!typeVerifier || typeof typeVerifier === "function"); + this._typeVerifier = typeVerifier || WebInspector.Collection.TypeVerifier.Any; + } + + // Public + + get items() { return this._items; } + get typeVerifier() { return this._typeVerifier; } + + add(item) + { + let isValidType = this._typeVerifier(item); + console.assert(isValidType); + if (!isValidType) + return; + + console.assert(!this._items.has(item)); + this._items.add(item); + + this.itemAdded(item); + + this.dispatchEventToListeners(WebInspector.Collection.Event.ItemAdded, {item}); + } + + remove(item) + { + let wasRemoved = this._items.delete(item); + console.assert(wasRemoved); + + this.itemRemoved(item); + + this.dispatchEventToListeners(WebInspector.Collection.Event.ItemRemoved, {item}); + } + + clear() + { + let items = new Set(this._items); + + this._items.clear(); + + this.itemsCleared(items); + + for (let item of items) + this.dispatchEventToListeners(WebInspector.Collection.Event.ItemRemoved, {item}); + } + + toArray() + { + return Array.from(this._items); + } + + toJSON() + { + return this.toArray(); + } + + // Protected + + itemAdded(item) + { + // Implemented by subclasses. + } + + itemRemoved(item) + { + // Implemented by subclasses. + } + + itemsCleared(items) + { + // Implemented by subclasses. + } +}; + + WebInspector.Collection.Event = { + ItemAdded: "collection-item-added", + ItemRemoved: "collection-item-removed", +}; + + WebInspector.Collection.TypeVerifier = { + Any: (object) => true, + ContentFlow: (object) => object instanceof WebInspector.ContentFlow, + Frame: (object) => object instanceof WebInspector.Frame, + Resource: (object) => object instanceof WebInspector.Resource, + Script: (object) => object instanceof WebInspector.Script, +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/CollectionEntry.js b/Source/WebInspectorUI/UserInterface/Models/CollectionEntry.js new file mode 100644 index 000000000..681e0a4a7 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/CollectionEntry.js @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2015 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. + */ + +WebInspector.CollectionEntry = class CollectionEntry extends WebInspector.Object +{ + constructor(key, value) + { + super(); + + console.assert(value instanceof WebInspector.RemoteObject); + console.assert(!key || key instanceof WebInspector.RemoteObject); + + this._key = key; + this._value = value; + } + + // Static + + // Runtime.CollectionEntry. + static fromPayload(payload, target) + { + if (payload.key) + payload.key = WebInspector.RemoteObject.fromPayload(payload.key, target); + if (payload.value) + payload.value = WebInspector.RemoteObject.fromPayload(payload.value, target); + + return new WebInspector.CollectionEntry(payload.key, payload.value); + } + + // Public + + get key() { return this._key; } + get value() { return this._value; } +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/CollectionEntryPreview.js b/Source/WebInspectorUI/UserInterface/Models/CollectionEntryPreview.js new file mode 100644 index 000000000..d88ad8230 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/CollectionEntryPreview.js @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2015 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. + */ + +WebInspector.CollectionEntryPreview = class CollectionEntryPreview extends WebInspector.Object +{ + constructor(keyPreview, valuePreview) + { + super(); + + console.assert(valuePreview instanceof WebInspector.ObjectPreview); + console.assert(!keyPreview || keyPreview instanceof WebInspector.ObjectPreview); + + this._key = keyPreview; + this._value = valuePreview; + } + + // Static + + // Runtime.EntryPreview. + static fromPayload(payload) + { + if (payload.key) + payload.key = WebInspector.ObjectPreview.fromPayload(payload.key); + if (payload.value) + payload.value = WebInspector.ObjectPreview.fromPayload(payload.value); + + return new WebInspector.CollectionEntryPreview(payload.key, payload.value); + } + + // Public + + get keyPreview() { return this._key; } + get valuePreview() { return this._value; } +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/Color.js b/Source/WebInspectorUI/UserInterface/Models/Color.js new file mode 100644 index 000000000..491bf2870 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/Color.js @@ -0,0 +1,762 @@ +/* + * Copyright (C) 2009, 2013 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 Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.Color = class Color +{ + constructor(format, components) + { + this.format = format; + + if (format === WebInspector.Color.Format.HSL || format === WebInspector.Color.Format.HSLA) + this._hsla = components; + else + this._rgba = components; + + this.valid = !components.some(isNaN); + } + + // Static + + static fromString(colorString) + { + let value = colorString.toLowerCase().replace(/%|\s+/g, ""); + let transparentKeywords = ["transparent", "rgba(0,0,0,0)", "hsla(0,0,0,0)"]; + if (transparentKeywords.includes(value)) { + let color = new WebInspector.Color(WebInspector.Color.Format.Keyword, [0, 0, 0, 0]); + color.keyword = "transparent"; + color.original = colorString; + return color; + } + + // Simple - #hex, rgb(), keyword, hsl() + let simple = /^(?:#([0-9a-f]{3,8})|rgb\(([^)]+)\)|(\w+)|hsl\(([^)]+)\))$/i; + let match = colorString.match(simple); + if (match) { + if (match[1]) { // hex + let hex = match[1].toUpperCase(); + let len = hex.length; + if (len === 3) { + return new WebInspector.Color(WebInspector.Color.Format.ShortHEX, [ + parseInt(hex.charAt(0) + hex.charAt(0), 16), + parseInt(hex.charAt(1) + hex.charAt(1), 16), + parseInt(hex.charAt(2) + hex.charAt(2), 16), + 1 + ]); + } else if (len === 6) { + return new WebInspector.Color(WebInspector.Color.Format.HEX, [ + parseInt(hex.substring(0, 2), 16), + parseInt(hex.substring(2, 4), 16), + parseInt(hex.substring(4, 6), 16), + 1 + ]); + } else if (len === 4) { + return new WebInspector.Color(WebInspector.Color.Format.ShortHEXAlpha, [ + parseInt(hex.charAt(0) + hex.charAt(0), 16), + parseInt(hex.charAt(1) + hex.charAt(1), 16), + parseInt(hex.charAt(2) + hex.charAt(2), 16), + parseInt(hex.charAt(3) + hex.charAt(3), 16) / 255 + ]); + } else if (len === 8) { + return new WebInspector.Color(WebInspector.Color.Format.HEXAlpha, [ + parseInt(hex.substring(0, 2), 16), + parseInt(hex.substring(2, 4), 16), + parseInt(hex.substring(4, 6), 16), + parseInt(hex.substring(6, 8), 16) / 255 + ]); + } else + return null; + } else if (match[2]) { // rgb + let rgb = match[2].split(/\s*,\s*/); + if (rgb.length !== 3) + return null; + return new WebInspector.Color(WebInspector.Color.Format.RGB, [ + parseInt(rgb[0]), + parseInt(rgb[1]), + parseInt(rgb[2]), + 1 + ]); + } else if (match[3]) { // keyword + let keyword = match[3].toLowerCase(); + if (!WebInspector.Color.Keywords.hasOwnProperty(keyword)) + return null; + let color = new WebInspector.Color(WebInspector.Color.Format.Keyword, WebInspector.Color.Keywords[keyword].concat(1)); + color.keyword = keyword; + color.original = colorString; + return color; + } else if (match[4]) { // hsl + let hsl = match[4].replace(/%/g, "").split(/\s*,\s*/); + if (hsl.length !== 3) + return null; + return new WebInspector.Color(WebInspector.Color.Format.HSL, [ + parseInt(hsl[0]), + parseInt(hsl[1]), + parseInt(hsl[2]), + 1 + ]); + } + } + + // Advanced - rgba(), hsla() + let advanced = /^(?:rgba\(([^)]+)\)|hsla\(([^)]+)\))$/i; + match = colorString.match(advanced); + if (match) { + if (match[1]) { // rgba + let rgba = match[1].split(/\s*,\s*/); + if (rgba.length !== 4) + return null; + return new WebInspector.Color(WebInspector.Color.Format.RGBA, [ + parseInt(rgba[0]), + parseInt(rgba[1]), + parseInt(rgba[2]), + Number.constrain(parseFloat(rgba[3]), 0, 1) + ]); + } else if (match[2]) { // hsla + let hsla = match[2].replace(/%/g, "").split(/\s*,\s*/); + if (hsla.length !== 4) + return null; + return new WebInspector.Color(WebInspector.Color.Format.HSLA, [ + parseInt(hsla[0]), + parseInt(hsla[1]), + parseInt(hsla[2]), + Number.constrain(parseFloat(hsla[3]), 0, 1) + ]); + } + } + + return null; + } + + static rgb2hsv(r, g, b) + { + r /= 255; + g /= 255; + b /= 255; + + let min = Math.min(Math.min(r, g), b); + let max = Math.max(Math.max(r, g), b); + let delta = max - min; + + let h; + let s; + let v = max; + + if (delta === 0) + h = 0; + else if (max === r) + h = (60 * ((g - b) / delta)) % 360; + else if (max === g) + h = 60 * ((b - r) / delta) + 120; + else if (max === b) + h = 60 * ((r - g) / delta) + 240; + + if (h < 0) + h += 360; + + // Saturation + if (max === 0) + s = 0; + else + s = 1 - (min / max); + + return [h, s, v]; + } + + static hsv2rgb(h, s, v) + { + if (s === 0) + return [v, v, v]; + + h /= 60; + let i = Math.floor(h); + let data = [ + v * (1 - s), + v * (1 - s * (h - i)), + v * (1 - s * (1 - (h - i))) + ]; + let rgb; + + switch (i) { + case 0: + rgb = [v, data[2], data[0]]; + break; + case 1: + rgb = [data[1], v, data[0]]; + break; + case 2: + rgb = [data[0], v, data[2]]; + break; + case 3: + rgb = [data[0], data[1], v]; + break; + case 4: + rgb = [data[2], data[0], v]; + break; + default: + rgb = [v, data[0], data[1]]; + break; + } + + return rgb; + } + + + // Public + + nextFormat(format) + { + format = format || this.format; + + switch (format) { + case WebInspector.Color.Format.Original: + case WebInspector.Color.Format.HEX: + case WebInspector.Color.Format.HEXAlpha: + return this.simple ? WebInspector.Color.Format.RGB : WebInspector.Color.Format.RGBA; + + case WebInspector.Color.Format.RGB: + case WebInspector.Color.Format.RGBA: + return this.simple ? WebInspector.Color.Format.HSL : WebInspector.Color.Format.HSLA; + + case WebInspector.Color.Format.HSL: + case WebInspector.Color.Format.HSLA: + if (this.isKeyword()) + return WebInspector.Color.Format.Keyword; + if (this.simple) + return this.canBeSerializedAsShortHEX() ? WebInspector.Color.Format.ShortHEX : WebInspector.Color.Format.HEX; + return this.canBeSerializedAsShortHEX() ? WebInspector.Color.Format.ShortHEXAlpha : WebInspector.Color.Format.HEXAlpha; + + case WebInspector.Color.Format.ShortHEX: + return WebInspector.Color.Format.HEX; + + case WebInspector.Color.Format.ShortHEXAlpha: + return WebInspector.Color.Format.HEXAlpha; + + case WebInspector.Color.Format.Keyword: + if (this.simple) + return this.canBeSerializedAsShortHEX() ? WebInspector.Color.Format.ShortHEX : WebInspector.Color.Format.HEX; + return this.canBeSerializedAsShortHEX() ? WebInspector.Color.Format.ShortHEXAlpha : WebInspector.Color.Format.HEXAlpha; + + default: + console.error("Unknown color format."); + return null; + } + } + + get alpha() + { + return this._rgba ? this._rgba[3] : this._hsla[3]; + } + + get simple() + { + return this.alpha === 1; + } + + get rgb() + { + let rgb = this.rgba.slice(); + rgb.pop(); + return rgb; + } + + get hsl() + { + let hsl = this.hsla.slice(); + hsl.pop(); + return hsl; + } + + get rgba() + { + if (!this._rgba) + this._rgba = this._hslaToRGBA(this._hsla); + return this._rgba; + } + + get hsla() + { + if (!this._hsla) + this._hsla = this._rgbaToHSLA(this.rgba); + return this._hsla; + } + + copy() + { + switch (this.format) { + case WebInspector.Color.Format.RGB: + case WebInspector.Color.Format.HEX: + case WebInspector.Color.Format.ShortHEX: + case WebInspector.Color.Format.HEXAlpha: + case WebInspector.Color.Format.ShortHEXAlpha: + case WebInspector.Color.Format.Keyword: + case WebInspector.Color.Format.RGBA: + return new WebInspector.Color(this.format, this.rgba); + case WebInspector.Color.Format.HSL: + case WebInspector.Color.Format.HSLA: + return new WebInspector.Color(this.format, this.hsla); + } + } + + toString(format) + { + if (!format) + format = this.format; + + switch (format) { + case WebInspector.Color.Format.Original: + return this._toOriginalString(); + case WebInspector.Color.Format.RGB: + return this._toRGBString(); + case WebInspector.Color.Format.RGBA: + return this._toRGBAString(); + case WebInspector.Color.Format.HSL: + return this._toHSLString(); + case WebInspector.Color.Format.HSLA: + return this._toHSLAString(); + case WebInspector.Color.Format.HEX: + return this._toHEXString(); + case WebInspector.Color.Format.ShortHEX: + return this._toShortHEXString(); + case WebInspector.Color.Format.HEXAlpha: + return this._toHEXAlphaString(); + case WebInspector.Color.Format.ShortHEXAlpha: + return this._toShortHEXAlphaString(); + case WebInspector.Color.Format.Keyword: + return this._toKeywordString(); + } + + throw "invalid color format"; + } + + isKeyword() + { + if (this.keyword) + return true; + + if (!this.simple) + return Array.shallowEqual(this._rgba, [0, 0, 0, 0]) || Array.shallowEqual(this._hsla, [0, 0, 0, 0]); + + let rgb = (this._rgba && this._rgba.slice(0, 3)) || this._hslToRGB(this._hsla); + return Object.keys(WebInspector.Color.Keywords).some(key => Array.shallowEqual(WebInspector.Color.Keywords[key], rgb)); + } + + canBeSerializedAsShortHEX() + { + let rgba = this.rgba || this._hslaToRGBA(this._hsla); + + let r = this._componentToHexValue(rgba[0]); + if (r[0] !== r[1]) + return false; + + let g = this._componentToHexValue(rgba[1]); + if (g[0] !== g[1]) + return false; + + let b = this._componentToHexValue(rgba[2]); + if (b[0] !== b[1]) + return false; + + if (!this.simple) { + let a = this._componentToHexValue(Math.round(rgba[3] * 255)); + if (a[0] !== a[1]) + return false; + } + + return true; + } + + // Private + + _toOriginalString() + { + return this.original || this._toKeywordString(); + } + + _toKeywordString() + { + if (this.keyword) + return this.keyword; + + let rgba = this.rgba; + if (!this.simple) { + if (rgba[0] === 0 && rgba[1] === 0 && rgba[2] === 0 && rgba[3] === 0) + return "transparent"; + return this._toRGBAString(); + } + + let keywords = WebInspector.Color.Keywords; + for (let keyword in keywords) { + if (!keywords.hasOwnProperty(keyword)) + continue; + + let keywordRGB = keywords[keyword]; + if (keywordRGB[0] === rgba[0] && keywordRGB[1] === rgba[1] && keywordRGB[2] === rgba[2]) + return keyword; + } + + return this._toRGBString(); + } + + _toShortHEXString() + { + if (!this.simple) + return this._toRGBAString(); + + let rgba = this.rgba; + let r = this._componentToHexValue(rgba[0]); + let g = this._componentToHexValue(rgba[1]); + let b = this._componentToHexValue(rgba[2]); + + if (r[0] === r[1] && g[0] === g[1] && b[0] === b[1]) + return "#" + r[0] + g[0] + b[0]; + else + return "#" + r + g + b; + } + + _toHEXString() + { + if (!this.simple) + return this._toRGBAString(); + + let rgba = this.rgba; + let r = this._componentToHexValue(rgba[0]); + let g = this._componentToHexValue(rgba[1]); + let b = this._componentToHexValue(rgba[2]); + + return "#" + r + g + b; + } + + _toShortHEXAlphaString() + { + let rgba = this.rgba; + let r = this._componentToHexValue(rgba[0]); + let g = this._componentToHexValue(rgba[1]); + let b = this._componentToHexValue(rgba[2]); + let a = this._componentToHexValue(Math.round(rgba[3] * 255)); + + if (r[0] === r[1] && g[0] === g[1] && b[0] === b[1] && a[0] === a[1]) + return "#" + r[0] + g[0] + b[0] + a[0]; + else + return "#" + r + g + b + a; + } + + _toHEXAlphaString() + { + let rgba = this.rgba; + let r = this._componentToHexValue(rgba[0]); + let g = this._componentToHexValue(rgba[1]); + let b = this._componentToHexValue(rgba[2]); + let a = this._componentToHexValue(Math.round(rgba[3] * 255)); + + return "#" + r + g + b + a; + } + + _toRGBString() + { + if (!this.simple) + return this._toRGBAString(); + + let rgba = this.rgba.slice(0, -1); + rgba = rgba.map((value) => value.maxDecimals(2)); + return "rgb(" + rgba.join(", ") + ")"; + } + + _toRGBAString() + { + let rgba = this.rgba; + rgba = rgba.map((value) => value.maxDecimals(2)); + return "rgba(" + rgba.join(", ") + ")"; + } + + _toHSLString() + { + if (!this.simple) + return this._toHSLAString(); + + let hsla = this.hsla; + hsla = hsla.map((value) => value.maxDecimals(2)); + return "hsl(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%)"; + } + + _toHSLAString() + { + let hsla = this.hsla; + hsla = hsla.map((value) => value.maxDecimals(2)); + return "hsla(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%, " + hsla[3] + ")"; + } + + _componentToNumber(value) + { + return Number.constrain(value, 0, 255); + } + + _componentToHexValue(value) + { + let hex = this._componentToNumber(value).toString(16); + if (hex.length === 1) + hex = "0" + hex; + return hex; + } + + _rgbToHSL(rgb) + { + let r = this._componentToNumber(rgb[0]) / 255; + let g = this._componentToNumber(rgb[1]) / 255; + let b = this._componentToNumber(rgb[2]) / 255; + let max = Math.max(r, g, b); + let min = Math.min(r, g, b); + let diff = max - min; + let add = max + min; + + let h; + let s; + let l = 0.5 * add; + + if (min === max) + h = 0; + else if (r === max) + h = ((60 * (g - b) / diff) + 360) % 360; + else if (g === max) + h = (60 * (b - r) / diff) + 120; + else + h = (60 * (r - g) / diff) + 240; + + if (l === 0) + s = 0; + else if (l === 1) + s = 1; + else if (l <= 0.5) + s = diff / add; + else + s = diff / (2 - add); + + return [ + Math.round(h), + Math.round(s * 100), + Math.round(l * 100) + ]; + } + + _hslToRGB(hsl) + { + let h = parseFloat(hsl[0]) / 360; + let s = parseFloat(hsl[1]) / 100; + let l = parseFloat(hsl[2]) / 100; + + h *= 6; + let sArray = [ + l += s *= l < .5 ? l : 1 - l, + l - h % 1 * s * 2, + l -= s *= 2, + l, + l + h % 1 * s, + l + s + ]; + return [ + Math.round(sArray[ ~~h % 6 ] * 255), + Math.round(sArray[ (h | 16) % 6 ] * 255), + Math.round(sArray[ (h | 8) % 6 ] * 255) + ]; + } + + _rgbaToHSLA(rgba) + { + let hsl = this._rgbToHSL(rgba); + hsl.push(rgba[3]); + return hsl; + } + + _hslaToRGBA(hsla) + { + let rgba = this._hslToRGB(hsla); + rgba.push(hsla[3]); + return rgba; + } +}; + +WebInspector.Color.Format = { + Original: "color-format-original", + Keyword: "color-format-keyword", + HEX: "color-format-hex", + ShortHEX: "color-format-short-hex", + HEXAlpha: "color-format-hex-alpha", + ShortHEXAlpha: "color-format-short-hex-alpha", + RGB: "color-format-rgb", + RGBA: "color-format-rgba", + HSL: "color-format-hsl", + HSLA: "color-format-hsla" +}; + +WebInspector.Color.Keywords = { + "aliceblue": [240, 248, 255], + "antiquewhite": [250, 235, 215], + "aquamarine": [127, 255, 212], + "azure": [240, 255, 255], + "beige": [245, 245, 220], + "bisque": [255, 228, 196], + "black": [0, 0, 0], + "blanchedalmond": [255, 235, 205], + "blue": [0, 0, 255], + "blueviolet": [138, 43, 226], + "brown": [165, 42, 42], + "burlywood": [222, 184, 135], + "cadetblue": [95, 158, 160], + "chartreuse": [127, 255, 0], + "chocolate": [210, 105, 30], + "coral": [255, 127, 80], + "cornflowerblue": [100, 149, 237], + "cornsilk": [255, 248, 220], + "crimson": [237, 164, 61], + "cyan": [0, 255, 255], + "darkblue": [0, 0, 139], + "darkcyan": [0, 139, 139], + "darkgoldenrod": [184, 134, 11], + "darkgray": [169, 169, 169], + "darkgreen": [0, 100, 0], + "darkgrey": [169, 169, 169], + "darkkhaki": [189, 183, 107], + "darkmagenta": [139, 0, 139], + "darkolivegreen": [85, 107, 47], + "darkorange": [255, 140, 0], + "darkorchid": [153, 50, 204], + "darkred": [139, 0, 0], + "darksalmon": [233, 150, 122], + "darkseagreen": [143, 188, 143], + "darkslateblue": [72, 61, 139], + "darkslategray": [47, 79, 79], + "darkslategrey": [47, 79, 79], + "darkturquoise": [0, 206, 209], + "darkviolet": [148, 0, 211], + "deeppink": [255, 20, 147], + "deepskyblue": [0, 191, 255], + "dimgray": [105, 105, 105], + "dimgrey": [105, 105, 105], + "dodgerblue": [30, 144, 255], + "firebrick": [178, 34, 34], + "floralwhite": [255, 250, 240], + "forestgreen": [34, 139, 34], + "gainsboro": [220, 220, 220], + "ghostwhite": [248, 248, 255], + "gold": [255, 215, 0], + "goldenrod": [218, 165, 32], + "gray": [128, 128, 128], + "green": [0, 128, 0], + "greenyellow": [173, 255, 47], + "grey": [128, 128, 128], + "honeydew": [240, 255, 240], + "hotpink": [255, 105, 180], + "indianred": [205, 92, 92], + "indigo": [75, 0, 130], + "ivory": [255, 255, 240], + "khaki": [240, 230, 140], + "lavender": [230, 230, 250], + "lavenderblush": [255, 240, 245], + "lawngreen": [124, 252, 0], + "lemonchiffon": [255, 250, 205], + "lightblue": [173, 216, 230], + "lightcoral": [240, 128, 128], + "lightcyan": [224, 255, 255], + "lightgoldenrodyellow": [250, 250, 210], + "lightgray": [211, 211, 211], + "lightgreen": [144, 238, 144], + "lightgrey": [211, 211, 211], + "lightpink": [255, 182, 193], + "lightsalmon": [255, 160, 122], + "lightseagreen": [32, 178, 170], + "lightskyblue": [135, 206, 250], + "lightslategray": [119, 136, 153], + "lightslategrey": [119, 136, 153], + "lightsteelblue": [176, 196, 222], + "lightyellow": [255, 255, 224], + "lime": [0, 255, 0], + "limegreen": [50, 205, 50], + "linen": [250, 240, 230], + "magenta": [255, 0, 255], + "maroon": [128, 0, 0], + "mediumaquamarine": [102, 205, 170], + "mediumblue": [0, 0, 205], + "mediumorchid": [186, 85, 211], + "mediumpurple": [147, 112, 219], + "mediumseagreen": [60, 179, 113], + "mediumslateblue": [123, 104, 238], + "mediumspringgreen": [0, 250, 154], + "mediumturquoise": [72, 209, 204], + "mediumvioletred": [199, 21, 133], + "midnightblue": [25, 25, 112], + "mintcream": [245, 255, 250], + "mistyrose": [255, 228, 225], + "moccasin": [255, 228, 181], + "navajowhite": [255, 222, 173], + "navy": [0, 0, 128], + "oldlace": [253, 245, 230], + "olive": [128, 128, 0], + "olivedrab": [107, 142, 35], + "orange": [255, 165, 0], + "orangered": [255, 69, 0], + "orchid": [218, 112, 214], + "palegoldenrod": [238, 232, 170], + "palegreen": [152, 251, 152], + "paleturquoise": [175, 238, 238], + "palevioletred": [219, 112, 147], + "papayawhip": [255, 239, 213], + "peachpuff": [255, 218, 185], + "peru": [205, 133, 63], + "pink": [255, 192, 203], + "plum": [221, 160, 221], + "powderblue": [176, 224, 230], + "purple": [128, 0, 128], + "rebeccapurple": [102, 51, 153], + "red": [255, 0, 0], + "rosybrown": [188, 143, 143], + "royalblue": [65, 105, 225], + "saddlebrown": [139, 69, 19], + "salmon": [250, 128, 114], + "sandybrown": [244, 164, 96], + "seagreen": [46, 139, 87], + "seashell": [255, 245, 238], + "sienna": [160, 82, 45], + "silver": [192, 192, 192], + "skyblue": [135, 206, 235], + "slateblue": [106, 90, 205], + "slategray": [112, 128, 144], + "slategrey": [112, 128, 144], + "snow": [255, 250, 250], + "springgreen": [0, 255, 127], + "steelblue": [70, 130, 180], + "tan": [210, 180, 140], + "teal": [0, 128, 128], + "thistle": [216, 191, 216], + "tomato": [255, 99, 71], + "turquoise": [64, 224, 208], + "violet": [238, 130, 238], + "wheat": [245, 222, 179], + "white": [255, 255, 255], + "whitesmoke": [245, 245, 245], + "yellow": [255, 255, 0], + "yellowgreen": [154, 205, 50] +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/ConsoleCommandResultMessage.js b/Source/WebInspectorUI/UserInterface/Models/ConsoleCommandResultMessage.js new file mode 100644 index 000000000..f76c8a82c --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/ConsoleCommandResultMessage.js @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2007, 2008, 2013 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 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.ConsoleCommandResultMessage = class ConsoleCommandResult extends WebInspector.ConsoleMessage +{ + constructor(target, result, wasThrown, savedResultIndex, shouldRevealConsole = true) + { + let source = WebInspector.ConsoleMessage.MessageSource.JS; + let level = wasThrown ? WebInspector.ConsoleMessage.MessageLevel.Error : WebInspector.ConsoleMessage.MessageLevel.Log; + let type = WebInspector.ConsoleMessage.MessageType.Result; + + super(target, source, level, "", type, undefined, undefined, undefined, 0, [result], undefined, undefined); + + this._savedResultIndex = savedResultIndex; + this._shouldRevealConsole = shouldRevealConsole; + + if (this._savedResultIndex && this._savedResultIndex > WebInspector.ConsoleCommandResultMessage.maximumSavedResultIndex) + WebInspector.ConsoleCommandResultMessage.maximumSavedResultIndex = this._savedResultIndex; + } + + // Static + + static clearMaximumSavedResultIndex() + { + WebInspector.ConsoleCommandResultMessage.maximumSavedResultIndex = 0; + } + + // Public + + get savedResultIndex() + { + return this._savedResultIndex; + } + + get shouldRevealConsole() + { + return this._shouldRevealConsole; + } +}; + +WebInspector.ConsoleCommandResultMessage.maximumSavedResultIndex = 0; diff --git a/Source/WebInspectorUI/UserInterface/Models/ConsoleMessage.js b/Source/WebInspectorUI/UserInterface/Models/ConsoleMessage.js new file mode 100644 index 000000000..b8154ae06 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/ConsoleMessage.js @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2015 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. + */ + +WebInspector.ConsoleMessage = class ConsoleMessage extends WebInspector.Object +{ + constructor(target, source, level, message, type, url, line, column, repeatCount, parameters, callFrames, request) + { + super(); + + console.assert(typeof source === "string"); + console.assert(typeof level === "string"); + console.assert(typeof message === "string"); + console.assert(target instanceof WebInspector.Target); + console.assert(!parameters || parameters.every((x) => x instanceof WebInspector.RemoteObject)); + + this._target = target; + this._source = source; + this._level = level; + this._messageText = message; + this._type = type || WebInspector.ConsoleMessage.MessageType.Log; + + this._url = url || null; + this._line = line || 0; + this._column = column || 0; + this._sourceCodeLocation = undefined; + + this._repeatCount = repeatCount || 0; + this._parameters = parameters; + + callFrames = callFrames || []; + this._stackTrace = WebInspector.StackTrace.fromPayload(this._target, {callFrames}); + + this._request = request; + } + + // Public + + get target() { return this._target; } + get source() { return this._source; } + get level() { return this._level; } + get messageText() { return this._messageText; } + get type() { return this._type; } + get url() { return this._url; } + get line() { return this._line; } + get column() { return this._column; } + get repeatCount() { return this._repeatCount; } + get parameters() { return this._parameters; } + get stackTrace() { return this._stackTrace; } + get request() { return this._request; } + + get sourceCodeLocation() + { + if (this._sourceCodeLocation !== undefined) + return this._sourceCodeLocation; + + // First try to get the location from the top frame of the stack trace. + let topCallFrame = this._stackTrace.callFrames[0]; + if (topCallFrame && topCallFrame.sourceCodeLocation) { + this._sourceCodeLocation = topCallFrame.sourceCodeLocation; + return this._sourceCodeLocation; + } + + // If that doesn't exist try to get a location from the url/line/column in the ConsoleMessage. + // FIXME <http://webkit.org/b/76404>: Remove the string equality checks for undefined once we don't get that value anymore. + if (this._url && this._url !== "undefined") { + let sourceCode = WebInspector.frameResourceManager.resourceForURL(this._url); + if (sourceCode) { + let lineNumber = this._line > 0 ? this._line - 1 : 0; + let columnNumber = this._column > 0 ? this._column - 1 : 0; + this._sourceCodeLocation = new WebInspector.SourceCodeLocation(sourceCode, lineNumber, columnNumber); + return this._sourceCodeLocation; + } + } + + this._sourceCodeLocation = null; + return this._sourceCodeLocation; + } +}; + +WebInspector.ConsoleMessage.MessageSource = { + HTML: "html", + XML: "xml", + JS: "javascript", + Network: "network", + ConsoleAPI: "console-api", + Storage: "storage", + Appcache: "appcache", + Rendering: "rendering", + CSS: "css", + Security: "security", + Other: "other", +}; + +WebInspector.ConsoleMessage.MessageType = { + Log: "log", + Dir: "dir", + DirXML: "dirxml", + Table: "table", + Trace: "trace", + StartGroup: "startGroup", + StartGroupCollapsed: "startGroupCollapsed", + EndGroup: "endGroup", + Assert: "assert", + Timing: "timing", + Profile: "profile", + ProfileEnd: "profileEnd", + Result: "result", // Frontend Only. +}; + +WebInspector.ConsoleMessage.MessageLevel = { + Log: "log", + Info: "info", + Warning: "warning", + Error: "error", + Debug: "debug", +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/ContentFlow.js b/Source/WebInspectorUI/UserInterface/Models/ContentFlow.js new file mode 100644 index 000000000..cc063d3df --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/ContentFlow.js @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2013 Adobe Systems Incorporated. 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 THE COPYRIGHT HOLDER "AS IS" AND ANY + * EXPRESS OR 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 HOLDER BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (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.ContentFlow = class ContentFlow extends WebInspector.Object +{ + constructor(documentNodeIdentifier, name, overset, contentNodes) + { + super(); + + this._documentNodeIdentifier = documentNodeIdentifier; + this._name = name; + this._overset = overset; + this._contentNodes = contentNodes; + } + + // Public + + get id() + { + // Use the flow node id, to avoid collisions when we change main document id. + return this._documentNodeIdentifier + ":" + this._name; + } + + get documentNodeIdentifier() + { + return this._documentNodeIdentifier; + } + + get name() + { + return this._name; + } + + get overset() + { + return this._overset; + } + + set overset(overset) + { + if (this._overset === overset) + return; + this._overset = overset; + this.dispatchEventToListeners(WebInspector.ContentFlow.Event.FlowOversetWasChanged); + } + + get contentNodes() + { + return this._contentNodes; + } + + insertContentNodeBefore(contentNode, referenceNode) + { + var index = this._contentNodes.indexOf(referenceNode); + console.assert(index !== -1); + this._contentNodes.splice(index, 0, contentNode); + this.dispatchEventToListeners(WebInspector.ContentFlow.Event.ContentNodeWasAdded, {node: contentNode, before: referenceNode}); + } + + appendContentNode(contentNode) + { + this._contentNodes.push(contentNode); + this.dispatchEventToListeners(WebInspector.ContentFlow.Event.ContentNodeWasAdded, {node: contentNode}); + } + + removeContentNode(contentNode) + { + var index = this._contentNodes.indexOf(contentNode); + console.assert(index !== -1); + this._contentNodes.splice(index, 1); + this.dispatchEventToListeners(WebInspector.ContentFlow.Event.ContentNodeWasRemoved, {node: contentNode}); + } +}; + +WebInspector.ContentFlow.Event = { + OversetWasChanged: "content-flow-overset-was-changed", + ContentNodeWasAdded: "content-flow-content-node-was-added", + ContentNodeWasRemoved: "content-flow-content-node-was-removed" +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/CookieStorageObject.js b/Source/WebInspectorUI/UserInterface/Models/CookieStorageObject.js new file mode 100644 index 000000000..fe41077be --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/CookieStorageObject.js @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2013, 2015 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. + */ + +WebInspector.CookieStorageObject = class CookieStorageObject +{ + constructor(host) + { + this._host = host; + } + + // Static + + static cookieMatchesResourceURL(cookie, resourceURL) + { + var parsedURL = parseURL(resourceURL); + if (!parsedURL || !WebInspector.CookieStorageObject.cookieDomainMatchesResourceDomain(cookie.domain, parsedURL.host)) + return false; + + return parsedURL.path.startsWith(cookie.path) + && (!cookie.port || parsedURL.port === cookie.port) + && (!cookie.secure || parsedURL.scheme === "https"); + } + + static cookieDomainMatchesResourceDomain(cookieDomain, resourceDomain) + { + if (cookieDomain.charAt(0) !== ".") + return resourceDomain === cookieDomain; + return !!resourceDomain.match(new RegExp("^(?:[^\\.]+\\.)*" + cookieDomain.substring(1).escapeForRegExp() + "$"), "i"); + } + + // Public + + get host() + { + return this._host; + } + + saveIdentityToCookie(cookie) + { + // FIXME <https://webkit.org/b/151413>: This class should actually store cookie data for this host. + cookie[WebInspector.CookieStorageObject.CookieHostCookieKey] = this.host; + } +}; + +WebInspector.CookieStorageObject.TypeIdentifier = "cookie-storage"; +WebInspector.CookieStorageObject.CookieHostCookieKey = "cookie-storage-host"; diff --git a/Source/WebInspectorUI/UserInterface/Models/DOMNode.js b/Source/WebInspectorUI/UserInterface/Models/DOMNode.js new file mode 100644 index 000000000..636a7586f --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/DOMNode.js @@ -0,0 +1,833 @@ +/* + * Copyright (C) 2009, 2010 Google Inc. All rights reserved. + * Copyright (C) 2009 Joseph Pecoraro + * Copyright (C) 2013, 2016 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. + */ + +WebInspector.DOMNode = class DOMNode extends WebInspector.Object +{ + constructor(domTreeManager, doc, isInShadowTree, payload) + { + super(); + + this._domTreeManager = domTreeManager; + this._isInShadowTree = isInShadowTree; + + this.id = payload.nodeId; + this._domTreeManager._idToDOMNode[this.id] = this; + + this._nodeType = payload.nodeType; + this._nodeName = payload.nodeName; + this._localName = payload.localName; + this._nodeValue = payload.nodeValue; + this._pseudoType = payload.pseudoType; + this._shadowRootType = payload.shadowRootType; + this._computedRole = payload.role; + this._contentSecurityPolicyHash = payload.contentSecurityPolicyHash; + + if (this._nodeType === Node.DOCUMENT_NODE) + this.ownerDocument = this; + else + this.ownerDocument = doc; + + this._attributes = []; + this._attributesMap = {}; + if (payload.attributes) + this._setAttributesPayload(payload.attributes); + + this._childNodeCount = payload.childNodeCount; + this._children = null; + this._filteredChildren = null; + this._filteredChildrenNeedsUpdating = true; + + this._nextSibling = null; + this._previousSibling = null; + this.parentNode = null; + + this._enabledPseudoClasses = []; + + // FIXME: The logic around this._shadowRoots and this._children is very confusing. + this._shadowRoots = []; + if (payload.shadowRoots) { + for (var i = 0; i < payload.shadowRoots.length; ++i) { + var root = payload.shadowRoots[i]; + var node = new WebInspector.DOMNode(this._domTreeManager, this.ownerDocument, true, root); + node.parentNode = this; + this._shadowRoots.push(node); + } + } + + if (this._nodeType === Node.ELEMENT_NODE) + this._customElementState = payload.customElementState || WebInspector.DOMNode.CustomElementState.Builtin; + else + this._customElementState = null; + + if (payload.templateContent) { + this._templateContent = new WebInspector.DOMNode(this._domTreeManager, this.ownerDocument, false, payload.templateContent); + this._templateContent.parentNode = this; + } + + if (payload.children) + this._setChildrenPayload(payload.children); + else if (!this._children && this._shadowRoots.length) + this._children = this._shadowRoots.slice(); + + this._pseudoElements = new Map; + if (payload.pseudoElements) { + for (var i = 0; i < payload.pseudoElements.length; ++i) { + var node = new WebInspector.DOMNode(this._domTreeManager, this.ownerDocument, this._isInShadowTree, payload.pseudoElements[i]); + node.parentNode = this; + this._pseudoElements.set(node.pseudoType(), node); + } + } + + if (payload.contentDocument) { + this._contentDocument = new WebInspector.DOMNode(this._domTreeManager, null, false, payload.contentDocument); + this._children = [this._contentDocument]; + this._renumber(); + } + + if (payload.frameId) + this._frameIdentifier = payload.frameId; + + 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; + if (payload.documentURL) + this.documentURL = payload.documentURL; + } else if (this._nodeType === Node.DOCUMENT_TYPE_NODE) { + this.publicId = payload.publicId; + this.systemId = payload.systemId; + } else if (this._nodeType === Node.DOCUMENT_NODE) { + this.documentURL = payload.documentURL; + this.xmlVersion = payload.xmlVersion; + } else if (this._nodeType === Node.ATTRIBUTE_NODE) { + this.name = payload.name; + this.value = payload.value; + } + } + + // Public + + get frameIdentifier() + { + return this._frameIdentifier || this.ownerDocument.frameIdentifier; + } + + get frame() + { + if (!this._frame) + this._frame = WebInspector.frameResourceManager.frameForIdentifier(this.frameIdentifier); + return this._frame; + } + + get children() + { + if (!this._children) + return null; + + if (WebInspector.showShadowDOMSetting.value) + return this._children; + + if (this._filteredChildrenNeedsUpdating) { + this._filteredChildrenNeedsUpdating = false; + this._filteredChildren = this._children.filter(function(node) { + return !node._isInShadowTree; + }); + } + + return this._filteredChildren; + } + + get firstChild() + { + var children = this.children; + + if (children && children.length > 0) + return children[0]; + + return null; + } + + get lastChild() + { + var children = this.children; + + if (children && children.length > 0) + return children.lastValue; + + return null; + } + + get nextSibling() + { + if (WebInspector.showShadowDOMSetting.value) + return this._nextSibling; + + var node = this._nextSibling; + while (node) { + if (!node._isInShadowTree) + return node; + node = node._nextSibling; + } + return null; + } + + get previousSibling() + { + if (WebInspector.showShadowDOMSetting.value) + return this._previousSibling; + + var node = this._previousSibling; + while (node) { + if (!node._isInShadowTree) + return node; + node = node._previousSibling; + } + return null; + } + + get childNodeCount() + { + var children = this.children; + if (children) + return children.length; + + if (WebInspector.showShadowDOMSetting.value) + return this._childNodeCount + this._shadowRoots.length; + + return this._childNodeCount; + } + + set childNodeCount(count) + { + this._childNodeCount = count; + } + + computedRole() + { + return this._computedRole; + } + + contentSecurityPolicyHash() + { + return this._contentSecurityPolicyHash; + } + + hasAttributes() + { + return this._attributes.length > 0; + } + + hasChildNodes() + { + return this.childNodeCount > 0; + } + + hasShadowRoots() + { + return !!this._shadowRoots.length; + } + + isInShadowTree() + { + return this._isInShadowTree; + } + + isInUserAgentShadowTree() + { + return this._isInShadowTree && this.ancestorShadowRoot().isUserAgentShadowRoot(); + } + + isCustomElement() + { + return this._customElementState === WebInspector.DOMNode.CustomElementState.Custom; + } + + customElementState() + { + return this._customElementState; + } + + isShadowRoot() + { + return !!this._shadowRootType; + } + + isUserAgentShadowRoot() + { + return this._shadowRootType === WebInspector.DOMNode.ShadowRootType.UserAgent; + } + + ancestorShadowRoot() + { + if (!this._isInShadowTree) + return null; + + let node = this; + while (node && !node.isShadowRoot()) + node = node.parentNode; + return node; + } + + ancestorShadowHost() + { + let shadowRoot = this.ancestorShadowRoot(); + return shadowRoot ? shadowRoot.parentNode : null; + } + + isPseudoElement() + { + return this._pseudoType !== undefined; + } + + nodeType() + { + return this._nodeType; + } + + nodeName() + { + return this._nodeName; + } + + nodeNameInCorrectCase() + { + return this.isXMLNode() ? this.nodeName() : this.nodeName().toLowerCase(); + } + + setNodeName(name, callback) + { + DOMAgent.setNodeName(this.id, name, this._makeUndoableCallback(callback)); + } + + localName() + { + return this._localName; + } + + templateContent() + { + return this._templateContent || null; + } + + pseudoType() + { + return this._pseudoType; + } + + hasPseudoElements() + { + return this._pseudoElements.size > 0; + } + + pseudoElements() + { + return this._pseudoElements; + } + + beforePseudoElement() + { + return this._pseudoElements.get(WebInspector.DOMNode.PseudoElementType.Before) || null; + } + + afterPseudoElement() + { + return this._pseudoElements.get(WebInspector.DOMNode.PseudoElementType.After) || null; + } + + shadowRoots() + { + return this._shadowRoots; + } + + shadowRootType() + { + return this._shadowRootType; + } + + nodeValue() + { + return this._nodeValue; + } + + setNodeValue(value, callback) + { + DOMAgent.setNodeValue(this.id, value, this._makeUndoableCallback(callback)); + } + + getAttribute(name) + { + var attr = this._attributesMap[name]; + return attr ? attr.value : undefined; + } + + setAttribute(name, text, callback) + { + DOMAgent.setAttributesAsText(this.id, text, name, this._makeUndoableCallback(callback)); + } + + setAttributeValue(name, value, callback) + { + DOMAgent.setAttributeValue(this.id, name, value, this._makeUndoableCallback(callback)); + } + + attributes() + { + return this._attributes; + } + + removeAttribute(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; + } + } + } + + this._makeUndoableCallback(callback)(error); + } + DOMAgent.removeAttribute(this.id, name, mycallback.bind(this)); + } + + toggleClass(className, flag) + { + if (!className || !className.length) + return; + + if (this.isPseudoElement()) { + this.parentNode.toggleClass(className, flag); + return; + } + + if (this.nodeType() !== Node.ELEMENT_NODE) + return; + + function resolvedNode(object) + { + if (!object) + return; + + function inspectedPage_node_toggleClass(className, flag) + { + this.classList.toggle(className, flag); + } + + object.callFunction(inspectedPage_node_toggleClass, [className, flag]); + object.release(); + } + + WebInspector.RemoteObject.resolveNode(this, "", resolvedNode); + } + + getChildNodes(callback) + { + if (this.children) { + if (callback) + callback(this.children); + return; + } + + function mycallback(error) { + if (!error && callback) + callback(this.children); + } + + DOMAgent.requestChildNodes(this.id, mycallback.bind(this)); + } + + getSubtree(depth, callback) + { + function mycallback(error) + { + if (callback) + callback(error ? null : this.children); + } + + DOMAgent.requestChildNodes(this.id, depth, mycallback.bind(this)); + } + + getOuterHTML(callback) + { + DOMAgent.getOuterHTML(this.id, callback); + } + + setOuterHTML(html, callback) + { + DOMAgent.setOuterHTML(this.id, html, this._makeUndoableCallback(callback)); + } + + removeNode(callback) + { + DOMAgent.removeNode(this.id, this._makeUndoableCallback(callback)); + } + + copyNode() + { + function copy(error, text) + { + if (!error) + InspectorFrontendHost.copyText(text); + } + DOMAgent.getOuterHTML(this.id, copy); + } + + eventListeners(callback) + { + DOMAgent.getEventListenersForNode(this.id, callback); + } + + accessibilityProperties(callback) + { + function accessibilityPropertiesCallback(error, accessibilityProperties) + { + if (!error && callback && accessibilityProperties) { + callback({ + activeDescendantNodeId: accessibilityProperties.activeDescendantNodeId, + busy: accessibilityProperties.busy, + checked: accessibilityProperties.checked, + childNodeIds: accessibilityProperties.childNodeIds, + controlledNodeIds: accessibilityProperties.controlledNodeIds, + current: accessibilityProperties.current, + disabled: accessibilityProperties.disabled, + exists: accessibilityProperties.exists, + expanded: accessibilityProperties.expanded, + flowedNodeIds: accessibilityProperties.flowedNodeIds, + focused: accessibilityProperties.focused, + ignored: accessibilityProperties.ignored, + ignoredByDefault: accessibilityProperties.ignoredByDefault, + invalid: accessibilityProperties.invalid, + isPopupButton: accessibilityProperties.isPopUpButton, + headingLevel: accessibilityProperties.headingLevel, + hierarchyLevel: accessibilityProperties.hierarchyLevel, + hidden: accessibilityProperties.hidden, + label: accessibilityProperties.label, + liveRegionAtomic: accessibilityProperties.liveRegionAtomic, + liveRegionRelevant: accessibilityProperties.liveRegionRelevant, + liveRegionStatus: accessibilityProperties.liveRegionStatus, + mouseEventNodeId: accessibilityProperties.mouseEventNodeId, + nodeId: accessibilityProperties.nodeId, + ownedNodeIds: accessibilityProperties.ownedNodeIds, + parentNodeId: accessibilityProperties.parentNodeId, + pressed: accessibilityProperties.pressed, + readonly: accessibilityProperties.readonly, + required: accessibilityProperties.required, + role: accessibilityProperties.role, + selected: accessibilityProperties.selected, + selectedChildNodeIds: accessibilityProperties.selectedChildNodeIds + }); + } + } + DOMAgent.getAccessibilityPropertiesForNode(this.id, accessibilityPropertiesCallback.bind(this)); + } + + path() + { + 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(","); + } + + get escapedIdSelector() + { + let id = this.getAttribute("id"); + if (!id) + return ""; + + id = id.trim(); + if (!id.length) + return ""; + + id = CSS.escape(id); + if (/[\s'"]/.test(id)) + return `[id=\"${id}\"]`; + + return `#${id}`; + } + + get escapedClassSelector() + { + let classes = this.getAttribute("class"); + if (!classes) + return ""; + + classes = classes.trim(); + if (!classes.length) + return ""; + + let foundClasses = new Set; + return classes.split(/\s+/).reduce((selector, className) => { + if (!className.length || foundClasses.has(className)) + return selector; + + foundClasses.add(className); + return `${selector}.${CSS.escape(className)}`; + }, ""); + } + + get displayName() + { + return this.nodeNameInCorrectCase() + this.escapedIdSelector + this.escapedClassSelector; + } + + appropriateSelectorFor(justSelector) + { + if (this.isPseudoElement()) + return this.parentNode.appropriateSelectorFor() + "::" + this._pseudoType; + + let lowerCaseName = this.localName() || this.nodeName().toLowerCase(); + + let id = this.escapedIdSelector; + if (id.length) + return justSelector ? id : lowerCaseName + id; + + let classes = this.escapedClassSelector; + if (classes.length) + return justSelector ? classes : lowerCaseName + classes; + + if (lowerCaseName === "input" && this.getAttribute("type")) + return lowerCaseName + "[type=\"" + this.getAttribute("type") + "\"]"; + + return lowerCaseName; + } + + isAncestor(node) + { + if (!node) + return false; + + var currentNode = node.parentNode; + while (currentNode) { + if (this === currentNode) + return true; + currentNode = currentNode.parentNode; + } + return false; + } + + isDescendant(descendant) + { + return descendant !== null && descendant.isAncestor(this); + } + + get ownerSVGElement() + { + if (this._nodeName === "svg") + return this; + + if (!this.parentNode) + return null; + + return this.parentNode.ownerSVGElement; + } + + isSVGElement() + { + return !!this.ownerSVGElement; + } + + _setAttributesPayload(attrs) + { + this._attributes = []; + this._attributesMap = {}; + for (var i = 0; i < attrs.length; i += 2) + this._addAttribute(attrs[i], attrs[i + 1]); + } + + _insertChild(prev, payload) + { + var node = new WebInspector.DOMNode(this._domTreeManager, this.ownerDocument, this._isInShadowTree, payload); + if (!prev) { + if (!this._children) { + // First node + this._children = this._shadowRoots.concat([node]); + } else + this._children.unshift(node); + } else + this._children.splice(this._children.indexOf(prev) + 1, 0, node); + this._renumber(); + return node; + } + + _removeChild(node) + { + // FIXME: Handle removal if this is a shadow root. + if (node.isPseudoElement()) { + this._pseudoElements.delete(node.pseudoType()); + node.parentNode = null; + } else { + this._children.splice(this._children.indexOf(node), 1); + node.parentNode = null; + this._renumber(); + } + } + + _setChildrenPayload(payloads) + { + // We set children in the constructor. + if (this._contentDocument) + return; + + this._children = this._shadowRoots.slice(); + for (var i = 0; i < payloads.length; ++i) { + var node = new WebInspector.DOMNode(this._domTreeManager, this.ownerDocument, this._isInShadowTree, payloads[i]); + this._children.push(node); + } + this._renumber(); + } + + _renumber() + { + this._filteredChildrenNeedsUpdating = true; + + var childNodeCount = this._children.length; + if (childNodeCount === 0) + return; + + for (var i = 0; i < childNodeCount; ++i) { + var child = this._children[i]; + child.index = i; + child._nextSibling = i + 1 < childNodeCount ? this._children[i + 1] : null; + child._previousSibling = i - 1 >= 0 ? this._children[i - 1] : null; + child.parentNode = this; + } + } + + _addAttribute(name, value) + { + var attr = {name, value, _node: this}; + this._attributesMap[name] = attr; + this._attributes.push(attr); + } + + _setAttribute(name, value) + { + var attr = this._attributesMap[name]; + if (attr) + attr.value = value; + else + this._addAttribute(name, value); + } + + _removeAttribute(name) + { + var attr = this._attributesMap[name]; + if (attr) { + this._attributes.remove(attr); + delete this._attributesMap[name]; + } + } + + moveTo(targetNode, anchorNode, callback) + { + DOMAgent.moveTo(this.id, targetNode.id, anchorNode ? anchorNode.id : undefined, this._makeUndoableCallback(callback)); + } + + isXMLNode() + { + return !!this.ownerDocument && !!this.ownerDocument.xmlVersion; + } + + get enabledPseudoClasses() + { + return this._enabledPseudoClasses; + } + + setPseudoClassEnabled(pseudoClass, enabled) + { + var pseudoClasses = this._enabledPseudoClasses; + if (enabled) { + if (pseudoClasses.includes(pseudoClass)) + return; + pseudoClasses.push(pseudoClass); + } else { + if (!pseudoClasses.includes(pseudoClass)) + return; + pseudoClasses.remove(pseudoClass); + } + + function changed(error) + { + if (!error) + this.dispatchEventToListeners(WebInspector.DOMNode.Event.EnabledPseudoClassesChanged); + } + + CSSAgent.forcePseudoState(this.id, pseudoClasses, changed.bind(this)); + } + + _makeUndoableCallback(callback) + { + return function(error) + { + if (!error) + DOMAgent.markUndoableState(); + + if (callback) + callback.apply(null, arguments); + }; + } +}; + +WebInspector.DOMNode.Event = { + EnabledPseudoClassesChanged: "dom-node-enabled-pseudo-classes-did-change", + AttributeModified: "dom-node-attribute-modified", + AttributeRemoved: "dom-node-attribute-removed" +}; + +WebInspector.DOMNode.PseudoElementType = { + Before: "before", + After: "after", +}; + +WebInspector.DOMNode.ShadowRootType = { + UserAgent: "user-agent", + Closed: "closed", + Open: "open", +}; + +WebInspector.DOMNode.CustomElementState = { + Builtin: "builtin", + Custom: "custom", + Waiting: "waiting", + Failed: "failed", +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/DOMNodeStyles.js b/Source/WebInspectorUI/UserInterface/Models/DOMNodeStyles.js new file mode 100644 index 000000000..564be5e52 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/DOMNodeStyles.js @@ -0,0 +1,985 @@ +/* + * Copyright (C) 2013 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. + */ + +WebInspector.DOMNodeStyles = class DOMNodeStyles extends WebInspector.Object +{ + constructor(node) + { + super(); + + console.assert(node); + this._node = node || null; + + this._rulesMap = {}; + this._styleDeclarationsMap = {}; + + this._matchedRules = []; + this._inheritedRules = []; + this._pseudoElements = {}; + this._inlineStyle = null; + this._attributesStyle = null; + this._computedStyle = null; + this._orderedStyles = []; + + this._propertyNameToEffectivePropertyMap = {}; + + this._pendingRefreshTask = null; + this.refresh(); + } + + // Public + + get node() + { + return this._node; + } + + get needsRefresh() + { + return this._pendingRefreshTask || this._needsRefresh; + } + + refreshIfNeeded() + { + if (!this._needsRefresh) + return; + this.refresh(); + } + + refresh() + { + if (this._pendingRefreshTask) + return this._pendingRefreshTask; + + this._needsRefresh = false; + + let fetchedMatchedStylesPromise = new WebInspector.WrappedPromise; + let fetchedInlineStylesPromise = new WebInspector.WrappedPromise; + let fetchedComputedStylesPromise = new WebInspector.WrappedPromise; + + function parseRuleMatchArrayPayload(matchArray, node, inherited) + { + var result = []; + + // Iterate in reverse order to match the cascade order. + var ruleOccurrences = {}; + for (var i = matchArray.length - 1; i >= 0; --i) { + var rule = this._parseRulePayload(matchArray[i].rule, matchArray[i].matchingSelectors, node, inherited, ruleOccurrences); + if (!rule) + continue; + result.push(rule); + } + + return result; + } + + function fetchedMatchedStyles(error, matchedRulesPayload, pseudoElementRulesPayload, inheritedRulesPayload) + { + matchedRulesPayload = matchedRulesPayload || []; + pseudoElementRulesPayload = pseudoElementRulesPayload || []; + inheritedRulesPayload = inheritedRulesPayload || []; + + // Move the current maps to previous. + this._previousRulesMap = this._rulesMap; + this._previousStyleDeclarationsMap = this._styleDeclarationsMap; + + // Clear the current maps. + this._rulesMap = {}; + this._styleDeclarationsMap = {}; + + this._matchedRules = parseRuleMatchArrayPayload.call(this, matchedRulesPayload, this._node); + + this._pseudoElements = {}; + for (var pseudoElementRulePayload of pseudoElementRulesPayload) { + var pseudoElementRules = parseRuleMatchArrayPayload.call(this, pseudoElementRulePayload.matches, this._node); + this._pseudoElements[pseudoElementRulePayload.pseudoId] = {matchedRules: pseudoElementRules}; + } + + this._inheritedRules = []; + + var i = 0; + var currentNode = this._node.parentNode; + while (currentNode && i < inheritedRulesPayload.length) { + var inheritedRulePayload = inheritedRulesPayload[i]; + + var inheritedRuleInfo = {node: currentNode}; + inheritedRuleInfo.inlineStyle = inheritedRulePayload.inlineStyle ? this._parseStyleDeclarationPayload(inheritedRulePayload.inlineStyle, currentNode, true, WebInspector.CSSStyleDeclaration.Type.Inline) : null; + inheritedRuleInfo.matchedRules = inheritedRulePayload.matchedCSSRules ? parseRuleMatchArrayPayload.call(this, inheritedRulePayload.matchedCSSRules, currentNode, true) : []; + + if (inheritedRuleInfo.inlineStyle || inheritedRuleInfo.matchedRules.length) + this._inheritedRules.push(inheritedRuleInfo); + + currentNode = currentNode.parentNode; + ++i; + } + + fetchedMatchedStylesPromise.resolve(); + } + + function fetchedInlineStyles(error, inlineStylePayload, attributesStylePayload) + { + this._inlineStyle = inlineStylePayload ? this._parseStyleDeclarationPayload(inlineStylePayload, this._node, false, WebInspector.CSSStyleDeclaration.Type.Inline) : null; + this._attributesStyle = attributesStylePayload ? this._parseStyleDeclarationPayload(attributesStylePayload, this._node, false, WebInspector.CSSStyleDeclaration.Type.Attribute) : null; + + this._updateStyleCascade(); + + fetchedInlineStylesPromise.resolve(); + } + + function fetchedComputedStyle(error, computedPropertiesPayload) + { + var properties = []; + for (var i = 0; computedPropertiesPayload && i < computedPropertiesPayload.length; ++i) { + var propertyPayload = computedPropertiesPayload[i]; + + var canonicalName = WebInspector.cssStyleManager.canonicalNameForPropertyName(propertyPayload.name); + propertyPayload.implicit = !this._propertyNameToEffectivePropertyMap[canonicalName]; + + var property = this._parseStylePropertyPayload(propertyPayload, NaN, this._computedStyle); + if (!property.implicit) + property.implicit = !this._isPropertyFoundInMatchingRules(property.name); + properties.push(property); + } + + if (this._computedStyle) + this._computedStyle.update(null, properties); + else + this._computedStyle = new WebInspector.CSSStyleDeclaration(this, null, null, WebInspector.CSSStyleDeclaration.Type.Computed, this._node, false, null, properties); + + let significantChange = false; + for (let key in this._styleDeclarationsMap) { + // Check if the same key exists in the previous map and has the same style objects. + if (key in this._previousStyleDeclarationsMap) { + if (Array.shallowEqual(this._styleDeclarationsMap[key], this._previousStyleDeclarationsMap[key])) + continue; + + // Some styles have selectors such that they will match with the DOM node twice (for example "::before, ::after"). + // In this case a second style for a second matching may be generated and added which will cause the shallowEqual + // to not return true, so in this case we just want to ensure that all the current styles existed previously. + let styleFound = false; + for (let style of this._styleDeclarationsMap[key]) { + if (this._previousStyleDeclarationsMap[key].includes(style)) { + styleFound = true; + break; + } + } + + if (styleFound) + continue; + } + + if (!this._includeUserAgentRulesOnNextRefresh) { + // We can assume all the styles with the same key are from the same stylesheet and rule, so we only check the first. + let firstStyle = this._styleDeclarationsMap[key][0]; + if (firstStyle && firstStyle.ownerRule && firstStyle.ownerRule.type === WebInspector.CSSStyleSheet.Type.UserAgent) { + // User Agent styles get different identifiers after some edits. This would cause us to fire a significant refreshed + // event more than it is helpful. And since the user agent stylesheet is static it shouldn't match differently + // between refreshes for the same node. This issue is tracked by: https://webkit.org/b/110055 + continue; + } + } + + // This key is new or has different style objects than before. This is a significant change. + significantChange = true; + break; + } + + if (!significantChange) { + for (var key in this._previousStyleDeclarationsMap) { + // Check if the same key exists in current map. If it does exist it was already checked for equality above. + if (key in this._styleDeclarationsMap) + continue; + + if (!this._includeUserAgentRulesOnNextRefresh) { + // See above for why we skip user agent style rules. + var firstStyle = this._previousStyleDeclarationsMap[key][0]; + if (firstStyle && firstStyle.ownerRule && firstStyle.ownerRule.type === WebInspector.CSSStyleSheet.Type.UserAgent) + continue; + } + + // This key no longer exists. This is a significant change. + significantChange = true; + break; + } + } + + delete this._includeUserAgentRulesOnNextRefresh; + + // Delete the previous maps now that any reused rules and style have been moved over. + delete this._previousRulesMap; + delete this._previousStyleDeclarationsMap; + + this.dispatchEventToListeners(WebInspector.DOMNodeStyles.Event.Refreshed, {significantChange}); + + fetchedComputedStylesPromise.resolve(); + } + + // FIXME: Convert to pushing StyleSheet information to the frontend. <rdar://problem/13213680> + WebInspector.cssStyleManager.fetchStyleSheetsIfNeeded(); + + CSSAgent.getMatchedStylesForNode.invoke({nodeId: this._node.id, includePseudo: true, includeInherited: true}, fetchedMatchedStyles.bind(this)); + CSSAgent.getInlineStylesForNode.invoke({nodeId: this._node.id}, fetchedInlineStyles.bind(this)); + CSSAgent.getComputedStyleForNode.invoke({nodeId: this._node.id}, fetchedComputedStyle.bind(this)); + + this._pendingRefreshTask = Promise.all([fetchedMatchedStylesPromise.promise, fetchedInlineStylesPromise.promise, fetchedComputedStylesPromise.promise]) + .then(() => { + this._pendingRefreshTask = null; + }); + + return this._pendingRefreshTask; + } + + addRule(selector, text) + { + selector = selector || this._node.appropriateSelectorFor(true); + + function completed() + { + DOMAgent.markUndoableState(); + this.refresh(); + } + + function styleChanged(error, stylePayload) + { + if (error) + return; + + completed.call(this); + } + + function addedRule(error, rulePayload) + { + if (error) + return; + + if (!text || !text.length) { + completed.call(this); + return; + } + + CSSAgent.setStyleText(rulePayload.style.styleId, text, styleChanged.bind(this)); + } + + // COMPATIBILITY (iOS 9): Before CSS.createStyleSheet, CSS.addRule could be called with a contextNode. + if (!CSSAgent.createStyleSheet) { + CSSAgent.addRule.invoke({contextNodeId: this._node.id, selector}, addedRule.bind(this)); + return; + } + + function inspectorStyleSheetAvailable(styleSheet) + { + CSSAgent.addRule(styleSheet.id, selector, addedRule.bind(this)); + } + + WebInspector.cssStyleManager.preferredInspectorStyleSheetForFrame(this._node.frame, inspectorStyleSheetAvailable.bind(this)); + } + + rulesForSelector(selector) + { + selector = selector || this._node.appropriateSelectorFor(true); + + function ruleHasSelector(rule) { + return !rule.mediaList.length && rule.selectorText === selector; + } + + let rules = this._matchedRules.filter(ruleHasSelector); + + for (let id in this._pseudoElements) + rules = rules.concat(this._pseudoElements[id].matchedRules.filter(ruleHasSelector)); + + return rules; + } + + get matchedRules() + { + return this._matchedRules; + } + + get inheritedRules() + { + return this._inheritedRules; + } + + get inlineStyle() + { + return this._inlineStyle; + } + + get attributesStyle() + { + return this._attributesStyle; + } + + get pseudoElements() + { + return this._pseudoElements; + } + + get computedStyle() + { + return this._computedStyle; + } + + get orderedStyles() + { + return this._orderedStyles; + } + + effectivePropertyForName(name) + { + let property = this._propertyNameToEffectivePropertyMap[name]; + if (property) + return property; + + let canonicalName = WebInspector.cssStyleManager.canonicalNameForPropertyName(name); + return this._propertyNameToEffectivePropertyMap[canonicalName] || null; + } + + // Protected + + mediaQueryResultDidChange() + { + this._markAsNeedsRefresh(); + } + + pseudoClassesDidChange(node) + { + this._includeUserAgentRulesOnNextRefresh = true; + this._markAsNeedsRefresh(); + } + + attributeDidChange(node, attributeName) + { + this._markAsNeedsRefresh(); + } + + changeRule(rule, selector, text) + { + if (!rule) + return; + + selector = selector || ""; + + function changeCompleted() + { + DOMAgent.markUndoableState(); + this.refresh(); + } + + function styleChanged(error, stylePayload) + { + if (error) + return; + + changeCompleted.call(this); + } + + function changeText(styleId) + { + if (!text || !text.length) { + changeCompleted.call(this); + return; + } + + CSSAgent.setStyleText(styleId, text, styleChanged.bind(this)); + } + + function ruleSelectorChanged(error, rulePayload) + { + if (error) + return; + + changeText.call(this, rulePayload.style.styleId); + } + + this._needsRefresh = true; + this._ignoreNextContentDidChangeForStyleSheet = rule.ownerStyleSheet; + + CSSAgent.setRuleSelector(rule.id, selector, ruleSelectorChanged.bind(this)); + } + + changeRuleSelector(rule, selector) + { + selector = selector || ""; + let result = new WebInspector.WrappedPromise; + + function ruleSelectorChanged(error, rulePayload) + { + if (error) { + result.reject(error); + return; + } + + DOMAgent.markUndoableState(); + + // Do a full refresh incase the rule no longer matches the node or the + // matched selector indices changed. + this.refresh().then(() => { + result.resolve(rulePayload); + }); + } + + this._needsRefresh = true; + this._ignoreNextContentDidChangeForStyleSheet = rule.ownerStyleSheet; + + CSSAgent.setRuleSelector(rule.id, selector, ruleSelectorChanged.bind(this)); + return result.promise; + } + + changeStyleText(style, text) + { + if (!style.ownerStyleSheet || !style.styleSheetTextRange) + return; + + text = text || ""; + + function styleChanged(error, stylePayload) + { + if (error) + return; + this.refresh(); + } + + CSSAgent.setStyleText(style.id, text, styleChanged.bind(this)); + } + + // Private + + _createSourceCodeLocation(sourceURL, sourceLine, sourceColumn) + { + if (!sourceURL) + return null; + + var sourceCode; + + // Try to use the node to find the frame which has the correct resource first. + if (this._node.ownerDocument) { + var mainResource = WebInspector.frameResourceManager.resourceForURL(this._node.ownerDocument.documentURL); + if (mainResource) { + var parentFrame = mainResource.parentFrame; + sourceCode = parentFrame.resourceForURL(sourceURL); + } + } + + // If that didn't find the resource, then search all frames. + if (!sourceCode) + sourceCode = WebInspector.frameResourceManager.resourceForURL(sourceURL); + + if (!sourceCode) + return null; + + return sourceCode.createSourceCodeLocation(sourceLine || 0, sourceColumn || 0); + } + + _parseSourceRangePayload(payload) + { + if (!payload) + return null; + + return new WebInspector.TextRange(payload.startLine, payload.startColumn, payload.endLine, payload.endColumn); + } + + _parseStylePropertyPayload(payload, index, styleDeclaration, styleText) + { + var text = payload.text || ""; + var name = payload.name; + var value = (payload.value || "").replace(/\s*!important\s*$/, ""); + var priority = payload.priority || ""; + + var enabled = true; + var overridden = false; + var implicit = payload.implicit || false; + var anonymous = false; + var valid = "parsedOk" in payload ? payload.parsedOk : true; + + switch (payload.status || "style") { + case "active": + enabled = true; + break; + case "inactive": + overridden = true; + enabled = true; + break; + case "disabled": + enabled = false; + break; + case "style": + // FIXME: Is this still needed? This includes UserAgent styles and HTML attribute styles. + anonymous = true; + break; + } + + var styleSheetTextRange = this._parseSourceRangePayload(payload.range); + + if (styleDeclaration) { + // Use propertyForName when the index is NaN since propertyForName is fast in that case. + var property = isNaN(index) ? styleDeclaration.propertyForName(name, true) : styleDeclaration.properties[index]; + + // Reuse a property if the index and name matches. Otherwise it is a different property + // and should be created from scratch. This works in the simple cases where only existing + // properties change in place and no properties are inserted or deleted at the beginning. + // FIXME: This could be smarter by ignoring index and just go by name. However, that gets + // tricky for rules that have more than one property with the same name. + if (property && property.name === name && (property.index === index || (isNaN(property.index) && isNaN(index)))) { + property.update(text, name, value, priority, enabled, overridden, implicit, anonymous, valid, styleSheetTextRange); + return property; + } + + // Reuse a pending property with the same name. These properties are pending being committed, + // so if we find a match that likely means it got committed and we should use it. + var pendingProperties = styleDeclaration.pendingProperties; + for (var i = 0; i < pendingProperties.length; ++i) { + var pendingProperty = pendingProperties[i]; + if (pendingProperty.name === name && isNaN(pendingProperty.index)) { + pendingProperty.index = index; + pendingProperty.update(text, name, value, priority, enabled, overridden, implicit, anonymous, valid, styleSheetTextRange); + return pendingProperty; + } + } + } + + return new WebInspector.CSSProperty(index, text, name, value, priority, enabled, overridden, implicit, anonymous, valid, styleSheetTextRange); + } + + _parseStyleDeclarationPayload(payload, node, inherited, type, rule, updateAllStyles) + { + if (!payload) + return null; + + rule = rule || null; + inherited = inherited || false; + + var id = payload.styleId; + var mapKey = id ? id.styleSheetId + ":" + id.ordinal : null; + + if (type === WebInspector.CSSStyleDeclaration.Type.Attribute) + mapKey = node.id + ":attribute"; + + var styleDeclaration = rule ? rule.style : null; + var styleDeclarations = []; + + // Look for existing styles in the previous map if there is one, otherwise use the current map. + var previousStyleDeclarationsMap = this._previousStyleDeclarationsMap || this._styleDeclarationsMap; + if (mapKey && mapKey in previousStyleDeclarationsMap) { + styleDeclarations = previousStyleDeclarationsMap[mapKey]; + + // If we need to update all styles, then stop here and call _parseStyleDeclarationPayload for each style. + // We need to parse multiple times so we reuse the right properties from each style. + if (updateAllStyles && styleDeclarations.length) { + for (var i = 0; i < styleDeclarations.length; ++i) { + var styleDeclaration = styleDeclarations[i]; + this._parseStyleDeclarationPayload(payload, styleDeclaration.node, styleDeclaration.inherited, styleDeclaration.type, styleDeclaration.ownerRule); + } + + return null; + } + + if (!styleDeclaration) { + var filteredStyleDeclarations = styleDeclarations.filter(function(styleDeclaration) { + // This case only applies for styles that are not part of a rule. + if (styleDeclaration.ownerRule) { + console.assert(!rule); + return false; + } + + if (styleDeclaration.node !== node) + return false; + + if (styleDeclaration.inherited !== inherited) + return false; + + return true; + }); + + console.assert(filteredStyleDeclarations.length <= 1); + styleDeclaration = filteredStyleDeclarations[0] || null; + } + } + + if (previousStyleDeclarationsMap !== this._styleDeclarationsMap) { + // If the previous and current maps differ then make sure the found styleDeclaration is added to the current map. + styleDeclarations = mapKey && mapKey in this._styleDeclarationsMap ? this._styleDeclarationsMap[mapKey] : []; + + if (styleDeclaration && !styleDeclarations.includes(styleDeclaration)) { + styleDeclarations.push(styleDeclaration); + this._styleDeclarationsMap[mapKey] = styleDeclarations; + } + } + + var shorthands = {}; + for (var i = 0; payload.shorthandEntries && i < payload.shorthandEntries.length; ++i) { + var shorthand = payload.shorthandEntries[i]; + shorthands[shorthand.name] = shorthand.value; + } + + var text = payload.cssText; + + var inheritedPropertyCount = 0; + + var properties = []; + for (var i = 0; payload.cssProperties && i < payload.cssProperties.length; ++i) { + var propertyPayload = payload.cssProperties[i]; + + if (inherited && WebInspector.CSSProperty.isInheritedPropertyName(propertyPayload.name)) + ++inheritedPropertyCount; + + var property = this._parseStylePropertyPayload(propertyPayload, i, styleDeclaration, text); + properties.push(property); + } + + var styleSheetTextRange = this._parseSourceRangePayload(payload.range); + + if (styleDeclaration) { + styleDeclaration.update(text, properties, styleSheetTextRange); + return styleDeclaration; + } + + var styleSheet = id ? WebInspector.cssStyleManager.styleSheetForIdentifier(id.styleSheetId) : null; + if (styleSheet) { + if (type === WebInspector.CSSStyleDeclaration.Type.Inline) + styleSheet.markAsInlineStyleAttributeStyleSheet(); + styleSheet.addEventListener(WebInspector.CSSStyleSheet.Event.ContentDidChange, this._styleSheetContentDidChange, this); + } + + if (inherited && !inheritedPropertyCount) + return null; + + styleDeclaration = new WebInspector.CSSStyleDeclaration(this, styleSheet, id, type, node, inherited, text, properties, styleSheetTextRange); + + if (mapKey) { + styleDeclarations.push(styleDeclaration); + this._styleDeclarationsMap[mapKey] = styleDeclarations; + } + + return styleDeclaration; + } + + _parseSelectorListPayload(selectorList) + { + var selectors = selectorList.selectors; + if (!selectors.length) + return []; + + // COMPATIBILITY (iOS 8): The selectorList payload was an array of selector text strings. + // Now they are CSSSelector objects with multiple properties. + if (typeof selectors[0] === "string") { + return selectors.map(function(selectorText) { + return new WebInspector.CSSSelector(selectorText); + }); + } + + return selectors.map(function(selectorPayload) { + return new WebInspector.CSSSelector(selectorPayload.text, selectorPayload.specificity, selectorPayload.dynamic); + }); + } + + _parseRulePayload(payload, matchedSelectorIndices, node, inherited, ruleOccurrences) + { + if (!payload) + return null; + + // User and User Agent rules don't have 'ruleId' in the payload. However, their style's have 'styleId' and + // 'styleId' is the same identifier the backend uses for Author rule identifiers, so do the same here. + // They are excluded by the backend because they are not editable, however our front-end does not determine + // editability solely based on the existence of the id like the open source front-end does. + var id = payload.ruleId || payload.style.styleId; + + var mapKey = id ? id.styleSheetId + ":" + id.ordinal + ":" + (inherited ? "I" : "N") + ":" + node.id : null; + + // Rules can match multiple times if they have multiple selectors or because of inheritance. We keep a count + // of occurrences so we have unique rules per occurrence, that way properties will be correctly marked as overridden. + var occurrence = 0; + if (mapKey) { + if (mapKey in ruleOccurrences) + occurrence = ++ruleOccurrences[mapKey]; + else + ruleOccurrences[mapKey] = occurrence; + + // Append the occurrence number to the map key for lookup in the rules map. + mapKey += ":" + occurrence; + } + + var rule = null; + + // Look for existing rules in the previous map if there is one, otherwise use the current map. + var previousRulesMap = this._previousRulesMap || this._rulesMap; + if (mapKey && mapKey in previousRulesMap) { + rule = previousRulesMap[mapKey]; + + if (previousRulesMap !== this._rulesMap) { + // If the previous and current maps differ then make sure the found rule is added to the current map. + this._rulesMap[mapKey] = rule; + } + } + + var style = this._parseStyleDeclarationPayload(payload.style, node, inherited, WebInspector.CSSStyleDeclaration.Type.Rule, rule); + if (!style) + return null; + + var styleSheet = id ? WebInspector.cssStyleManager.styleSheetForIdentifier(id.styleSheetId) : null; + + var selectorText = payload.selectorList.text; + var selectors = this._parseSelectorListPayload(payload.selectorList); + var type = WebInspector.CSSStyleManager.protocolStyleSheetOriginToEnum(payload.origin); + + var sourceCodeLocation = null; + var sourceRange = payload.selectorList.range; + if (sourceRange) + sourceCodeLocation = this._createSourceCodeLocation(payload.sourceURL, sourceRange.startLine, sourceRange.startColumn); + else { + // FIXME: Is it possible for a CSSRule to have a sourceLine without its selectorList having a sourceRange? Fall back just in case. + sourceCodeLocation = this._createSourceCodeLocation(payload.sourceURL, payload.sourceLine); + } + + if (styleSheet) + sourceCodeLocation = styleSheet.offsetSourceCodeLocation(sourceCodeLocation); + + var mediaList = []; + for (var i = 0; payload.media && i < payload.media.length; ++i) { + var mediaItem = payload.media[i]; + var mediaType = WebInspector.CSSStyleManager.protocolMediaSourceToEnum(mediaItem.source); + var mediaText = mediaItem.text; + var mediaSourceCodeLocation = this._createSourceCodeLocation(mediaItem.sourceURL, mediaItem.sourceLine); + if (styleSheet) + mediaSourceCodeLocation = styleSheet.offsetSourceCodeLocation(mediaSourceCodeLocation); + + mediaList.push(new WebInspector.CSSMedia(mediaType, mediaText, mediaSourceCodeLocation)); + } + + if (rule) { + rule.update(sourceCodeLocation, selectorText, selectors, matchedSelectorIndices, style, mediaList); + return rule; + } + + if (styleSheet) + styleSheet.addEventListener(WebInspector.CSSStyleSheet.Event.ContentDidChange, this._styleSheetContentDidChange, this); + + rule = new WebInspector.CSSRule(this, styleSheet, id, type, sourceCodeLocation, selectorText, selectors, matchedSelectorIndices, style, mediaList); + + if (mapKey) + this._rulesMap[mapKey] = rule; + + return rule; + } + + _markAsNeedsRefresh() + { + this._needsRefresh = true; + this.dispatchEventToListeners(WebInspector.DOMNodeStyles.Event.NeedsRefresh); + } + + _styleSheetContentDidChange(event) + { + var styleSheet = event.target; + console.assert(styleSheet); + if (!styleSheet) + return; + + // Ignore the stylesheet we know we just changed and handled above. + if (styleSheet === this._ignoreNextContentDidChangeForStyleSheet) { + delete this._ignoreNextContentDidChangeForStyleSheet; + return; + } + + this._markAsNeedsRefresh(); + } + + _updateStyleCascade() + { + var cascadeOrderedStyleDeclarations = this._collectStylesInCascadeOrder(this._matchedRules, this._inlineStyle, this._attributesStyle); + + for (var i = 0; i < this._inheritedRules.length; ++i) { + var inheritedStyleInfo = this._inheritedRules[i]; + var inheritedCascadeOrder = this._collectStylesInCascadeOrder(inheritedStyleInfo.matchedRules, inheritedStyleInfo.inlineStyle, null); + cascadeOrderedStyleDeclarations = cascadeOrderedStyleDeclarations.concat(inheritedCascadeOrder); + } + + this._orderedStyles = cascadeOrderedStyleDeclarations; + + this._propertyNameToEffectivePropertyMap = {}; + + this._markOverriddenProperties(cascadeOrderedStyleDeclarations, this._propertyNameToEffectivePropertyMap); + this._associateRelatedProperties(cascadeOrderedStyleDeclarations, this._propertyNameToEffectivePropertyMap); + + for (var pseudoIdentifier in this._pseudoElements) { + var pseudoElementInfo = this._pseudoElements[pseudoIdentifier]; + pseudoElementInfo.orderedStyles = this._collectStylesInCascadeOrder(pseudoElementInfo.matchedRules, null, null); + this._markOverriddenProperties(pseudoElementInfo.orderedStyles); + this._associateRelatedProperties(pseudoElementInfo.orderedStyles); + } + } + + _collectStylesInCascadeOrder(matchedRules, inlineStyle, attributesStyle) + { + var result = []; + + // Inline style has the greatest specificity. So it goes first in the cascade order. + if (inlineStyle) + result.push(inlineStyle); + + var userAndUserAgentStyles = []; + + for (var i = 0; i < matchedRules.length; ++i) { + var rule = matchedRules[i]; + + // Only append to the result array here for author and inspector rules since attribute + // styles come between author rules and user/user agent rules. + switch (rule.type) { + case WebInspector.CSSStyleSheet.Type.Inspector: + case WebInspector.CSSStyleSheet.Type.Author: + result.push(rule.style); + break; + + case WebInspector.CSSStyleSheet.Type.User: + case WebInspector.CSSStyleSheet.Type.UserAgent: + userAndUserAgentStyles.push(rule.style); + break; + } + } + + // Style properties from HTML attributes are next. + if (attributesStyle) + result.push(attributesStyle); + + // Finally add the user and user stylesheet's matched style rules we collected earlier. + result = result.concat(userAndUserAgentStyles); + + return result; + } + + _markOverriddenProperties(styles, propertyNameToEffectiveProperty) + { + propertyNameToEffectiveProperty = propertyNameToEffectiveProperty || {}; + + for (var i = 0; i < styles.length; ++i) { + var style = styles[i]; + var properties = style.properties; + + for (var j = 0; j < properties.length; ++j) { + var property = properties[j]; + if (!property.enabled || !property.valid) { + property.overridden = false; + continue; + } + + if (style.inherited && !property.inherited) { + property.overridden = false; + continue; + } + + var canonicalName = property.canonicalName; + if (canonicalName in propertyNameToEffectiveProperty) { + var effectiveProperty = propertyNameToEffectiveProperty[canonicalName]; + + if (effectiveProperty.ownerStyle === property.ownerStyle) { + if (effectiveProperty.important && !property.important) { + property.overridden = true; + continue; + } + } else if (effectiveProperty.important || !property.important || effectiveProperty.ownerStyle.node !== property.ownerStyle.node) { + property.overridden = true; + continue; + } + + if (!property.anonymous) + effectiveProperty.overridden = true; + } + + property.overridden = false; + + propertyNameToEffectiveProperty[canonicalName] = property; + } + } + } + + _associateRelatedProperties(styles, propertyNameToEffectiveProperty) + { + for (var i = 0; i < styles.length; ++i) { + var properties = styles[i].properties; + + var knownShorthands = {}; + + for (var j = 0; j < properties.length; ++j) { + var property = properties[j]; + + if (!property.valid) + continue; + + if (!WebInspector.CSSCompletions.cssNameCompletions.isShorthandPropertyName(property.name)) + continue; + + if (knownShorthands[property.canonicalName] && !knownShorthands[property.canonicalName].overridden) { + console.assert(property.overridden); + continue; + } + + knownShorthands[property.canonicalName] = property; + } + + for (var j = 0; j < properties.length; ++j) { + var property = properties[j]; + + if (!property.valid) + continue; + + var shorthandProperty = null; + + if (!isEmptyObject(knownShorthands)) { + var possibleShorthands = WebInspector.CSSCompletions.cssNameCompletions.shorthandsForLonghand(property.canonicalName); + for (var k = 0; k < possibleShorthands.length; ++k) { + if (possibleShorthands[k] in knownShorthands) { + shorthandProperty = knownShorthands[possibleShorthands[k]]; + break; + } + } + } + + if (!shorthandProperty || shorthandProperty.overridden !== property.overridden) { + property.relatedShorthandProperty = null; + property.clearRelatedLonghandProperties(); + continue; + } + + shorthandProperty.addRelatedLonghandProperty(property); + property.relatedShorthandProperty = shorthandProperty; + + if (propertyNameToEffectiveProperty && propertyNameToEffectiveProperty[shorthandProperty.canonicalName] === shorthandProperty) + propertyNameToEffectiveProperty[property.canonicalName] = property; + } + } + } + + _isPropertyFoundInMatchingRules(propertyName) + { + return this._orderedStyles.some((style) => { + return style.properties.some((property) => property.name === propertyName); + }); + } +}; + +WebInspector.DOMNodeStyles.Event = { + NeedsRefresh: "dom-node-styles-needs-refresh", + Refreshed: "dom-node-styles-refreshed" +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/DOMSearchMatchObject.js b/Source/WebInspectorUI/UserInterface/Models/DOMSearchMatchObject.js new file mode 100644 index 000000000..bf8b9308c --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/DOMSearchMatchObject.js @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2013 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. + */ + +WebInspector.DOMSearchMatchObject = class DOMSearchMatchObject extends WebInspector.Object +{ + constructor(resource, domNode, title, searchTerm, textRange) + { + super(); + + console.assert(resource instanceof WebInspector.Resource); + console.assert(domNode instanceof WebInspector.DOMNode); + + this._resource = resource; + this._domNode = domNode; + this._title = title; + this._searchTerm = searchTerm; + this._sourceCodeTextRange = resource.createSourceCodeTextRange(textRange); + } + + // Static + + static titleForDOMNode(domNode) + { + switch (domNode.nodeType()) { + case Node.ELEMENT_NODE: + var title = "<" + domNode.nodeNameInCorrectCase(); + for (var attribute of domNode.attributes()) { + title += " " + attribute.name; + if (attribute.value.length) + title += "=\"" + attribute.value + "\""; + } + return title + ">"; + + case Node.TEXT_NODE: + return "\"" + domNode.nodeValue() + "\""; + + case Node.COMMENT_NODE: + return "<!--" + domNode.nodeValue() + "-->"; + + case Node.DOCUMENT_TYPE_NODE: + var title = "<!DOCTYPE " + domNode.nodeName(); + if (domNode.publicId) { + title += " PUBLIC \"" + domNode.publicId + "\""; + if (domNode.systemId) + title += " \"" + domNode.systemId + "\""; + } else if (domNode.systemId) + title += " SYSTEM \"" + domNode.systemId + "\""; + + return title + ">"; + + case Node.CDATA_SECTION_NODE: + return "<![CDATA[" + domNode + "]]>"; + + case Node.PROCESSING_INSTRUCTION_NODE: + var data = domNode.nodeValue(); + var dataString = data.length ? " " + data : ""; + var title = "<?" + domNode.nodeNameInCorrectCase() + dataString + "?>"; + return title; + + default: + console.error("Unknown DOM node type: ", domNode.nodeType()); + return domNode.nodeNameInCorrectCase(); + } + } + + // Public + + get resource() + { + return this._resource; + } + + get domNode() + { + return this._domNode; + } + + get title() + { + return this._title; + } + + get className() + { + if (!this._className) + this._className = this._generateClassName(); + + return this._className; + } + + get searchTerm() + { + return this._searchTerm; + } + + get sourceCodeTextRange() + { + return this._sourceCodeTextRange; + } + + saveIdentityToCookie(cookie) + { + cookie[WebInspector.DOMSearchMatchObject.URLCookieKey] = this._resource.url.hash; + cookie[WebInspector.DOMSearchMatchObject.TitleKey] = this._title; + var textRange = this._sourceCodeTextRange.textRange; + cookie[WebInspector.DOMSearchMatchObject.TextRangeKey] = [textRange.startLine, textRange.startColumn, textRange.endLine, textRange.endColumn].join(); + } + + // Private + + _generateClassName() + { + switch (this._domNode.nodeType()) { + case Node.ELEMENT_NODE: + return WebInspector.DOMSearchMatchObject.DOMMatchElementIconStyleClassName; + + case Node.TEXT_NODE: + return WebInspector.DOMSearchMatchObject.DOMMatchTextNodeIconStyleClassName; + + case Node.COMMENT_NODE: + return WebInspector.DOMSearchMatchObject.DOMMatchCommentIconStyleClassName; + + case Node.DOCUMENT_TYPE_NODE: + return WebInspector.DOMSearchMatchObject.DOMMatchDocumentTypeIconStyleClassName; + + case Node.CDATA_SECTION_NODE: + return WebInspector.DOMSearchMatchObject.DOMMatchCharacterDataIconStyleClassName; + + case Node.PROCESSING_INSTRUCTION_NODE: + // <rdar://problem/12800950> Need icon for DOCUMENT_FRAGMENT_NODE and PROCESSING_INSTRUCTION_NODE + return WebInspector.DOMSearchMatchObject.DOMMatchDocumentTypeIconStyleClassName; + + default: + console.error("Unknown DOM node type: ", this._domNode.nodeType()); + return WebInspector.DOMSearchMatchObject.DOMMatchNodeIconStyleClassName; + } + } +}; + +WebInspector.DOMSearchMatchObject.DOMMatchElementIconStyleClassName = "dom-match-element-icon"; +WebInspector.DOMSearchMatchObject.DOMMatchTextNodeIconStyleClassName = "dom-match-text-node-icon"; +WebInspector.DOMSearchMatchObject.DOMMatchCommentIconStyleClassName = "dom-match-comment-icon"; +WebInspector.DOMSearchMatchObject.DOMMatchDocumentTypeIconStyleClassName = "dom-match-document-type-icon"; +WebInspector.DOMSearchMatchObject.DOMMatchCharacterDataIconStyleClassName = "dom-match-character-data-icon"; +WebInspector.DOMSearchMatchObject.DOMMatchNodeIconStyleClassName = "dom-match-node-icon"; + +WebInspector.DOMSearchMatchObject.TypeIdentifier = "dom-search-match-object"; +WebInspector.DOMSearchMatchObject.URLCookieKey = "resource-url"; +WebInspector.DOMSearchMatchObject.TitleKey = "title"; +WebInspector.DOMSearchMatchObject.TextRangeKey = "text-range"; diff --git a/Source/WebInspectorUI/UserInterface/Models/DOMStorageObject.js b/Source/WebInspectorUI/UserInterface/Models/DOMStorageObject.js new file mode 100644 index 000000000..ac03b8d1b --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/DOMStorageObject.js @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2013 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. + */ + +WebInspector.DOMStorageObject = class DOMStorageObject extends WebInspector.Object +{ + constructor(id, host, isLocalStorage) + { + super(); + + this._id = id; + this._host = host; + this._isLocalStorage = isLocalStorage; + this._entries = new Map; + } + + // Public + + get id() { return this._id; } + get host() { return this._host; } + get entries() { return this._entries; } + + saveIdentityToCookie(cookie) + { + cookie[WebInspector.DOMStorageObject.HostCookieKey] = this.host; + cookie[WebInspector.DOMStorageObject.LocalStorageCookieKey] = this.isLocalStorage(); + } + + isLocalStorage() + { + return this._isLocalStorage; + } + + getEntries(callback) + { + function innerCallback(error, entries) + { + if (error) + return; + + for (let [key, value] of entries) { + if (!key || !value) + continue; + + this._entries.set(key, value); + } + + callback(error, entries); + } + + DOMStorageAgent.getDOMStorageItems(this._id, innerCallback.bind(this)); + } + + removeItem(key) + { + DOMStorageAgent.removeDOMStorageItem(this._id, key); + } + + setItem(key, value) + { + DOMStorageAgent.setDOMStorageItem(this._id, key, value); + } + + itemsCleared() + { + this._entries.clear(); + this.dispatchEventToListeners(WebInspector.DOMStorageObject.Event.ItemsCleared); + } + + itemRemoved(key) + { + this._entries.delete(key); + this.dispatchEventToListeners(WebInspector.DOMStorageObject.Event.ItemRemoved, {key}); + } + + itemAdded(key, value) + { + this._entries.set(key, value); + this.dispatchEventToListeners(WebInspector.DOMStorageObject.Event.ItemAdded, {key, value}); + } + + itemUpdated(key, oldValue, value) + { + this._entries.set(key, value); + this.dispatchEventToListeners(WebInspector.DOMStorageObject.Event.ItemUpdated, {key, oldValue, value}); + } +}; + +WebInspector.DOMStorageObject.TypeIdentifier = "dom-storage"; +WebInspector.DOMStorageObject.HostCookieKey = "dom-storage-object-host"; +WebInspector.DOMStorageObject.LocalStorageCookieKey = "dom-storage-object-local-storage"; + +WebInspector.DOMStorageObject.Event = { + ItemsCleared: "dom-storage-object-items-cleared", + ItemAdded: "dom-storage-object-item-added", + ItemRemoved: "dom-storage-object-item-removed", + ItemUpdated: "dom-storage-object-updated", +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/DOMTree.js b/Source/WebInspectorUI/UserInterface/Models/DOMTree.js new file mode 100644 index 000000000..dfabd481f --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/DOMTree.js @@ -0,0 +1,309 @@ +/* + * Copyright (C) 2013 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. + */ + +WebInspector.DOMTree = class DOMTree extends WebInspector.Object +{ + constructor(frame) + { + super(); + + this._frame = frame; + + this._rootDOMNode = null; + this._requestIdentifier = 0; + this._contentFlowCollection = new WebInspector.Collection(WebInspector.Collection.TypeVerifier.ContentFlow); + + this._frame.addEventListener(WebInspector.Frame.Event.PageExecutionContextChanged, this._framePageExecutionContextChanged, this); + + WebInspector.domTreeManager.addEventListener(WebInspector.DOMTreeManager.Event.DocumentUpdated, this._documentUpdated, this); + + // Only add extra event listeners when not the main frame. Since DocumentUpdated is enough for the main frame. + if (!this._frame.isMainFrame()) { + WebInspector.domTreeManager.addEventListener(WebInspector.DOMTreeManager.Event.NodeRemoved, this._nodeRemoved, this); + this._frame.addEventListener(WebInspector.Frame.Event.MainResourceDidChange, this._frameMainResourceDidChange, this); + } + + WebInspector.domTreeManager.addEventListener(WebInspector.DOMTreeManager.Event.ContentFlowListWasUpdated, this._contentFlowListWasUpdated, this); + WebInspector.domTreeManager.addEventListener(WebInspector.DOMTreeManager.Event.ContentFlowWasAdded, this._contentFlowWasAdded, this); + WebInspector.domTreeManager.addEventListener(WebInspector.DOMTreeManager.Event.ContentFlowWasRemoved, this._contentFlowWasRemoved, this); + } + + // Public + + get frame() { return this._frame; } + get contentFlowCollection() { return this._contentFlowCollection; } + + disconnect() + { + WebInspector.domTreeManager.removeEventListener(null, null, this); + this._frame.removeEventListener(null, null, this); + } + + invalidate() + { + // Set to null so it is fetched again next time requestRootDOMNode is called. + this._rootDOMNode = null; + + // Clear the pending callbacks. It is the responsibility of the client to listen for + // the RootDOMNodeInvalidated event and request the root DOM node again. + this._pendingRootDOMNodeRequests = null; + + if (this._invalidateTimeoutIdentifier) + return; + + function performInvalidate() + { + this._invalidateTimeoutIdentifier = undefined; + + this.dispatchEventToListeners(WebInspector.DOMTree.Event.RootDOMNodeInvalidated); + } + + // Delay the invalidation on a timeout to coalesce multiple calls to invalidate. + this._invalidateTimeoutIdentifier = setTimeout(performInvalidate.bind(this), 0); + } + + requestRootDOMNode(callback) + { + console.assert(typeof callback === "function"); + if (typeof callback !== "function") + return; + + if (this._rootDOMNode) { + callback(this._rootDOMNode); + return; + } + + if (!this._frame.isMainFrame() && !this._frame.pageExecutionContext) { + this._rootDOMNodeRequestWaitingForExecutionContext = true; + if (!this._pendingRootDOMNodeRequests) + this._pendingRootDOMNodeRequests = []; + this._pendingRootDOMNodeRequests.push(callback); + return; + } + + if (this._pendingRootDOMNodeRequests) { + this._pendingRootDOMNodeRequests.push(callback); + return; + } + + this._pendingRootDOMNodeRequests = [callback]; + this._requestRootDOMNode(); + } + + requestContentFlowList() + { + this.requestRootDOMNode(function(rootNode) { + // Let the backend know we are interested about the named flow events for this document. + WebInspector.domTreeManager.getNamedFlowCollection(rootNode.id); + }); + } + + // Private + + _requestRootDOMNode() + { + console.assert(this._frame.isMainFrame() || this._frame.pageExecutionContext); + console.assert(this._pendingRootDOMNodeRequests.length); + + // Bump the request identifier. This prevents pending callbacks for previous requests from completing. + var requestIdentifier = ++this._requestIdentifier; + + function rootObjectAvailable(error, result) + { + // Check to see if we have been invalidated (if the callbacks were cleared). + if (!this._pendingRootDOMNodeRequests || requestIdentifier !== this._requestIdentifier) + return; + + if (error) { + console.error(JSON.stringify(error)); + + this._rootDOMNode = null; + dispatchCallbacks.call(this); + return; + } + + // Convert the RemoteObject to a DOMNode by asking the backend to push it to us. + var remoteObject = WebInspector.RemoteObject.fromPayload(result); + remoteObject.pushNodeToFrontend(rootDOMNodeAvailable.bind(this, remoteObject)); + } + + function rootDOMNodeAvailable(remoteObject, nodeId) + { + remoteObject.release(); + + // Check to see if we have been invalidated (if the callbacks were cleared). + if (!this._pendingRootDOMNodeRequests || requestIdentifier !== this._requestIdentifier) + return; + + if (!nodeId) { + this._rootDOMNode = null; + dispatchCallbacks.call(this); + return; + } + + this._rootDOMNode = WebInspector.domTreeManager.nodeForId(nodeId); + + console.assert(this._rootDOMNode); + if (!this._rootDOMNode) { + dispatchCallbacks.call(this); + return; + } + + // Request the child nodes since the root node is often not shown in the UI, + // and the child nodes will be needed immediately. + this._rootDOMNode.getChildNodes(dispatchCallbacks.bind(this)); + } + + function mainDocumentAvailable(document) + { + this._rootDOMNode = document; + + dispatchCallbacks.call(this); + } + + function dispatchCallbacks() + { + // Check to see if we have been invalidated (if the callbacks were cleared). + if (!this._pendingRootDOMNodeRequests || requestIdentifier !== this._requestIdentifier) + return; + + for (var i = 0; i < this._pendingRootDOMNodeRequests.length; ++i) + this._pendingRootDOMNodeRequests[i](this._rootDOMNode); + this._pendingRootDOMNodeRequests = null; + } + + // For the main frame we can use the more straight forward requestDocument function. For + // child frames we need to do a more roundabout approach since the protocol does not include + // a specific way to request a document given a frame identifier. The child frame approach + // involves evaluating the JavaScript "document" and resolving that into a DOMNode. + if (this._frame.isMainFrame()) + WebInspector.domTreeManager.requestDocument(mainDocumentAvailable.bind(this)); + else { + var contextId = this._frame.pageExecutionContext.id; + RuntimeAgent.evaluate.invoke({expression: appendWebInspectorSourceURL("document"), objectGroup: "", includeCommandLineAPI: false, doNotPauseOnExceptionsAndMuteConsole: true, contextId, returnByValue: false, generatePreview: false}, rootObjectAvailable.bind(this)); + } + } + + _nodeRemoved(event) + { + console.assert(!this._frame.isMainFrame()); + + if (event.data.node !== this._rootDOMNode) + return; + + this.invalidate(); + } + + _documentUpdated(event) + { + this.invalidate(); + } + + _frameMainResourceDidChange(event) + { + console.assert(!this._frame.isMainFrame()); + + this.invalidate(); + } + + _framePageExecutionContextChanged(event) + { + if (this._rootDOMNodeRequestWaitingForExecutionContext) { + console.assert(this._frame.pageExecutionContext); + console.assert(this._pendingRootDOMNodeRequests && this._pendingRootDOMNodeRequests.length); + + this._rootDOMNodeRequestWaitingForExecutionContext = false; + + this._requestRootDOMNode(); + } + } + + _isContentFlowInCurrentDocument(flow) + { + return this._rootDOMNode && this._rootDOMNode.id === flow.documentNodeIdentifier; + } + + _contentFlowListWasUpdated(event) + { + if (!this._rootDOMNode || this._rootDOMNode.id !== event.data.documentNodeIdentifier) + return; + + // Assume that all the flows have been removed. + let deletedFlows = new Set(this._contentFlowCollection.items); + let newFlows = new Set; + for (let flow of event.data.flows) { + // All the flows received from WebKit are part of the same document. + console.assert(this._isContentFlowInCurrentDocument(flow)); + + if (this._contentFlowCollection.items.has(flow)) { + // Remove the flow name from the deleted list. + console.assert(deletedFlows.has(flow)); + deletedFlows.delete(flow); + } else { + this._contentFlowCollection.add(flow); + newFlows.add(flow); + } + } + + for (let flow of deletedFlows) + this._contentFlowCollection.remove(flow); + + // Send update events to listeners. + + for (let flow of deletedFlows) + this.dispatchEventToListeners(WebInspector.DOMTree.Event.ContentFlowWasRemoved, {flow}); + + for (let flow of newFlows) + this.dispatchEventToListeners(WebInspector.DOMTree.Event.ContentFlowWasAdded, {flow}); + } + + _contentFlowWasAdded(event) + { + let flow = event.data.flow; + if (!this._isContentFlowInCurrentDocument(flow)) + return; + + this._contentFlowCollection.add(flow); + + this.dispatchEventToListeners(WebInspector.DOMTree.Event.ContentFlowWasAdded, {flow}); + } + + _contentFlowWasRemoved(event) + { + let flow = event.data.flow; + if (!this._isContentFlowInCurrentDocument(flow)) + return; + + this._contentFlowCollection.remove(flow); + + this.dispatchEventToListeners(WebInspector.DOMTree.Event.ContentFlowWasRemoved, {flow}); + } +}; + +WebInspector.DOMTree.Event = { + RootDOMNodeInvalidated: "dom-tree-root-dom-node-invalidated", + ContentFlowWasAdded: "dom-tree-content-flow-was-added", + ContentFlowWasRemoved: "dom-tree-content-flow-was-removed" +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/DatabaseObject.js b/Source/WebInspectorUI/UserInterface/Models/DatabaseObject.js new file mode 100644 index 000000000..961e41aa9 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/DatabaseObject.js @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2013 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. + */ + +WebInspector.DatabaseObject = class DatabaseObject extends WebInspector.Object +{ + constructor(id, host, name, version) + { + super(); + + this._id = id; + this._host = host ? host : WebInspector.UIString("Local File"); + this._name = name; + this._version = version; + } + + // Public + + get id() { return this._id; } + get host() { return this._host; } + get name() { return this._name; } + get version() { return this._version; } + + saveIdentityToCookie(cookie) + { + cookie[WebInspector.DatabaseObject.HostCookieKey] = this.host; + cookie[WebInspector.DatabaseObject.NameCookieKey] = this.name; + } + + getTableNames(callback) + { + function sortingCallback(error, names) + { + if (!error) + callback(names.sort()); + } + + DatabaseAgent.getDatabaseTableNames(this._id, sortingCallback); + } + + executeSQL(query, successCallback, errorCallback) + { + function queryCallback(error, columnNames, values, sqlError) + { + if (error) { + errorCallback(WebInspector.UIString("An unexpected error occurred.")); + return; + } + + if (sqlError) { + switch (sqlError.code) { + case SQLException.VERSION_ERR: + errorCallback(WebInspector.UIString("Database no longer has expected version.")); + break; + case SQLException.TOO_LARGE_ERR: + errorCallback(WebInspector.UIString("Data returned from the database is too large.")); + break; + default: + errorCallback(WebInspector.UIString("An unexpected error occurred.")); + break; + } + return; + } + + successCallback(columnNames, values); + } + + DatabaseAgent.executeSQL(this._id, query, queryCallback); + } +}; + +WebInspector.DatabaseObject.TypeIdentifier = "database"; +WebInspector.DatabaseObject.HostCookieKey = "database-object-host"; +WebInspector.DatabaseObject.NameCookieKey = "database-object-name"; diff --git a/Source/WebInspectorUI/UserInterface/Models/DatabaseTableObject.js b/Source/WebInspectorUI/UserInterface/Models/DatabaseTableObject.js new file mode 100644 index 000000000..98513eb74 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/DatabaseTableObject.js @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2013 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. + */ + +WebInspector.DatabaseTableObject = class DatabaseTableObject extends WebInspector.Object +{ + constructor(name, database) + { + super(); + + console.assert(database instanceof WebInspector.DatabaseObject); + + this._name = name; + this._database = database; + } + + // Public + + get name() { return this._name; } + get database() { return this._database; } + + saveIdentityToCookie(cookie) + { + cookie[WebInspector.DatabaseTableObject.NameCookieKey] = this.name; + } +}; + +WebInspector.DatabaseTableObject.TypeIdentifier = "database-table"; +WebInspector.DatabaseTableObject.NameCookieKey = "database-table-object-name"; diff --git a/Source/WebInspectorUI/UserInterface/Models/DebuggerDashboard.js b/Source/WebInspectorUI/UserInterface/Models/DebuggerDashboard.js new file mode 100644 index 000000000..0c4258c91 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/DebuggerDashboard.js @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2014 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. + */ + +WebInspector.DebuggerDashboard = class DebuggerDashboard extends WebInspector.Object +{ +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/DebuggerData.js b/Source/WebInspectorUI/UserInterface/Models/DebuggerData.js new file mode 100644 index 000000000..394b8ed45 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/DebuggerData.js @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2016 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. + */ + +WebInspector.DebuggerData = class DebuggerData extends WebInspector.Object +{ + constructor(target) + { + super(); + + console.assert(target instanceof WebInspector.Target); + + this._target = target; + + this._paused = false; + this._pausing = false; + this._pauseReason = null; + this._pauseData = null; + this._callFrames = []; + this._asyncStackTrace = null; + + this._scriptIdMap = new Map; + this._scriptContentIdentifierMap = new Map; + + this._makePausingAfterNextResume = false; + } + + // Public + + get target() { return this._target; } + get paused() { return this._paused; } + get pausing() { return this._pausing; } + get pauseReason() { return this._pauseReason; } + get pauseData() { return this._pauseData; } + get callFrames() { return this._callFrames; } + get asyncStackTrace() { return this._asyncStackTrace; } + + get scripts() + { + return Array.from(this._scriptIdMap.values()); + } + + scriptForIdentifier(id) + { + return this._scriptIdMap.get(id); + } + + scriptsForURL(url) + { + return this._scriptContentIdentifierMap.get(url) || []; + } + + // Protected (Called by DebuggerManager) + + reset() + { + this._scriptIdMap.clear(); + } + + addScript(script) + { + this._scriptIdMap.set(script.id, script); + + if (script.contentIdentifier) { + let scripts = this._scriptContentIdentifierMap.get(script.contentIdentifier); + if (!scripts) { + scripts = []; + this._scriptContentIdentifierMap.set(script.contentIdentifier, scripts); + } + scripts.push(script); + } + } + + pauseIfNeeded() + { + if (this._paused || this._pausing) + return Promise.resolve(); + + this._pausing = true; + + return this._target.DebuggerAgent.pause(); + } + + resumeIfNeeded() + { + if (!this._paused && !this._pausing) + return Promise.resolve(); + + this._pausing = false; + + return this._target.DebuggerAgent.resume(); + } + + continueUntilNextRunLoop() + { + if (!this._paused || this._pausing) + return Promise.resolve(); + + // The backend will automatically start pausing + // after resuming, so we need to match that here. + this._makePausingAfterNextResume = true; + + return this._target.DebuggerAgent.continueUntilNextRunLoop(); + } + + updateForPause(callFrames, pauseReason, pauseData, asyncStackTrace) + { + this._paused = true; + this._pausing = false; + this._pauseReason = pauseReason; + this._pauseData = pauseData; + this._callFrames = callFrames; + this._asyncStackTrace = asyncStackTrace; + + // We paused, no need for auto-pausing. + this._makePausingAfterNextResume = false; + } + + updateForResume() + { + this._paused = false; + this._pausing = false; + this._pauseReason = null; + this._pauseData = null; + this._callFrames = []; + this._asyncStackTrace = null; + + // We resumed, but may be auto-pausing. + if (this._makePausingAfterNextResume) { + this._makePausingAfterNextResume = false; + this._pausing = true; + } + } +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/DefaultDashboard.js b/Source/WebInspectorUI/UserInterface/Models/DefaultDashboard.js new file mode 100644 index 000000000..33b8d6d8a --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/DefaultDashboard.js @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2013, 2014 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 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.DefaultDashboard = class DefaultDashboard extends WebInspector.Object +{ + constructor() + { + super(); + + this._waitingForFirstMainResourceToStartTrackingSize = true; + + // Necessary event required to track page load time and resource sizes. + WebInspector.Frame.addEventListener(WebInspector.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this); + WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.Event.CapturingStopped, this._capturingStopped, this); + + // Necessary events required to track load of resources. + WebInspector.Frame.addEventListener(WebInspector.Frame.Event.ResourceWasAdded, this._resourceWasAdded, this); + WebInspector.Target.addEventListener(WebInspector.Target.Event.ResourceAdded, this._resourceWasAdded, this); + WebInspector.frameResourceManager.addEventListener(WebInspector.FrameResourceManager.Event.FrameWasAdded, this._frameWasAdded, this); + + // Necessary events required to track console messages. + var logManager = WebInspector.logManager; + logManager.addEventListener(WebInspector.LogManager.Event.Cleared, this._consoleWasCleared, this); + logManager.addEventListener(WebInspector.LogManager.Event.MessageAdded, this._consoleMessageAdded, this); + logManager.addEventListener(WebInspector.LogManager.Event.PreviousMessageRepeatCountUpdated, this._consoleMessageWasRepeated, this); + + this._resourcesCount = 0; + this._resourcesSize = 0; + this._time = 0; + this._logs = 0; + this._errors = 0; + this._issues = 0; + } + + // Public + + get resourcesCount() + { + return this._resourcesCount; + } + + set resourcesCount(value) + { + this._resourcesCount = value; + this._dataDidChange(); + } + + get resourcesSize() + { + return this._resourcesSize; + } + + set resourcesSize(value) + { + this._resourcesSize = value; + this._dataDidChange(); + } + + get time() + { + return this._time; + } + + set time(value) + { + this._time = value; + this._dataDidChange(); + } + + get logs() + { + return this._logs; + } + + set logs(value) + { + this._logs = value; + this._dataDidChange(); + } + + get errors() + { + return this._errors; + } + + set errors(value) + { + this._errors = value; + this._dataDidChange(); + } + + get issues() + { + return this._issues; + } + + set issues(value) + { + this._issues = value; + this._dataDidChange(); + } + + // Private + + _dataDidChange() + { + this.dispatchEventToListeners(WebInspector.DefaultDashboard.Event.DataDidChange); + } + + _mainResourceDidChange(event) + { + console.assert(event.target instanceof WebInspector.Frame); + + if (!event.target.isMainFrame()) + return; + + this._time = 0; + this._resourcesCount = 1; + this._resourcesSize = WebInspector.frameResourceManager.mainFrame.mainResource.size || 0; + + // We should only track resource sizes on fresh loads. + if (this._waitingForFirstMainResourceToStartTrackingSize) { + this._waitingForFirstMainResourceToStartTrackingSize = false; + WebInspector.Resource.addEventListener(WebInspector.Resource.Event.SizeDidChange, this._resourceSizeDidChange, this); + } + + this._dataDidChange(); + this._startUpdatingTime(); + } + + _capturingStopped(event) + { + // If recording stops, we should stop the timer if it hasn't stopped already. + this._stopUpdatingTime(); + } + + _resourceWasAdded(event) + { + ++this.resourcesCount; + } + + _frameWasAdded(event) + { + ++this.resourcesCount; + } + + _resourceSizeDidChange(event) + { + if (event.target.urlComponents.scheme === "data") + return; + this.resourcesSize += event.target.size - event.data.previousSize; + } + + _startUpdatingTime() + { + this._stopUpdatingTime(); + + this.time = 0; + + this._timelineBaseTime = Date.now(); + this._timeIntervalDelay = 50; + this._timeIntervalIdentifier = setInterval(this._updateTime.bind(this), this._timeIntervalDelay); + } + + _stopUpdatingTime() + { + if (!this._timeIntervalIdentifier) + return; + + clearInterval(this._timeIntervalIdentifier); + this._timeIntervalIdentifier = undefined; + } + + _updateTime() + { + var duration = Date.now() - this._timelineBaseTime; + + var timeIntervalDelay = this._timeIntervalDelay; + if (duration >= 1000) // 1 second + timeIntervalDelay = 100; + else if (duration >= 60000) // 60 seconds + timeIntervalDelay = 1000; + else if (duration >= 3600000) // 1 minute + timeIntervalDelay = 10000; + + if (timeIntervalDelay !== this._timeIntervalDelay) { + this._timeIntervalDelay = timeIntervalDelay; + + clearInterval(this._timeIntervalIdentifier); + this._timeIntervalIdentifier = setInterval(this._updateTime.bind(this), this._timeIntervalDelay); + } + + var mainFrame = WebInspector.frameResourceManager.mainFrame; + var mainFrameStartTime = mainFrame.mainResource.firstTimestamp; + var mainFrameLoadEventTime = mainFrame.loadEventTimestamp; + + if (isNaN(mainFrameStartTime) || isNaN(mainFrameLoadEventTime)) { + this.time = duration / 1000; + return; + } + + this.time = mainFrameLoadEventTime - mainFrameStartTime; + + this._stopUpdatingTime(); + } + + _consoleMessageAdded(event) + { + var message = event.data.message; + this._lastConsoleMessageType = message.level; + this._incrementConsoleMessageType(message.level, message.repeatCount); + } + + _consoleMessageWasRepeated(event) + { + this._incrementConsoleMessageType(this._lastConsoleMessageType, 1); + } + + _incrementConsoleMessageType(type, increment) + { + switch (type) { + case WebInspector.ConsoleMessage.MessageLevel.Log: + case WebInspector.ConsoleMessage.MessageLevel.Info: + case WebInspector.ConsoleMessage.MessageLevel.Debug: + this.logs += increment; + break; + case WebInspector.ConsoleMessage.MessageLevel.Warning: + this.issues += increment; + break; + case WebInspector.ConsoleMessage.MessageLevel.Error: + this.errors += increment; + break; + } + } + + _consoleWasCleared(event) + { + this._logs = 0; + this._issues = 0; + this._errors = 0; + this._dataDidChange(); + } +}; + +WebInspector.DefaultDashboard.Event = { + DataDidChange: "default-dashboard-data-did-change" +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/ExecutionContext.js b/Source/WebInspectorUI/UserInterface/Models/ExecutionContext.js new file mode 100644 index 000000000..383fc8173 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/ExecutionContext.js @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2013 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. + */ + +WebInspector.ExecutionContext = class ExecutionContext extends WebInspector.Object +{ + constructor(target, id, name, isPageContext, frame) + { + super(); + + console.assert(target instanceof WebInspector.Target); + console.assert(typeof id === "number" || id === WebInspector.RuntimeManager.TopLevelExecutionContextIdentifier); + console.assert(typeof name === "string"); + + this._target = target; + this._id = id; + this._name = name; + this._isPageContext = isPageContext || false; + this._frame = frame || null; + } + + // Public + + get target() { return this._target; } + get id() { return this._id; } + get name() { return this._name; } + get isPageContext() { return this._isPageContext; } + get frame() { return this._frame; } +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/ExecutionContextList.js b/Source/WebInspectorUI/UserInterface/Models/ExecutionContextList.js new file mode 100644 index 000000000..e094fc742 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/ExecutionContextList.js @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2013 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. + */ + +WebInspector.ExecutionContextList = class ExecutionContextList extends WebInspector.Object +{ + constructor() + { + super(); + + this._contexts = []; + this._pageExecutionContext = null; + } + + // Public + + get pageExecutionContext() + { + return this._pageExecutionContext; + } + + get contexts() + { + return this._contexts; + } + + add(context) + { + // FIXME: The backend sends duplicate page context execution contexts with the same id. Why? + if (context.isPageContext && this._pageExecutionContext) { + console.assert(context.id === this._pageExecutionContext.id); + return false; + } + + this._contexts.push(context); + + if (context.isPageContext) { + console.assert(!this._pageExecutionContext); + this._pageExecutionContext = context; + return true; + } + + return false; + } + + clear() + { + this._contexts = []; + this._pageExecutionContext = null; + } +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/FPSInstrument.js b/Source/WebInspectorUI/UserInterface/Models/FPSInstrument.js new file mode 100644 index 000000000..a5e4452a7 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/FPSInstrument.js @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2015 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. + */ + +WebInspector.FPSInstrument = class FPSInstrument extends WebInspector.Instrument +{ + constructor() + { + super(); + + console.assert(WebInspector.FPSInstrument.supported()); + } + + // Static + + static supported() + { + // COMPATIBILITY (iOS 8): TimelineAgent.EventType.RenderingFrame did not exist. + return window.TimelineAgent && TimelineAgent.EventType.RenderingFrame; + } + + // Protected + + get timelineRecordType() + { + return WebInspector.TimelineRecord.Type.RenderingFrame; + } +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/Frame.js b/Source/WebInspectorUI/UserInterface/Models/Frame.js new file mode 100644 index 000000000..d32333a54 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/Frame.js @@ -0,0 +1,509 @@ +/* + * Copyright (C) 2013 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. + */ + +WebInspector.Frame = class Frame extends WebInspector.Object +{ + constructor(id, name, securityOrigin, loaderIdentifier, mainResource) + { + super(); + + console.assert(id); + + this._id = id; + + this._name = null; + this._securityOrigin = null; + + this._resourceCollection = new WebInspector.ResourceCollection; + this._provisionalResourceCollection = new WebInspector.ResourceCollection; + this._extraScriptCollection = new WebInspector.Collection(WebInspector.Collection.TypeVerifier.Script); + + this._childFrameCollection = new WebInspector.Collection(WebInspector.Collection.TypeVerifier.Frame); + this._childFrameIdentifierMap = new Map; + + this._parentFrame = null; + this._isMainFrame = false; + + this._domContentReadyEventTimestamp = NaN; + this._loadEventTimestamp = NaN; + + this._executionContextList = new WebInspector.ExecutionContextList; + + this.initialize(name, securityOrigin, loaderIdentifier, mainResource); + } + + // Public + + get resourceCollection() { return this._resourceCollection; } + get extraScriptCollection() { return this._extraScriptCollection; } + get childFrameCollection() { return this._childFrameCollection; } + + initialize(name, securityOrigin, loaderIdentifier, mainResource) + { + console.assert(loaderIdentifier); + console.assert(mainResource); + + var oldName = this._name; + var oldSecurityOrigin = this._securityOrigin; + var oldMainResource = this._mainResource; + + this._name = name || null; + this._securityOrigin = securityOrigin || null; + this._loaderIdentifier = loaderIdentifier || null; + + this._mainResource = mainResource; + this._mainResource._parentFrame = this; + + if (oldMainResource && this._mainResource !== oldMainResource) + this._disassociateWithResource(oldMainResource); + + this.removeAllResources(); + this.removeAllChildFrames(); + this.clearExecutionContexts(); + this.clearProvisionalLoad(); + + if (this._mainResource !== oldMainResource) + this._dispatchMainResourceDidChangeEvent(oldMainResource); + + if (this._securityOrigin !== oldSecurityOrigin) + this.dispatchEventToListeners(WebInspector.Frame.Event.SecurityOriginDidChange, {oldSecurityOrigin}); + + if (this._name !== oldName) + this.dispatchEventToListeners(WebInspector.Frame.Event.NameDidChange, {oldName}); + } + + startProvisionalLoad(provisionalMainResource) + { + console.assert(provisionalMainResource); + + this._provisionalMainResource = provisionalMainResource; + this._provisionalMainResource._parentFrame = this; + + this._provisionalLoaderIdentifier = provisionalMainResource.loaderIdentifier; + + this._provisionalResourceCollection.clear(); + + this.dispatchEventToListeners(WebInspector.Frame.Event.ProvisionalLoadStarted); + } + + commitProvisionalLoad(securityOrigin) + { + console.assert(this._provisionalMainResource); + console.assert(this._provisionalLoaderIdentifier); + if (!this._provisionalLoaderIdentifier) + return; + + var oldSecurityOrigin = this._securityOrigin; + var oldMainResource = this._mainResource; + + this._securityOrigin = securityOrigin || null; + this._loaderIdentifier = this._provisionalLoaderIdentifier; + this._mainResource = this._provisionalMainResource; + + this._domContentReadyEventTimestamp = NaN; + this._loadEventTimestamp = NaN; + + if (oldMainResource && this._mainResource !== oldMainResource) + this._disassociateWithResource(oldMainResource); + + this.removeAllResources(); + + this._resourceCollection = this._provisionalResourceCollection; + this._provisionalResourceCollection = new WebInspector.ResourceCollection; + this._extraScriptCollection.clear(); + + this.clearExecutionContexts(true); + this.clearProvisionalLoad(true); + this.removeAllChildFrames(); + + this.dispatchEventToListeners(WebInspector.Frame.Event.ProvisionalLoadCommitted); + + if (this._mainResource !== oldMainResource) + this._dispatchMainResourceDidChangeEvent(oldMainResource); + + if (this._securityOrigin !== oldSecurityOrigin) + this.dispatchEventToListeners(WebInspector.Frame.Event.SecurityOriginDidChange, {oldSecurityOrigin}); + } + + clearProvisionalLoad(skipProvisionalLoadClearedEvent) + { + if (!this._provisionalLoaderIdentifier) + return; + + this._provisionalLoaderIdentifier = null; + this._provisionalMainResource = null; + this._provisionalResourceCollection.clear(); + + if (!skipProvisionalLoadClearedEvent) + this.dispatchEventToListeners(WebInspector.Frame.Event.ProvisionalLoadCleared); + } + + get id() + { + return this._id; + } + + get loaderIdentifier() + { + return this._loaderIdentifier; + } + + get provisionalLoaderIdentifier() + { + return this._provisionalLoaderIdentifier; + } + + get name() + { + return this._name; + } + + get securityOrigin() + { + return this._securityOrigin; + } + + get url() + { + return this._mainResource._url; + } + + get domTree() + { + if (!this._domTree) + this._domTree = new WebInspector.DOMTree(this); + return this._domTree; + } + + get pageExecutionContext() + { + return this._executionContextList.pageExecutionContext; + } + + get executionContextList() + { + return this._executionContextList; + } + + clearExecutionContexts(committingProvisionalLoad) + { + if (this._executionContextList.contexts.length) { + let contexts = this._executionContextList.contexts.slice(); + this._executionContextList.clear(); + this.dispatchEventToListeners(WebInspector.Frame.Event.ExecutionContextsCleared, {committingProvisionalLoad: !!committingProvisionalLoad, contexts}); + } + } + + addExecutionContext(context) + { + var changedPageContext = this._executionContextList.add(context); + + if (changedPageContext) + this.dispatchEventToListeners(WebInspector.Frame.Event.PageExecutionContextChanged); + } + + get mainResource() + { + return this._mainResource; + } + + get provisionalMainResource() + { + return this._provisionalMainResource; + } + + get parentFrame() + { + return this._parentFrame; + } + + get domContentReadyEventTimestamp() + { + return this._domContentReadyEventTimestamp; + } + + get loadEventTimestamp() + { + return this._loadEventTimestamp; + } + + isMainFrame() + { + return this._isMainFrame; + } + + markAsMainFrame() + { + this._isMainFrame = true; + } + + unmarkAsMainFrame() + { + this._isMainFrame = false; + } + + markDOMContentReadyEvent(timestamp) + { + this._domContentReadyEventTimestamp = timestamp || NaN; + } + + markLoadEvent(timestamp) + { + this._loadEventTimestamp = timestamp || NaN; + } + + isDetached() + { + var frame = this; + while (frame) { + if (frame.isMainFrame()) + return false; + frame = frame.parentFrame; + } + + return true; + } + + childFrameForIdentifier(frameId) + { + return this._childFrameIdentifierMap.get(frameId) || null; + } + + addChildFrame(frame) + { + console.assert(frame instanceof WebInspector.Frame); + if (!(frame instanceof WebInspector.Frame)) + return; + + if (frame._parentFrame === this) + return; + + if (frame._parentFrame) + frame._parentFrame.removeChildFrame(frame); + + this._childFrameCollection.add(frame); + this._childFrameIdentifierMap.set(frame._id, frame); + + frame._parentFrame = this; + + this.dispatchEventToListeners(WebInspector.Frame.Event.ChildFrameWasAdded, {childFrame: frame}); + } + + removeChildFrame(frameOrFrameId) + { + console.assert(frameOrFrameId); + + let childFrameId = frameOrFrameId; + if (childFrameId instanceof WebInspector.Frame) + childFrameId = frameOrFrameId._id; + + // Fetch the frame by id even if we were passed a WebInspector.Frame. + // We do this incase the WebInspector.Frame is a new object that isn't + // in _childFrameCollection, but the id is a valid child frame. + let childFrame = this.childFrameForIdentifier(childFrameId); + console.assert(childFrame instanceof WebInspector.Frame); + if (!(childFrame instanceof WebInspector.Frame)) + return; + + console.assert(childFrame.parentFrame === this); + + this._childFrameCollection.remove(childFrame); + this._childFrameIdentifierMap.delete(childFrame._id); + + childFrame._detachFromParentFrame(); + + this.dispatchEventToListeners(WebInspector.Frame.Event.ChildFrameWasRemoved, {childFrame}); + } + + removeAllChildFrames() + { + this._detachFromParentFrame(); + + for (let childFrame of this._childFrameCollection.items) + childFrame.removeAllChildFrames(); + + this._childFrameCollection.clear(); + this._childFrameIdentifierMap.clear(); + + this.dispatchEventToListeners(WebInspector.Frame.Event.AllChildFramesRemoved); + } + + resourceForURL(url, recursivelySearchChildFrames) + { + var resource = this._resourceCollection.resourceForURL(url); + if (resource) + return resource; + + // Check the main resources of the child frames for the requested URL. + for (let childFrame of this._childFrameCollection.items) { + resource = childFrame.mainResource; + if (resource.url === url) + return resource; + } + + if (!recursivelySearchChildFrames) + return null; + + // Recursively search resources of child frames. + for (let childFrame of this._childFrameCollection.items) { + resource = childFrame.resourceForURL(url, true); + if (resource) + return resource; + } + + return null; + } + + resourceCollectionForType(type) + { + return this._resourceCollection.resourceCollectionForType(type); + } + + addResource(resource) + { + console.assert(resource instanceof WebInspector.Resource); + if (!(resource instanceof WebInspector.Resource)) + return; + + if (resource.parentFrame === this) + return; + + if (resource.parentFrame) + resource.parentFrame.remove(resource); + + this._associateWithResource(resource); + + if (this._isProvisionalResource(resource)) { + this._provisionalResourceCollection.add(resource); + this.dispatchEventToListeners(WebInspector.Frame.Event.ProvisionalResourceWasAdded, {resource}); + } else { + this._resourceCollection.add(resource); + this.dispatchEventToListeners(WebInspector.Frame.Event.ResourceWasAdded, {resource}); + } + } + + removeResource(resource) + { + // This does not remove provisional resources. + + this._resourceCollection.remove(resource); + + this._disassociateWithResource(resource); + + this.dispatchEventToListeners(WebInspector.Frame.Event.ResourceWasRemoved, {resource}); + } + + removeAllResources() + { + // This does not remove provisional resources, use clearProvisionalLoad for that. + + let resources = this._resourceCollection.items; + if (!resources.size) + return; + + for (let resource of resources) + this._disassociateWithResource(resource); + + this._resourceCollection.clear(); + + this.dispatchEventToListeners(WebInspector.Frame.Event.AllResourcesRemoved); + } + + addExtraScript(script) + { + this._extraScriptCollection.add(script); + + this.dispatchEventToListeners(WebInspector.Frame.Event.ExtraScriptAdded, {script}); + } + + saveIdentityToCookie(cookie) + { + cookie[WebInspector.Frame.MainResourceURLCookieKey] = this.mainResource.url.hash; + cookie[WebInspector.Frame.IsMainFrameCookieKey] = this._isMainFrame; + } + + // Private + + _detachFromParentFrame() + { + if (this._domTree) { + this._domTree.disconnect(); + this._domTree = null; + } + + this._parentFrame = null; + } + + _isProvisionalResource(resource) + { + return resource.loaderIdentifier && this._provisionalLoaderIdentifier && resource.loaderIdentifier === this._provisionalLoaderIdentifier; + } + + _associateWithResource(resource) + { + console.assert(!resource._parentFrame); + if (resource._parentFrame) + return; + + resource._parentFrame = this; + } + + _disassociateWithResource(resource) + { + console.assert(resource.parentFrame === this); + if (resource.parentFrame !== this) + return; + + resource._parentFrame = null; + } + + _dispatchMainResourceDidChangeEvent(oldMainResource) + { + this.dispatchEventToListeners(WebInspector.Frame.Event.MainResourceDidChange, {oldMainResource}); + } +}; + +WebInspector.Frame.Event = { + NameDidChange: "frame-name-did-change", + SecurityOriginDidChange: "frame-security-origin-did-change", + MainResourceDidChange: "frame-main-resource-did-change", + ProvisionalLoadStarted: "frame-provisional-load-started", + ProvisionalLoadCommitted: "frame-provisional-load-committed", + ProvisionalLoadCleared: "frame-provisional-load-cleared", + ProvisionalResourceWasAdded: "frame-provisional-resource-was-added", + ResourceWasAdded: "frame-resource-was-added", + ResourceWasRemoved: "frame-resource-was-removed", + AllResourcesRemoved: "frame-all-resources-removed", + ExtraScriptAdded: "frame-extra-script-added", + ChildFrameWasAdded: "frame-child-frame-was-added", + ChildFrameWasRemoved: "frame-child-frame-was-removed", + AllChildFramesRemoved: "frame-all-child-frames-removed", + PageExecutionContextChanged: "frame-page-execution-context-changed", + ExecutionContextsCleared: "frame-execution-contexts-cleared" +}; + +WebInspector.Frame.TypeIdentifier = "Frame"; +WebInspector.Frame.MainResourceURLCookieKey = "frame-main-resource-url"; +WebInspector.Frame.IsMainFrameCookieKey = "frame-is-main-frame"; diff --git a/Source/WebInspectorUI/UserInterface/Models/GarbageCollection.js b/Source/WebInspectorUI/UserInterface/Models/GarbageCollection.js new file mode 100644 index 000000000..84a3f0b31 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/GarbageCollection.js @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2015 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. + */ + +WebInspector.GarbageCollection = class GarbageCollection extends WebInspector.Object +{ + constructor(type, startTime, endTime) + { + super(); + + console.assert(endTime >= startTime); + + this._type = type; + this._startTime = startTime; + this._endTime = endTime; + } + + // Static + + static fromPayload(payload) + { + let type = WebInspector.GarbageCollection.Type.Full; + if (payload.type === HeapAgent.GarbageCollectionType.Partial) + type = WebInspector.GarbageCollection.Type.Partial; + + return new WebInspector.GarbageCollection(type, payload.startTime, payload.endTime); + } + + // Public + + get type() { return this._type; } + get startTime() { return this._startTime; } + get endTime() { return this._endTime; } + + get duration() + { + return this._endTime - this._startTime; + } +}; + +WebInspector.GarbageCollection.Type = { + Partial: Symbol("Partial"), + Full: Symbol("Full") +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/Geometry.js b/Source/WebInspectorUI/UserInterface/Models/Geometry.js new file mode 100644 index 000000000..2fde0a558 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/Geometry.js @@ -0,0 +1,571 @@ +/* + * Copyright (C) 2013 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. + */ + +WebInspector.Point = class Point +{ + constructor(x, y) + { + this.x = x || 0; + this.y = y || 0; + } + + // Static + + static fromEvent(event) + { + return new WebInspector.Point(event.pageX, event.pageY); + } + + static fromEventInElement(event, element) + { + var wkPoint = window.webkitConvertPointFromPageToNode(element, new WebKitPoint(event.pageX, event.pageY)); + return new WebInspector.Point(wkPoint.x, wkPoint.y); + } + + // Public + + toString() + { + return "WebInspector.Point[" + this.x + "," + this.y + "]"; + } + + copy() + { + return new WebInspector.Point(this.x, this.y); + } + + equals(anotherPoint) + { + return this.x === anotherPoint.x && this.y === anotherPoint.y; + } + + distance(anotherPoint) + { + var dx = anotherPoint.x - this.x; + var dy = anotherPoint.y - this.y; + return Math.sqrt(dx * dx, dy * dy); + } +}; + +WebInspector.Size = class Size +{ + constructor(width, height) + { + this.width = width || 0; + this.height = height || 0; + } + + // Public + + toString() + { + return "WebInspector.Size[" + this.width + "," + this.height + "]"; + } + + copy() + { + return new WebInspector.Size(this.width, this.height); + } + + equals(anotherSize) + { + return this.width === anotherSize.width && this.height === anotherSize.height; + } +}; + +WebInspector.Size.ZERO_SIZE = new WebInspector.Size(0, 0); + + +WebInspector.Rect = class Rect +{ + constructor(x, y, width, height) + { + this.origin = new WebInspector.Point(x || 0, y || 0); + this.size = new WebInspector.Size(width || 0, height || 0); + } + + // Static + + static rectFromClientRect(clientRect) + { + return new WebInspector.Rect(clientRect.left, clientRect.top, clientRect.width, clientRect.height); + } + + static unionOfRects(rects) + { + var union = rects[0]; + for (var i = 1; i < rects.length; ++i) + union = union.unionWithRect(rects[i]); + return union; + } + + // Public + + toString() + { + return "WebInspector.Rect[" + [this.origin.x, this.origin.y, this.size.width, this.size.height].join(", ") + "]"; + } + + copy() + { + return new WebInspector.Rect(this.origin.x, this.origin.y, this.size.width, this.size.height); + } + + equals(anotherRect) + { + return this.origin.equals(anotherRect.origin) && this.size.equals(anotherRect.size); + } + + inset(insets) + { + return new WebInspector.Rect( + this.origin.x + insets.left, + this.origin.y + insets.top, + this.size.width - insets.left - insets.right, + this.size.height - insets.top - insets.bottom + ); + } + + pad(padding) + { + return new WebInspector.Rect( + this.origin.x - padding, + this.origin.y - padding, + this.size.width + padding * 2, + this.size.height + padding * 2 + ); + } + + minX() + { + return this.origin.x; + } + + minY() + { + return this.origin.y; + } + + midX() + { + return this.origin.x + (this.size.width / 2); + } + + midY() + { + return this.origin.y + (this.size.height / 2); + } + + maxX() + { + return this.origin.x + this.size.width; + } + + maxY() + { + return this.origin.y + this.size.height; + } + + intersectionWithRect(rect) + { + var x1 = Math.max(this.minX(), rect.minX()); + var x2 = Math.min(this.maxX(), rect.maxX()); + if (x1 > x2) + return WebInspector.Rect.ZERO_RECT; + var intersection = new WebInspector.Rect; + intersection.origin.x = x1; + intersection.size.width = x2 - x1; + var y1 = Math.max(this.minY(), rect.minY()); + var y2 = Math.min(this.maxY(), rect.maxY()); + if (y1 > y2) + return WebInspector.Rect.ZERO_RECT; + intersection.origin.y = y1; + intersection.size.height = y2 - y1; + return intersection; + } + + unionWithRect(rect) + { + var x = Math.min(this.minX(), rect.minX()); + var y = Math.min(this.minY(), rect.minY()); + var width = Math.max(this.maxX(), rect.maxX()) - x; + var height = Math.max(this.maxY(), rect.maxY()) - y; + return new WebInspector.Rect(x, y, width, height); + } + + round() + { + return new WebInspector.Rect( + Math.floor(this.origin.x), + Math.floor(this.origin.y), + Math.ceil(this.size.width), + Math.ceil(this.size.height) + ); + } +}; + +WebInspector.Rect.ZERO_RECT = new WebInspector.Rect(0, 0, 0, 0); + + +WebInspector.EdgeInsets = class EdgeInsets +{ + constructor(top, right, bottom, left) + { + console.assert(arguments.length === 1 || arguments.length === 4); + + if (arguments.length === 1) { + this.top = top; + this.right = top; + this.bottom = top; + this.left = top; + } else if (arguments.length === 4) { + this.top = top; + this.right = right; + this.bottom = bottom; + this.left = left; + } + } + + // Public + + equals(anotherInset) + { + return this.top === anotherInset.top && this.right === anotherInset.right + && this.bottom === anotherInset.bottom && this.left === anotherInset.left; + } + + copy() + { + return new WebInspector.EdgeInsets(this.top, this.right, this.bottom, this.left); + } +}; + +WebInspector.RectEdge = { + MIN_X: 0, + MIN_Y: 1, + MAX_X: 2, + MAX_Y: 3 +}; + +WebInspector.Quad = class Quad +{ + constructor(quad) + { + this.points = [ + new WebInspector.Point(quad[0], quad[1]), // top left + new WebInspector.Point(quad[2], quad[3]), // top right + new WebInspector.Point(quad[4], quad[5]), // bottom right + new WebInspector.Point(quad[6], quad[7]) // bottom left + ]; + + this.width = Math.round(Math.sqrt(Math.pow(quad[0] - quad[2], 2) + Math.pow(quad[1] - quad[3], 2))); + this.height = Math.round(Math.sqrt(Math.pow(quad[0] - quad[6], 2) + Math.pow(quad[1] - quad[7], 2))); + } + + // Public + + toProtocol() + { + return [ + this.points[0].x, this.points[0].y, + this.points[1].x, this.points[1].y, + this.points[2].x, this.points[2].y, + this.points[3].x, this.points[3].y + ]; + } +}; + +WebInspector.Polygon = class Polygon +{ + constructor(points) + { + this.points = points; + } + + // Public + + bounds() + { + var minX = Number.MAX_VALUE; + var minY = Number.MAX_VALUE; + var maxX = -Number.MAX_VALUE; + var maxY = -Number.MAX_VALUE; + for (var point of this.points) { + minX = Math.min(minX, point.x); + maxX = Math.max(maxX, point.x); + minY = Math.min(minY, point.y); + maxY = Math.max(maxY, point.y); + } + return new WebInspector.Rect(minX, minY, maxX - minX, maxY - minY); + } +}; + +WebInspector.CubicBezier = class CubicBezier +{ + constructor(x1, y1, x2, y2) + { + this._inPoint = new WebInspector.Point(x1, y1); + this._outPoint = new WebInspector.Point(x2, y2); + + // Calculate the polynomial coefficients, implicit first and last control points are (0,0) and (1,1). + this._curveInfo = { + x: {c: 3.0 * x1}, + y: {c: 3.0 * y1} + }; + + this._curveInfo.x.b = 3.0 * (x2 - x1) - this._curveInfo.x.c; + this._curveInfo.x.a = 1.0 - this._curveInfo.x.c - this._curveInfo.x.b; + + this._curveInfo.y.b = 3.0 * (y2 - y1) - this._curveInfo.y.c; + this._curveInfo.y.a = 1.0 - this._curveInfo.y.c - this._curveInfo.y.b; + } + + // Static + + static fromCoordinates(coordinates) + { + if (!coordinates || coordinates.length < 4) + return null; + + coordinates = coordinates.map(Number); + if (coordinates.includes(NaN)) + return null; + + return new WebInspector.CubicBezier(coordinates[0], coordinates[1], coordinates[2], coordinates[3]); + } + + static fromString(text) + { + if (!text || !text.length) + return null; + + var trimmedText = text.toLowerCase().replace(/\s/g, ""); + if (!trimmedText.length) + return null; + + if (Object.keys(WebInspector.CubicBezier.keywordValues).includes(trimmedText)) + return WebInspector.CubicBezier.fromCoordinates(WebInspector.CubicBezier.keywordValues[trimmedText]); + + var matches = trimmedText.match(/^cubic-bezier\(([-\d.]+),([-\d.]+),([-\d.]+),([-\d.]+)\)$/); + if (!matches) + return null; + + matches.splice(0, 1); + return WebInspector.CubicBezier.fromCoordinates(matches); + } + + // Public + + get inPoint() + { + return this._inPoint; + } + + get outPoint() + { + return this._outPoint; + } + + copy() + { + return new WebInspector.CubicBezier(this._inPoint.x, this._inPoint.y, this._outPoint.x, this._outPoint.y); + } + + toString() + { + var values = [this._inPoint.x, this._inPoint.y, this._outPoint.x, this._outPoint.y]; + for (var key in WebInspector.CubicBezier.keywordValues) { + if (Array.shallowEqual(WebInspector.CubicBezier.keywordValues[key], values)) + return key; + } + + return "cubic-bezier(" + values.join(", ") + ")"; + } + + solve(x, epsilon) + { + return this._sampleCurveY(this._solveCurveX(x, epsilon)); + } + + // Private + + _sampleCurveX(t) + { + // `ax t^3 + bx t^2 + cx t' expanded using Horner's rule. + return ((this._curveInfo.x.a * t + this._curveInfo.x.b) * t + this._curveInfo.x.c) * t; + } + + _sampleCurveY(t) + { + return ((this._curveInfo.y.a * t + this._curveInfo.y.b) * t + this._curveInfo.y.c) * t; + } + + _sampleCurveDerivativeX(t) + { + return (3.0 * this._curveInfo.x.a * t + 2.0 * this._curveInfo.x.b) * t + this._curveInfo.x.c; + } + + // Given an x value, find a parametric value it came from. + _solveCurveX(x, epsilon) + { + var t0, t1, t2, x2, d2, i; + + // First try a few iterations of Newton's method -- normally very fast. + for (t2 = x, i = 0; i < 8; i++) { + x2 = this._sampleCurveX(t2) - x; + if (Math.abs(x2) < epsilon) + return t2; + d2 = this._sampleCurveDerivativeX(t2); + if (Math.abs(d2) < 1e-6) + break; + t2 = t2 - x2 / d2; + } + + // Fall back to the bisection method for reliability. + t0 = 0.0; + t1 = 1.0; + t2 = x; + + if (t2 < t0) + return t0; + if (t2 > t1) + return t1; + + while (t0 < t1) { + x2 = this._sampleCurveX(t2); + if (Math.abs(x2 - x) < epsilon) + return t2; + if (x > x2) + t0 = t2; + else + t1 = t2; + t2 = (t1 - t0) * 0.5 + t0; + } + + // Failure. + return t2; + } +}; + +WebInspector.CubicBezier.keywordValues = { + "ease": [0.25, 0.1, 0.25, 1], + "ease-in": [0.42, 0, 1, 1], + "ease-out": [0, 0, 0.58, 1], + "ease-in-out": [0.42, 0, 0.58, 1], + "linear": [0, 0, 1, 1] +}; + +WebInspector.Spring = class Spring +{ + constructor(mass, stiffness, damping, initialVelocity) + { + this.mass = Math.max(1, mass); + this.stiffness = Math.max(1, stiffness); + this.damping = Math.max(0, damping); + this.initialVelocity = initialVelocity; + } + + // Static + + static fromValues(values) + { + if (!values || values.length < 4) + return null; + + values = values.map(Number); + if (values.includes(NaN)) + return null; + + return new WebInspector.Spring(...values); + } + + static fromString(text) + { + if (!text || !text.length) + return null; + + let trimmedText = text.toLowerCase().trim(); + if (!trimmedText.length) + return null; + + let matches = trimmedText.match(/^spring\(([\d.]+)\s+([\d.]+)\s+([\d.]+)\s+([-\d.]+)\)$/); + if (!matches) + return null; + + return WebInspector.Spring.fromValues(matches.slice(1)); + } + + // Public + + copy() + { + return new WebInspector.Spring(this.mass, this.stiffness, this.damping, this.initialVelocity); + } + + toString() + { + return `spring(${this.mass} ${this.stiffness} ${this.damping} ${this.initialVelocity})`; + } + + solve(t) + { + let w0 = Math.sqrt(this.stiffness / this.mass); + let zeta = this.damping / (2 * Math.sqrt(this.stiffness * this.mass)); + + let wd = 0; + let A = 1; + let B = -this.initialVelocity + w0; + if (zeta < 1) { + // Under-damped. + wd = w0 * Math.sqrt(1 - zeta * zeta); + A = 1; + B = (zeta * w0 + -this.initialVelocity) / wd; + } + + if (zeta < 1) // Under-damped + t = Math.exp(-t * zeta * w0) * (A * Math.cos(wd * t) + B * Math.sin(wd * t)); + else // Critically damped (ignoring over-damped case). + t = (A + B * t) * Math.exp(-t * w0); + + return 1 - t; // Map range from [1..0] to [0..1]. + } + + calculateDuration(epsilon) + { + epsilon = epsilon || 0.0001; + let t = 0; + let current = 0; + let minimum = Number.POSITIVE_INFINITY; + while (current >= epsilon || minimum >= epsilon) { + current = Math.abs(1 - this.solve(t)); // Undo the range mapping + if (minimum < epsilon && current >= epsilon) + minimum = Number.POSITIVE_INFINITY; // Spring reversed direction + else if (current < minimum) + minimum = current; + t += 0.1; + } + return t; + } +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/Gradient.js b/Source/WebInspectorUI/UserInterface/Models/Gradient.js new file mode 100644 index 000000000..f9b1c238c --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/Gradient.js @@ -0,0 +1,392 @@ +/* + * Copyright (C) 2014 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. + */ + +WebInspector.Gradient = class Gradient +{ + constructor(type, stops) + { + this.type = type; + this.stops = stops; + } + + // Static + + static fromString(cssString) + { + var type; + var openingParenthesisIndex = cssString.indexOf("("); + var typeString = cssString.substring(0, openingParenthesisIndex); + if (typeString.indexOf(WebInspector.Gradient.Types.Linear) !== -1) + type = WebInspector.Gradient.Types.Linear; + else if (typeString.indexOf(WebInspector.Gradient.Types.Radial) !== -1) + type = WebInspector.Gradient.Types.Radial; + else + return null; + + var components = []; + var currentParams = []; + var currentParam = ""; + var openParentheses = 0; + var ch = openingParenthesisIndex + 1; + var c = null; + while (c = cssString[ch]) { + if (c === "(") + openParentheses++; + if (c === ")") + openParentheses--; + + var isComma = c === ","; + var isSpace = /\s/.test(c); + + if (openParentheses === 0) { + if (isSpace) { + if (currentParam !== "") + currentParams.push(currentParam); + currentParam = ""; + } else if (isComma) { + currentParams.push(currentParam); + components.push(currentParams); + currentParams = []; + currentParam = ""; + } + } + + if (openParentheses === -1) { + currentParams.push(currentParam); + components.push(currentParams); + break; + } + + if (openParentheses > 0 || (!isComma && !isSpace)) + currentParam += c; + + ch++; + } + + if (openParentheses !== -1) + return null; + + var gradient; + if (type === WebInspector.Gradient.Types.Linear) + gradient = WebInspector.LinearGradient.fromComponents(components); + else + gradient = WebInspector.RadialGradient.fromComponents(components); + + if (gradient) + gradient.repeats = typeString.startsWith("repeating"); + + return gradient; + } + + static stopsWithComponents(components) + { + // FIXME: handle lengths. + var stops = components.map(function(component) { + while (component.length) { + var color = WebInspector.Color.fromString(component.shift()); + if (!color) + continue; + + var stop = {color}; + if (component.length && component[0].substr(-1) === "%") + stop.offset = parseFloat(component.shift()) / 100; + return stop; + } + }); + + if (!stops.length) + return null; + + for (var i = 0, count = stops.length; i < count; ++i) { + var stop = stops[i]; + + // If one of the stops failed to parse, then this is not a valid + // set of components for a gradient. So the whole thing is invalid. + if (!stop) + return null; + + if (!stop.offset) + stop.offset = i / (count - 1); + } + + return stops; + } + + // Public + + stringFromStops(stops) + { + var count = stops.length - 1; + return stops.map(function(stop, index) { + var str = stop.color; + if (stop.offset !== index / count) + str += " " + Math.round(stop.offset * 10000) / 100 + "%"; + return str; + }).join(", "); + } + + // Public + + copy() + { + // Implemented by subclasses. + } + + toString() + { + // Implemented by subclasses. + } +}; + +WebInspector.Gradient.Types = { + Linear: "linear-gradient", + Radial: "radial-gradient" +}; + +WebInspector.LinearGradient = class LinearGradient extends WebInspector.Gradient +{ + constructor(angle, stops) + { + super(WebInspector.Gradient.Types.Linear, stops); + this._angle = angle; + } + + // Static + + static fromComponents(components) + { + let angle = {value: 180, units: WebInspector.LinearGradient.AngleUnits.DEG}; + + if (components[0].length === 1 && !WebInspector.Color.fromString(components[0][0])) { + let match = components[0][0].match(/([-\d\.]+)(\w+)/); + if (!match || !Object.values(WebInspector.LinearGradient.AngleUnits).includes(match[2])) + return null; + + angle.value = parseFloat(match[1]); + angle.units = match[2]; + + components.shift(); + } else if (components[0][0] === "to") { + components[0].shift(); + switch (components[0].sort().join(" ")) { + case "top": + angle.value = 0; + break; + case "right top": + angle.value = 45; + break; + case "right": + angle.value = 90; + break; + case "bottom right": + angle.value = 135; + break; + case "bottom": + angle.value = 180; + break; + case "bottom left": + angle.value = 225; + break; + case "left": + angle.value = 270; + break; + case "left top": + angle.value = 315; + break; + default: + return null; + } + + components.shift(); + } else if (components[0].length !== 1 && !WebInspector.Color.fromString(components[0][0])) { + // If the first component is not a color, then we're dealing with a + // legacy linear gradient format that we don't support. + return null; + } + + var stops = WebInspector.Gradient.stopsWithComponents(components); + if (!stops) + return null; + + return new WebInspector.LinearGradient(angle, stops); + } + + // Public + + set angleValue(value) { this._angle.value = value; } + + get angleValue() + { + return this._angle.value.maxDecimals(2); + } + + set angleUnits(units) + { + if (units === this._angle.units) + return; + + this._angle.value = this._angleValueForUnits(units); + this._angle.units = units; + } + + get angleUnits() { return this._angle.units; } + + copy() + { + return new WebInspector.LinearGradient(this._angle, this.stops.concat()); + } + + toString() + { + let str = ""; + + let deg = this._angleValueForUnits(WebInspector.LinearGradient.AngleUnits.DEG); + if (deg === 0) + str += "to top"; + else if (deg === 45) + str += "to top right"; + else if (deg === 90) + str += "to right"; + else if (deg === 135) + str += "to bottom right"; + else if (deg === 225) + str += "to bottom left"; + else if (deg === 270) + str += "to left"; + else if (deg === 315) + str += "to top left"; + else if (deg !== 180) + str += this.angleValue + this.angleUnits; + + if (str !== "") + str += ", "; + + str += this.stringFromStops(this.stops); + + return (this.repeats ? "repeating-" : "") + this.type + "(" + str + ")"; + } + + // Private + + _angleValueForUnits(units) + { + if (units === this._angle.units) + return this._angle.value; + + let deg = 0; + + switch (this._angle.units) { + case WebInspector.LinearGradient.AngleUnits.DEG: + deg = this._angle.value; + break; + + case WebInspector.LinearGradient.AngleUnits.RAD: + deg = this._angle.value * 180 / Math.PI; + break; + + case WebInspector.LinearGradient.AngleUnits.GRAD: + deg = this._angle.value / 400 * 360; + break; + + case WebInspector.LinearGradient.AngleUnits.TURN: + deg = this._angle.value * 360; + break; + + default: + WebInspector.reportInternalError(`Unknown angle units "${this._angle.units}"`); + return 0; + } + + let value = 0; + + switch (units) { + case WebInspector.LinearGradient.AngleUnits.DEG: + value = deg; + break; + + case WebInspector.LinearGradient.AngleUnits.RAD: + value = deg * Math.PI / 180; + break; + + case WebInspector.LinearGradient.AngleUnits.GRAD: + value = deg / 360 * 400; + break; + + case WebInspector.LinearGradient.AngleUnits.TURN: + value = deg / 360; + break; + } + + return value; + } +}; + +WebInspector.LinearGradient.AngleUnits = { + DEG: "deg", + RAD: "rad", + GRAD: "grad", + TURN: "turn", +}; + +WebInspector.RadialGradient = class RadialGradient extends WebInspector.Gradient +{ + constructor(sizing, stops) + { + super(WebInspector.Gradient.Types.Radial, stops); + this.sizing = sizing; + } + + // Static + + static fromComponents(components) + { + var sizing = !WebInspector.Color.fromString(components[0].join(" ")) ? components.shift().join(" ") : ""; + + var stops = WebInspector.Gradient.stopsWithComponents(components); + if (!stops) + return null; + + return new WebInspector.RadialGradient(sizing, stops); + } + + // Public + + copy() + { + return new WebInspector.RadialGradient(this.sizing, this.stops.concat()); + } + + toString() + { + var str = this.sizing; + + if (str !== "") + str += ", "; + + str += this.stringFromStops(this.stops); + + return (this.repeats ? "repeating-" : "") + this.type + "(" + str + ")"; + } +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/HeapAllocationsInstrument.js b/Source/WebInspectorUI/UserInterface/Models/HeapAllocationsInstrument.js new file mode 100644 index 000000000..f35c18408 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/HeapAllocationsInstrument.js @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2016 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. + */ + +WebInspector.HeapAllocationsInstrument = class HeapAllocationsInstrument extends WebInspector.Instrument +{ + constructor() + { + super(); + + console.assert(WebInspector.HeapAllocationsInstrument.supported()); + + this._snapshotIntervalIdentifier = undefined; + } + + // Static + + static supported() + { + // COMPATIBILITY (iOS 9): HeapAgent did not exist. + return window.HeapAgent; + } + + // Protected + + get timelineRecordType() + { + return WebInspector.TimelineRecord.Type.HeapAllocations; + } + + startInstrumentation(initiatedByBackend) + { + // FIXME: Include a "track allocations" option for this instrument. + // FIXME: Include a periodic snapshot interval option for this instrument. + + if (!initiatedByBackend) + HeapAgent.startTracking(); + + // Periodic snapshots. + const snapshotInterval = 10000; + this._snapshotIntervalIdentifier = setInterval(this._takeHeapSnapshot.bind(this), snapshotInterval); + } + + stopInstrumentation(initiatedByBackend) + { + if (!initiatedByBackend) + HeapAgent.stopTracking(); + + window.clearInterval(this._snapshotIntervalIdentifier); + this._snapshotIntervalIdentifier = undefined; + } + + // Private + + _takeHeapSnapshot() + { + HeapAgent.snapshot(function(error, timestamp, snapshotStringData) { + let workerProxy = WebInspector.HeapSnapshotWorkerProxy.singleton(); + workerProxy.createSnapshot(snapshotStringData, ({objectId, snapshot: serializedSnapshot}) => { + let snapshot = WebInspector.HeapSnapshotProxy.deserialize(objectId, serializedSnapshot); + WebInspector.timelineManager.heapSnapshotAdded(timestamp, snapshot); + }); + }); + } +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/HeapAllocationsTimelineRecord.js b/Source/WebInspectorUI/UserInterface/Models/HeapAllocationsTimelineRecord.js new file mode 100644 index 000000000..853fe3ecd --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/HeapAllocationsTimelineRecord.js @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2016 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. + */ + +WebInspector.HeapAllocationsTimelineRecord = class HeapAllocationsTimelineRecord extends WebInspector.TimelineRecord +{ + constructor(timestamp, heapSnapshot) + { + super(WebInspector.TimelineRecord.Type.HeapAllocations, timestamp, timestamp); + + console.assert(typeof timestamp === "number"); + console.assert(heapSnapshot instanceof WebInspector.HeapSnapshotProxy); + + this._timestamp = timestamp; + this._heapSnapshot = heapSnapshot; + } + + // Public + + get timestamp() { return this._timestamp; } + get heapSnapshot() { return this._heapSnapshot; } +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/HeapSnapshotRootPath.js b/Source/WebInspectorUI/UserInterface/Models/HeapSnapshotRootPath.js new file mode 100644 index 000000000..67bfa8ab3 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/HeapSnapshotRootPath.js @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2016 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. + */ + +WebInspector.HeapSnapshotRootPath = class HeapSnapshotRootPath extends WebInspector.Object +{ + constructor(node, pathComponent, parent, isGlobalScope) + { + super(); + + console.assert(!node || node instanceof WebInspector.HeapSnapshotNodeProxy); + console.assert(!pathComponent || typeof pathComponent === "string"); + console.assert(!parent || parent instanceof WebInspector.HeapSnapshotRootPath); + + this._node = node || null; + this._parent = parent || null; + this._pathComponent = typeof pathComponent === "string" ? pathComponent : null; + this._isGlobalScope = isGlobalScope || false; + + // Become the new root when appended to an empty path. + if (this._parent && this._parent.isEmpty()) + this._parent = null; + } + + // Static + + static emptyPath() + { + return new WebInspector.HeapSnapshotRootPath(null); + } + + static pathComponentForIndividualEdge(edge) + { + switch (edge.type) { + case WebInspector.HeapSnapshotEdgeProxy.EdgeType.Internal: + return null; + case WebInspector.HeapSnapshotEdgeProxy.EdgeType.Index: + return "[" + edge.data + "]"; + case WebInspector.HeapSnapshotEdgeProxy.EdgeType.Property: + case WebInspector.HeapSnapshotEdgeProxy.EdgeType.Variable: + if (WebInspector.HeapSnapshotRootPath.canPropertyNameBeDotAccess(edge.data)) + return edge.data; + return "[" + doubleQuotedString(edge.data) + "]"; + } + } + + static canPropertyNameBeDotAccess(propertyName) + { + return /^(?![0-9])\w+$/.test(propertyName); + } + + // Public + + get node() { return this._node; } + get parent() { return this._parent; } + get pathComponent() { return this._pathComponent; } + + get rootNode() + { + return this._parent ? this._parent.rootNode : this._node; + } + + get fullPath() + { + let components = []; + for (let p = this; p && p.pathComponent; p = p.parent) + components.push(p.pathComponent); + components.reverse(); + return components.join(""); + } + + isRoot() + { + return !this._parent; + } + + isEmpty() + { + return !this._node; + } + + isGlobalScope() + { + return this._isGlobalScope; + } + + isPathComponentImpossible() + { + return this._pathComponent && this._pathComponent.startsWith("@"); + } + + isFullPathImpossible() + { + if (this.isEmpty()) + return true; + + if (this.isPathComponentImpossible()) + return true; + + if (this._parent) + return this._parent.isFullPathImpossible(); + + return false; + } + + appendInternal(node) + { + return new WebInspector.HeapSnapshotRootPath(node, WebInspector.HeapSnapshotRootPath.SpecialPathComponent.InternalPropertyName, this); + } + + appendArrayIndex(node, index) + { + let component = "[" + index + "]"; + return new WebInspector.HeapSnapshotRootPath(node, component, this); + } + + appendPropertyName(node, propertyName) + { + let component = WebInspector.HeapSnapshotRootPath.canPropertyNameBeDotAccess(propertyName) ? "." + propertyName : "[" + doubleQuotedString(propertyName) + "]"; + return new WebInspector.HeapSnapshotRootPath(node, component, this); + } + + appendVariableName(node, variableName) + { + // Treat as a property of the global object, e.g. "window.foo". + if (this._isGlobalScope) + return this.appendPropertyName(node, variableName); + return new WebInspector.HeapSnapshotRootPath(node, variableName, this); + } + + appendGlobalScopeName(node, globalScopeName) + { + return new WebInspector.HeapSnapshotRootPath(node, globalScopeName, this, true); + } + + appendEdge(edge) + { + console.assert(edge instanceof WebInspector.HeapSnapshotEdgeProxy); + + switch (edge.type) { + case WebInspector.HeapSnapshotEdgeProxy.EdgeType.Internal: + return this.appendInternal(edge.to); + case WebInspector.HeapSnapshotEdgeProxy.EdgeType.Index: + return this.appendArrayIndex(edge.to, edge.data); + case WebInspector.HeapSnapshotEdgeProxy.EdgeType.Property: + return this.appendPropertyName(edge.to, edge.data); + case WebInspector.HeapSnapshotEdgeProxy.EdgeType.Variable: + return this.appendVariableName(edge.to, edge.data); + } + + console.error("Unexpected edge type", edge.type); + } +}; + +WebInspector.HeapSnapshotRootPath.SpecialPathComponent = { + InternalPropertyName: "@internal", +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/IndexedDatabase.js b/Source/WebInspectorUI/UserInterface/Models/IndexedDatabase.js new file mode 100644 index 000000000..f8ac1b54c --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/IndexedDatabase.js @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2014 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. + */ + +WebInspector.IndexedDatabase = class IndexedDatabase extends WebInspector.Object +{ + constructor(name, securityOrigin, version, objectStores) + { + super(); + + this._name = name; + this._securityOrigin = securityOrigin; + this._host = parseSecurityOrigin(securityOrigin).host; + this._version = version; + this._objectStores = objectStores || []; + + for (var objectStore of this._objectStores) + objectStore.establishRelationship(this); + } + + // Public + + get name() { return this._name; } + get securityOrigin() { return this._securityOrigin; } + get host() { return this._host; } + get version() { return this._version; } + get objectStores() { return this._objectStores; } + + saveIdentityToCookie(cookie) + { + cookie[WebInspector.IndexedDatabase.NameCookieKey] = this._name; + cookie[WebInspector.IndexedDatabase.HostCookieKey] = this._host; + } +}; + +WebInspector.IndexedDatabase.TypeIdentifier = "indexed-database"; +WebInspector.IndexedDatabase.NameCookieKey = "indexed-database-name"; +WebInspector.IndexedDatabase.HostCookieKey = "indexed-database-host"; diff --git a/Source/WebInspectorUI/UserInterface/Models/IndexedDatabaseObjectStore.js b/Source/WebInspectorUI/UserInterface/Models/IndexedDatabaseObjectStore.js new file mode 100644 index 000000000..91b45b122 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/IndexedDatabaseObjectStore.js @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2014 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. + */ + +WebInspector.IndexedDatabaseObjectStore = class IndexedDatabaseObjectStore extends WebInspector.Object +{ + constructor(name, keyPath, autoIncrement, indexes) + { + super(); + + this._name = name; + this._keyPath = keyPath; + this._autoIncrement = autoIncrement || false; + this._indexes = indexes || []; + this._parentDatabase = null; + + for (var index of this._indexes) + index.establishRelationship(this); + } + + // Public + + get name() { return this._name; } + get keyPath() { return this._keyPath; } + get autoIncrement() { return this._autoIncrement; } + get parentDatabase() { return this._parentDatabase; } + get indexes() { return this._indexes; } + + saveIdentityToCookie(cookie) + { + cookie[WebInspector.IndexedDatabaseObjectStore.NameCookieKey] = this._name; + cookie[WebInspector.IndexedDatabaseObjectStore.KeyPathCookieKey] = this._keyPath; + } + + // Protected + + establishRelationship(parentDatabase) + { + this._parentDatabase = parentDatabase || null; + } +}; + +WebInspector.IndexedDatabaseObjectStore.TypeIdentifier = "indexed-database-object-store"; +WebInspector.IndexedDatabaseObjectStore.NameCookieKey = "indexed-database-object-store-name"; +WebInspector.IndexedDatabaseObjectStore.KeyPathCookieKey = "indexed-database-object-store-key-path"; diff --git a/Source/WebInspectorUI/UserInterface/Models/IndexedDatabaseObjectStoreIndex.js b/Source/WebInspectorUI/UserInterface/Models/IndexedDatabaseObjectStoreIndex.js new file mode 100644 index 000000000..bdfa572ff --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/IndexedDatabaseObjectStoreIndex.js @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2014 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. + */ + +WebInspector.IndexedDatabaseObjectStoreIndex = class IndexedDatabaseObjectStoreIndex extends WebInspector.Object +{ + constructor(name, keyPath, unique, multiEntry) + { + super(); + + this._name = name; + this._keyPath = keyPath; + this._unique = unique || false; + this._multiEntry = multiEntry || false; + this._parentObjectStore = null; + } + + // Public + + get name() { return this._name; } + get keyPath() { return this._keyPath; } + get unique() { return this._unique; } + get multiEntry() { return this._multiEntry; } + get parentObjectStore() { return this._parentObjectStore; } + + saveIdentityToCookie(cookie) + { + cookie[WebInspector.IndexedDatabaseObjectStoreIndex.NameCookieKey] = this._name; + cookie[WebInspector.IndexedDatabaseObjectStoreIndex.KeyPathCookieKey] = this._keyPath; + } + + // Protected + + establishRelationship(parentObjectStore) + { + this._parentObjectStore = parentObjectStore || null; + } +}; + +WebInspector.IndexedDatabaseObjectStoreIndex.TypeIdentifier = "indexed-database-object-store-index"; +WebInspector.IndexedDatabaseObjectStoreIndex.NameCookieKey = "indexed-database-object-store-index-name"; +WebInspector.IndexedDatabaseObjectStoreIndex.KeyPathCookieKey = "indexed-database-object-store-index-key-path"; diff --git a/Source/WebInspectorUI/UserInterface/Models/Instrument.js b/Source/WebInspectorUI/UserInterface/Models/Instrument.js new file mode 100644 index 000000000..7659a0bd2 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/Instrument.js @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2015 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. + */ + +WebInspector.Instrument = class Instrument extends WebInspector.Object +{ + // Static + + static createForTimelineType(type) + { + switch (type) { + case WebInspector.TimelineRecord.Type.Network: + return new WebInspector.NetworkInstrument; + case WebInspector.TimelineRecord.Type.Layout: + return new WebInspector.LayoutInstrument; + case WebInspector.TimelineRecord.Type.Script: + return new WebInspector.ScriptInstrument; + case WebInspector.TimelineRecord.Type.RenderingFrame: + return new WebInspector.FPSInstrument; + case WebInspector.TimelineRecord.Type.Memory: + return new WebInspector.MemoryInstrument; + case WebInspector.TimelineRecord.Type.HeapAllocations: + return new WebInspector.HeapAllocationsInstrument; + default: + console.error("Unknown TimelineRecord.Type: " + type); + return null; + } + } + + static startLegacyTimelineAgent(initiatedByBackend) + { + console.assert(window.TimelineAgent, "Attempted to start legacy timeline agent without TimelineAgent."); + + if (WebInspector.Instrument._legacyTimelineAgentStarted) + return; + + WebInspector.Instrument._legacyTimelineAgentStarted = true; + + if (initiatedByBackend) + return; + + let result = TimelineAgent.start(); + + // COMPATIBILITY (iOS 7): recordingStarted event did not exist yet. Start explicitly. + if (!TimelineAgent.hasEvent("recordingStarted")) { + result.then(function() { + WebInspector.timelineManager.capturingStarted(); + }); + } + } + + static stopLegacyTimelineAgent(initiatedByBackend) + { + if (!WebInspector.Instrument._legacyTimelineAgentStarted) + return; + + WebInspector.Instrument._legacyTimelineAgentStarted = false; + + if (initiatedByBackend) + return; + + TimelineAgent.stop(); + } + + // Protected + + get timelineRecordType() + { + return null; // Implemented by subclasses. + } + + startInstrumentation(initiatedByBackend) + { + WebInspector.Instrument.startLegacyTimelineAgent(initiatedByBackend); + } + + stopInstrumentation(initiatedByBackend) + { + WebInspector.Instrument.stopLegacyTimelineAgent(initiatedByBackend); + } +}; + +WebInspector.Instrument._legacyTimelineAgentStarted = false; diff --git a/Source/WebInspectorUI/UserInterface/Models/IssueMessage.js b/Source/WebInspectorUI/UserInterface/Models/IssueMessage.js new file mode 100644 index 000000000..43acd7324 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/IssueMessage.js @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2013, 2016 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. + */ + +WebInspector.IssueMessage = class IssueMessage extends WebInspector.Object +{ + constructor(consoleMessage) + { + super(); + + console.assert(consoleMessage instanceof WebInspector.ConsoleMessage); + + this._consoleMessage = consoleMessage; + + this._text = this._issueText(); + + switch (this._consoleMessage.source) { + case "javascript": + // FIXME: It would be nice if we had this information (the specific type of JavaScript error) + // as part of the data passed from WebCore, instead of having to determine it ourselves. + var prefixRegex = /^([^:]+): (?:DOM Exception \d+: )?/; + var match = prefixRegex.exec(this._text); + if (match && match[1] in WebInspector.IssueMessage.Type._prefixTypeMap) { + this._type = WebInspector.IssueMessage.Type._prefixTypeMap[match[1]]; + this._text = this._text.substring(match[0].length); + } else + this._type = WebInspector.IssueMessage.Type.OtherIssue; + break; + + case "css": + case "xml": + this._type = WebInspector.IssueMessage.Type.PageIssue; + break; + + case "network": + this._type = WebInspector.IssueMessage.Type.NetworkIssue; + break; + + case "security": + this._type = WebInspector.IssueMessage.Type.SecurityIssue; + break; + + case "console-api": + case "storage": + case "appcache": + case "rendering": + case "other": + this._type = WebInspector.IssueMessage.Type.OtherIssue; + break; + + default: + console.error("Unknown issue source:", this._consoleMessage.source); + this._type = WebInspector.IssueMessage.Type.OtherIssue; + } + + this._sourceCodeLocation = consoleMessage.sourceCodeLocation; + if (this._sourceCodeLocation) + this._sourceCodeLocation.addEventListener(WebInspector.SourceCodeLocation.Event.DisplayLocationChanged, this._sourceCodeLocationDisplayLocationChanged, this); + } + + // Static + + static displayName(type) + { + switch (type) { + case WebInspector.IssueMessage.Type.SemanticIssue: + return WebInspector.UIString("Semantic Issue"); + case WebInspector.IssueMessage.Type.RangeIssue: + return WebInspector.UIString("Range Issue"); + case WebInspector.IssueMessage.Type.ReferenceIssue: + return WebInspector.UIString("Reference Issue"); + case WebInspector.IssueMessage.Type.TypeIssue: + return WebInspector.UIString("Type Issue"); + case WebInspector.IssueMessage.Type.PageIssue: + return WebInspector.UIString("Page Issue"); + case WebInspector.IssueMessage.Type.NetworkIssue: + return WebInspector.UIString("Network Issue"); + case WebInspector.IssueMessage.Type.SecurityIssue: + return WebInspector.UIString("Security Issue"); + case WebInspector.IssueMessage.Type.OtherIssue: + return WebInspector.UIString("Other Issue"); + default: + console.error("Unknown issue message type:", type); + return WebInspector.UIString("Other Issue"); + } + } + + // Public + + get text() { return this._text; } + get type() { return this._type; } + get level() { return this._consoleMessage.level; } + get source() { return this._consoleMessage.source; } + get url() { return this._consoleMessage.url; } + get sourceCodeLocation() { return this._sourceCodeLocation; } + + // Protected + + saveIdentityToCookie(cookie) + { + cookie[WebInspector.IssueMessage.URLCookieKey] = this.url; + cookie[WebInspector.IssueMessage.LineNumberCookieKey] = this._sourceCodeLocation ? this._sourceCodeLocation.lineNumber : 0; + cookie[WebInspector.IssueMessage.ColumnNumberCookieKey] = this._sourceCodeLocation ? this._sourceCodeLocation.columnNumber : 0; + } + + // Private + + _issueText() + { + let parameters = this._consoleMessage.parameters; + if (!parameters) + return this._consoleMessage.messageText; + + if (WebInspector.RemoteObject.type(parameters[0]) !== "string") + return this._consoleMessage.messageText; + + function valueFormatter(obj) + { + return obj.description; + } + + let formatters = {}; + formatters.o = valueFormatter; + formatters.s = valueFormatter; + formatters.f = valueFormatter; + formatters.i = valueFormatter; + formatters.d = valueFormatter; + + function append(a, b) + { + a += b; + return a; + } + + let result = String.format(parameters[0].description, parameters.slice(1), formatters, "", append); + let resultText = result.formattedResult; + + for (let i = 0; i < result.unusedSubstitutions.length; ++i) + resultText += " " + result.unusedSubstitutions[i].description; + + return resultText; + } + + _sourceCodeLocationDisplayLocationChanged(event) + { + this.dispatchEventToListeners(WebInspector.IssueMessage.Event.DisplayLocationDidChange, event.data); + } +}; + +WebInspector.IssueMessage.Level = { + Error: "error", + Warning: "warning" +}; + +WebInspector.IssueMessage.Type = { + SemanticIssue: "issue-message-type-semantic-issue", + RangeIssue: "issue-message-type-range-issue", + ReferenceIssue: "issue-message-type-reference-issue", + TypeIssue: "issue-message-type-type-issue", + PageIssue: "issue-message-type-page-issue", + NetworkIssue: "issue-message-type-network-issue", + SecurityIssue: "issue-message-type-security-issue", + OtherIssue: "issue-message-type-other-issue" +}; + +WebInspector.IssueMessage.TypeIdentifier = "issue-message"; +WebInspector.IssueMessage.URLCookieKey = "issue-message-url"; +WebInspector.IssueMessage.LineNumberCookieKey = "issue-message-line-number"; +WebInspector.IssueMessage.ColumnNumberCookieKey = "issue-message-column-number"; + +WebInspector.IssueMessage.Event = { + LocationDidChange: "issue-message-location-did-change", + DisplayLocationDidChange: "issue-message-display-location-did-change" +}; + +WebInspector.IssueMessage.Type._prefixTypeMap = { + "SyntaxError": WebInspector.IssueMessage.Type.SemanticIssue, + "URIError": WebInspector.IssueMessage.Type.SemanticIssue, + "EvalError": WebInspector.IssueMessage.Type.SemanticIssue, + "INVALID_CHARACTER_ERR": WebInspector.IssueMessage.Type.SemanticIssue, + "SYNTAX_ERR": WebInspector.IssueMessage.Type.SemanticIssue, + + "RangeError": WebInspector.IssueMessage.Type.RangeIssue, + "INDEX_SIZE_ERR": WebInspector.IssueMessage.Type.RangeIssue, + "DOMSTRING_SIZE_ERR": WebInspector.IssueMessage.Type.RangeIssue, + + "ReferenceError": WebInspector.IssueMessage.Type.ReferenceIssue, + "HIERARCHY_REQUEST_ERR": WebInspector.IssueMessage.Type.ReferenceIssue, + "INVALID_STATE_ERR": WebInspector.IssueMessage.Type.ReferenceIssue, + "NOT_FOUND_ERR": WebInspector.IssueMessage.Type.ReferenceIssue, + "WRONG_DOCUMENT_ERR": WebInspector.IssueMessage.Type.ReferenceIssue, + + "TypeError": WebInspector.IssueMessage.Type.TypeIssue, + "INVALID_NODE_TYPE_ERR": WebInspector.IssueMessage.Type.TypeIssue, + "TYPE_MISMATCH_ERR": WebInspector.IssueMessage.Type.TypeIssue, + + "SECURITY_ERR": WebInspector.IssueMessage.Type.SecurityIssue, + + "NETWORK_ERR": WebInspector.IssueMessage.Type.NetworkIssue, + + "ABORT_ERR": WebInspector.IssueMessage.Type.OtherIssue, + "DATA_CLONE_ERR": WebInspector.IssueMessage.Type.OtherIssue, + "INUSE_ATTRIBUTE_ERR": WebInspector.IssueMessage.Type.OtherIssue, + "INVALID_ACCESS_ERR": WebInspector.IssueMessage.Type.OtherIssue, + "INVALID_MODIFICATION_ERR": WebInspector.IssueMessage.Type.OtherIssue, + "NAMESPACE_ERR": WebInspector.IssueMessage.Type.OtherIssue, + "NOT_SUPPORTED_ERR": WebInspector.IssueMessage.Type.OtherIssue, + "NO_DATA_ALLOWED_ERR": WebInspector.IssueMessage.Type.OtherIssue, + "NO_MODIFICATION_ALLOWED_ERR": WebInspector.IssueMessage.Type.OtherIssue, + "QUOTA_EXCEEDED_ERR": WebInspector.IssueMessage.Type.OtherIssue, + "TIMEOUT_ERR": WebInspector.IssueMessage.Type.OtherIssue, + "URL_MISMATCH_ERR": WebInspector.IssueMessage.Type.OtherIssue, + "VALIDATION_ERR": WebInspector.IssueMessage.Type.OtherIssue +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/KeyboardShortcut.js b/Source/WebInspectorUI/UserInterface/Models/KeyboardShortcut.js new file mode 100644 index 000000000..43e8a7423 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/KeyboardShortcut.js @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2013, 2015 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. + */ + +WebInspector.KeyboardShortcut = class KeyboardShortcut extends WebInspector.Object +{ + constructor(modifiers, key, callback, targetElement) + { + super(); + + console.assert(key); + console.assert(!callback || typeof callback === "function"); + console.assert(!targetElement || targetElement instanceof Element); + + if (typeof key === "string") { + key = key[0].toUpperCase(); + key = new WebInspector.Key(key.charCodeAt(0), key); + } + + if (callback && !targetElement) + targetElement = document; + + this._modifiers = modifiers || WebInspector.KeyboardShortcut.Modifier.None; + this._key = key; + this._targetElement = targetElement; + this._callback = callback; + this._disabled = false; + this._implicitlyPreventsDefault = true; + + if (targetElement) { + var targetKeyboardShortcuts = targetElement._keyboardShortcuts; + if (!targetKeyboardShortcuts) + targetKeyboardShortcuts = targetElement._keyboardShortcuts = []; + + targetKeyboardShortcuts.push(this); + + if (!WebInspector.KeyboardShortcut._registeredKeyDownListener) { + WebInspector.KeyboardShortcut._registeredKeyDownListener = true; + window.addEventListener("keydown", WebInspector.KeyboardShortcut._handleKeyDown); + } + } + } + + // Static + + static _handleKeyDown(event) + { + if (event.defaultPrevented) + return; + + for (var targetElement = event.target; targetElement; targetElement = targetElement.parentNode) { + if (!targetElement._keyboardShortcuts) + continue; + + for (var i = 0; i < targetElement._keyboardShortcuts.length; ++i) { + var keyboardShortcut = targetElement._keyboardShortcuts[i]; + if (!keyboardShortcut.matchesEvent(event)) + continue; + + if (!keyboardShortcut.callback) + continue; + + keyboardShortcut.callback(event, keyboardShortcut); + + if (keyboardShortcut.implicitlyPreventsDefault) + event.preventDefault(); + + return; + } + } + } + + // Public + + get modifiers() + { + return this._modifiers; + } + + get key() + { + return this._key; + } + + get displayName() + { + var result = ""; + + if (this._modifiers & WebInspector.KeyboardShortcut.Modifier.Control) + result += "\u2303"; + if (this._modifiers & WebInspector.KeyboardShortcut.Modifier.Option) + result += WebInspector.Platform.name === "mac" ? "\u2325" : "\u2387"; + if (this._modifiers & WebInspector.KeyboardShortcut.Modifier.Shift) + result += "\u21e7"; + if (this._modifiers & WebInspector.KeyboardShortcut.Modifier.Command) + result += "\u2318"; + + result += this._key.toString(); + + return result; + } + + get callback() + { + return this._callback; + } + + set callback(callback) + { + console.assert(!callback || typeof callback === "function"); + + this._callback = callback || null; + } + + get disabled() + { + return this._disabled; + } + + set disabled(disabled) + { + this._disabled = disabled || false; + } + + get implicitlyPreventsDefault() + { + return this._implicitlyPreventsDefault; + } + + set implicitlyPreventsDefault(implicitly) + { + this._implicitlyPreventsDefault = implicitly; + } + + unbind() + { + this._disabled = true; + + if (!this._targetElement) + return; + + var targetKeyboardShortcuts = this._targetElement._keyboardShortcuts; + if (!targetKeyboardShortcuts) + return; + + targetKeyboardShortcuts.remove(this); + } + + matchesEvent(event) + { + if (this._disabled) + return false; + + if (this._key.keyCode !== event.keyCode) + return false; + + var eventModifiers = WebInspector.KeyboardShortcut.Modifier.None; + if (event.shiftKey) + eventModifiers |= WebInspector.KeyboardShortcut.Modifier.Shift; + if (event.ctrlKey) + eventModifiers |= WebInspector.KeyboardShortcut.Modifier.Control; + if (event.altKey) + eventModifiers |= WebInspector.KeyboardShortcut.Modifier.Option; + if (event.metaKey) + eventModifiers |= WebInspector.KeyboardShortcut.Modifier.Command; + return this._modifiers === eventModifiers; + } +}; + +WebInspector.Key = class Key +{ + constructor(keyCode, displayName) + { + this._keyCode = keyCode; + this._displayName = displayName; + } + + // Public + + get keyCode() + { + return this._keyCode; + } + + get displayName() + { + return this._displayName; + } + + toString() + { + return this._displayName; + } +}; + +WebInspector.KeyboardShortcut.Modifier = { + None: 0, + Shift: 1, + Control: 2, + Option: 4, + Command: 8, + + get CommandOrControl() + { + return WebInspector.Platform.name === "mac" ? this.Command : this.Control; + } +}; + +WebInspector.KeyboardShortcut.Key = { + Backspace: new WebInspector.Key(8, "\u232b"), + Tab: new WebInspector.Key(9, "\u21e5"), + Enter: new WebInspector.Key(13, "\u21a9"), + Escape: new WebInspector.Key(27, "\u238b"), + Space: new WebInspector.Key(32, "Space"), + PageUp: new WebInspector.Key(33, "\u21de"), + PageDown: new WebInspector.Key(34, "\u21df"), + End: new WebInspector.Key(35, "\u2198"), + Home: new WebInspector.Key(36, "\u2196"), + Left: new WebInspector.Key(37, "\u2190"), + Up: new WebInspector.Key(38, "\u2191"), + Right: new WebInspector.Key(39, "\u2192"), + Down: new WebInspector.Key(40, "\u2193"), + Delete: new WebInspector.Key(46, "\u2326"), + Zero: new WebInspector.Key(48, "0"), + F1: new WebInspector.Key(112, "F1"), + F2: new WebInspector.Key(113, "F2"), + F3: new WebInspector.Key(114, "F3"), + F4: new WebInspector.Key(115, "F4"), + F5: new WebInspector.Key(116, "F5"), + F6: new WebInspector.Key(117, "F6"), + F7: new WebInspector.Key(118, "F7"), + F8: new WebInspector.Key(119, "F8"), + F9: new WebInspector.Key(120, "F9"), + F10: new WebInspector.Key(121, "F10"), + F11: new WebInspector.Key(122, "F11"), + F12: new WebInspector.Key(123, "F12"), + Semicolon: new WebInspector.Key(186, ";"), + Plus: new WebInspector.Key(187, "+"), + Comma: new WebInspector.Key(188, ","), + Minus: new WebInspector.Key(189, "-"), + Period: new WebInspector.Key(190, "."), + Slash: new WebInspector.Key(191, "/"), + Apostrophe: new WebInspector.Key(192, "`"), + LeftCurlyBrace: new WebInspector.Key(219, "{"), + Backslash: new WebInspector.Key(220, "\\"), + RightCurlyBrace: new WebInspector.Key(221, "}"), + SingleQuote: new WebInspector.Key(222, "\'") +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/LayoutInstrument.js b/Source/WebInspectorUI/UserInterface/Models/LayoutInstrument.js new file mode 100644 index 000000000..2d33ac3d4 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/LayoutInstrument.js @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2015 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. + */ + +WebInspector.LayoutInstrument = class LayoutInstrument extends WebInspector.Instrument +{ + // Protected + + get timelineRecordType() + { + return WebInspector.TimelineRecord.Type.Layout; + } +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/LayoutTimelineRecord.js b/Source/WebInspectorUI/UserInterface/Models/LayoutTimelineRecord.js new file mode 100644 index 000000000..79376c548 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/LayoutTimelineRecord.js @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2013 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. + */ + +WebInspector.LayoutTimelineRecord = class LayoutTimelineRecord extends WebInspector.TimelineRecord +{ + constructor(eventType, startTime, endTime, callFrames, sourceCodeLocation, quad) + { + super(WebInspector.TimelineRecord.Type.Layout, startTime, endTime, callFrames, sourceCodeLocation); + + console.assert(eventType); + console.assert(!quad || quad instanceof WebInspector.Quad); + + if (eventType in WebInspector.LayoutTimelineRecord.EventType) + eventType = WebInspector.LayoutTimelineRecord.EventType[eventType]; + + this._eventType = eventType; + this._quad = quad || null; + } + + // Static + + static displayNameForEventType(eventType) + { + switch (eventType) { + case WebInspector.LayoutTimelineRecord.EventType.InvalidateStyles: + return WebInspector.UIString("Styles Invalidated"); + case WebInspector.LayoutTimelineRecord.EventType.RecalculateStyles: + return WebInspector.UIString("Styles Recalculated"); + case WebInspector.LayoutTimelineRecord.EventType.InvalidateLayout: + return WebInspector.UIString("Layout Invalidated"); + case WebInspector.LayoutTimelineRecord.EventType.ForcedLayout: + return WebInspector.UIString("Forced Layout"); + case WebInspector.LayoutTimelineRecord.EventType.Layout: + return WebInspector.UIString("Layout"); + case WebInspector.LayoutTimelineRecord.EventType.Paint: + return WebInspector.UIString("Paint"); + case WebInspector.LayoutTimelineRecord.EventType.Composite: + return WebInspector.UIString("Composite"); + } + } + + // Public + + get eventType() + { + return this._eventType; + } + + get width() + { + return this._quad ? this._quad.width : NaN; + } + + get height() + { + return this._quad ? this._quad.height : NaN; + } + + get area() + { + return this.width * this.height; + } + + get quad() + { + return this._quad; + } + + saveIdentityToCookie(cookie) + { + super.saveIdentityToCookie(cookie); + + cookie[WebInspector.LayoutTimelineRecord.EventTypeCookieKey] = this._eventType; + } +}; + +WebInspector.LayoutTimelineRecord.EventType = { + InvalidateStyles: "layout-timeline-record-invalidate-styles", + RecalculateStyles: "layout-timeline-record-recalculate-styles", + InvalidateLayout: "layout-timeline-record-invalidate-layout", + ForcedLayout: "layout-timeline-record-forced-layout", + Layout: "layout-timeline-record-layout", + Paint: "layout-timeline-record-paint", + Composite: "layout-timeline-record-composite" +}; + +WebInspector.LayoutTimelineRecord.TypeIdentifier = "layout-timeline-record"; +WebInspector.LayoutTimelineRecord.EventTypeCookieKey = "layout-timeline-record-event-type"; diff --git a/Source/WebInspectorUI/UserInterface/Models/LazySourceCodeLocation.js b/Source/WebInspectorUI/UserInterface/Models/LazySourceCodeLocation.js new file mode 100644 index 000000000..6f1a90ad7 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/LazySourceCodeLocation.js @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2014 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. + */ + +// FIXME: Investigate folding this into SourceCodeLocation proper so it can always be as lazy as possible. + +// Lazily compute the full SourceCodeLocation information only when such information is needed. +// - raw information doesn't require initialization, we have that information +// - formatted information does require initialization, done by overriding public APIs. +// - display information does require initialization, done by overriding private funnel API resolveMappedLocation. + +WebInspector.LazySourceCodeLocation = class LazySourceCodeLocation extends WebInspector.SourceCodeLocation +{ + constructor(sourceCode, lineNumber, columnNumber) + { + super(null, lineNumber, columnNumber); + + console.assert(sourceCode); + + this._initialized = false; + this._lazySourceCode = sourceCode; + } + + // Public + + isEqual(other) + { + if (!other) + return false; + return this._lazySourceCode === other._sourceCode && this._lineNumber === other._lineNumber && this._columnNumber === other._columnNumber; + } + + get sourceCode() + { + return this._lazySourceCode; + } + + set sourceCode(sourceCode) + { + // Getter and setter must be provided together. + this.setSourceCode(sourceCode); + } + + get formattedLineNumber() + { + this._lazyInitialization(); + return this._formattedLineNumber; + } + + get formattedColumnNumber() + { + this._lazyInitialization(); + return this._formattedColumnNumber; + } + + formattedPosition() + { + this._lazyInitialization(); + return new WebInspector.SourceCodePosition(this._formattedLineNumber, this._formattedColumnNumber); + } + + hasFormattedLocation() + { + this._lazyInitialization(); + return super.hasFormattedLocation(); + } + + hasDifferentDisplayLocation() + { + this._lazyInitialization(); + return super.hasDifferentDisplayLocation(); + } + + // Protected + + resolveMappedLocation() + { + this._lazyInitialization(); + super.resolveMappedLocation(); + } + + // Private + + _lazyInitialization() + { + if (!this._initialized) { + this._initialized = true; + this.sourceCode = this._lazySourceCode; + } + } +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/LineWidget.js b/Source/WebInspectorUI/UserInterface/Models/LineWidget.js new file mode 100644 index 000000000..06ffa1b64 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/LineWidget.js @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2015 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. + */ + +WebInspector.LineWidget = class LineWidget extends WebInspector.Object +{ + constructor(codeMirrorLineWidget, widgetElement) + { + super(); + + console.assert(widgetElement instanceof Element); + + this._codeMirrorLineWidget = codeMirrorLineWidget; + this._widgetElement = widgetElement; + } + + // Public + + get codeMirrorLineWidget() + { + return this._codeMirrorLineWidget; + } + + get widgetElement() + { + return this._widgetElement; + } + + clear() + { + this._codeMirrorLineWidget.clear(); + } + + update() + { + // FIXME: Later version of CodeMirror has update. + if (this._codeMirrorLineWidget.update) + this._codeMirrorLineWidget.update(); + } +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/LogObject.js b/Source/WebInspectorUI/UserInterface/Models/LogObject.js new file mode 100644 index 000000000..b9551226b --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/LogObject.js @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2013 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. + */ + +WebInspector.LogObject = class LogObject extends WebInspector.Object +{ + constructor() + { + super(); + + this._startDate = new Date; + } + + // Public + + get startDate() + { + return this._startDate; + } +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/MemoryCategory.js b/Source/WebInspectorUI/UserInterface/Models/MemoryCategory.js new file mode 100644 index 000000000..76cbba717 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/MemoryCategory.js @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2016 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. + */ + +WebInspector.MemoryCategory = class MemoryCategory extends WebInspector.Object +{ + constructor(type, size) + { + super(); + + console.assert(typeof type === "string"); + console.assert(typeof size === "number"); + console.assert(size >= 0); + + this.type = type; + this.size = size; + } +}; + +WebInspector.MemoryCategory.Type = { + JavaScript: "javascript", + Images: "images", + Layers: "layers", + Page: "page", +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/MemoryInstrument.js b/Source/WebInspectorUI/UserInterface/Models/MemoryInstrument.js new file mode 100644 index 000000000..cbe254394 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/MemoryInstrument.js @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2016 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. + */ + +WebInspector.MemoryInstrument = class MemoryInstrument extends WebInspector.Instrument +{ + constructor() + { + super(); + + console.assert(WebInspector.MemoryInstrument.supported()); + } + + // Static + + static supported() + { + // COMPATIBILITY (iOS 9): MemoryAgent did not exist. + return window.MemoryAgent; + } + + // Protected + + get timelineRecordType() + { + return WebInspector.TimelineRecord.Type.Memory; + } + + startInstrumentation(initiatedByBackend) + { + if (!initiatedByBackend) + MemoryAgent.startTracking(); + } + + stopInstrumentation(initiatedByBackend) + { + if (!initiatedByBackend) + MemoryAgent.stopTracking(); + } +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/MemoryPressureEvent.js b/Source/WebInspectorUI/UserInterface/Models/MemoryPressureEvent.js new file mode 100644 index 000000000..33aeb8dac --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/MemoryPressureEvent.js @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2016 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. + */ + +WebInspector.MemoryPressureEvent = class MemoryPressureEvent extends WebInspector.Object +{ + constructor(timestamp, severity) + { + super(); + + this._timestamp = timestamp; + this._severity = severity; + } + + // Static + + static fromPayload(timestamp, protocolSeverity) + { + let severity; + switch (protocolSeverity) { + case MemoryAgent.MemoryPressureSeverity.Critical: + severity = WebInspector.MemoryPressureEvent.Severity.Critical; + break; + case MemoryAgent.MemoryPressureSeverity.NonCritical: + severity = WebInspector.MemoryPressureEvent.Severity.NonCritical; + break; + default: + console.error("Unexpected memory pressure severity", protocolSeverity); + severity = WebInspector.MemoryPressureEvent.Severity.NonCritical; + break; + } + + return new WebInspector.MemoryPressureEvent(timestamp, severity); + } + + // Public + + get timestamp() { return this._timestamp; } + get severity() { return this._severity; } +}; + +WebInspector.MemoryPressureEvent.Severity = { + Critical: Symbol("Critical"), + NonCritical: Symbol("NonCritical"), +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/MemoryTimeline.js b/Source/WebInspectorUI/UserInterface/Models/MemoryTimeline.js new file mode 100644 index 000000000..8a791bab7 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/MemoryTimeline.js @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2016 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. + */ + +WebInspector.MemoryTimeline = class MemoryTimeline extends WebInspector.Timeline +{ + // Public + + get memoryPressureEvents() { return this._pressureEvents; } + + addMemoryPressureEvent(memoryPressureEvent) + { + console.assert(memoryPressureEvent instanceof WebInspector.MemoryPressureEvent); + + this._pressureEvents.push(memoryPressureEvent); + + this.dispatchEventToListeners(WebInspector.MemoryTimeline.Event.MemoryPressureEventAdded, {memoryPressureEvent}); + } + + // Protected + + reset(suppressEvents) + { + super.reset(suppressEvents); + + this._pressureEvents = []; + } +}; + +WebInspector.MemoryTimeline.Event = { + MemoryPressureEventAdded: "memory-timeline-memory-pressure-event-added", +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/MemoryTimelineRecord.js b/Source/WebInspectorUI/UserInterface/Models/MemoryTimelineRecord.js new file mode 100644 index 000000000..5b482d823 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/MemoryTimelineRecord.js @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2016 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. + */ + +WebInspector.MemoryTimelineRecord = class MemoryTimelineRecord extends WebInspector.TimelineRecord +{ + constructor(timestamp, categories) + { + super(WebInspector.TimelineRecord.Type.Memory, timestamp, timestamp); + + console.assert(typeof timestamp === "number"); + console.assert(categories instanceof Array); + + this._timestamp = timestamp; + this._categories = WebInspector.MemoryTimelineRecord.memoryCategoriesFromProtocol(categories); + + this._totalSize = 0; + for (let {size} of categories) + this._totalSize += size; + } + + // Static + + static memoryCategoriesFromProtocol(categories) + { + let javascriptSize = 0; + let imagesSize = 0; + let layersSize = 0; + let pageSize = 0; + + for (let {type, size} of categories) { + switch (type) { + case MemoryAgent.CategoryDataType.Javascript: + case MemoryAgent.CategoryDataType.JIT: + javascriptSize += size; + break; + case MemoryAgent.CategoryDataType.Images: + imagesSize += size; + break; + case MemoryAgent.CategoryDataType.Layers: + layersSize += size; + break; + case MemoryAgent.CategoryDataType.Page: + case MemoryAgent.CategoryDataType.Other: + pageSize += size; + break; + default: + console.warn("Unhandled Memory.CategoryDataType: " + type); + break; + } + } + + return [ + {type: WebInspector.MemoryCategory.Type.JavaScript, size: javascriptSize}, + {type: WebInspector.MemoryCategory.Type.Images, size: imagesSize}, + {type: WebInspector.MemoryCategory.Type.Layers, size: layersSize}, + {type: WebInspector.MemoryCategory.Type.Page, size: pageSize}, + ]; + } + + // Public + + get timestamp() { return this._timestamp; } + get categories() { return this._categories; } + get totalSize() { return this._totalSize; } +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/NativeFunctionParameters.js b/Source/WebInspectorUI/UserInterface/Models/NativeFunctionParameters.js new file mode 100644 index 000000000..0633a209e --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/NativeFunctionParameters.js @@ -0,0 +1,2232 @@ +/* + * Copyright (C) 2015 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. + */ + +// FIXME: Provide Parameter lists for the constructors themselves? (new RegExp(...)). +// FIXME: Provide Parameter lists for global functions (eval, decodeURI, ...). + +WebInspector.NativeConstructorFunctionParameters = { + Object: { + assign: "target, ...sources", + create: "prototype, [propertiesObject]", + defineProperties: "object, properties", + defineProperty: "object, propertyName, descriptor", + freeze: "object", + getOwnPropertyDescriptor: "object, propertyName", + getOwnPropertyNames: "object", + getOwnPropertySymbols: "object", + getPrototypeOf: "object", + is: "value1, value2", + isExtensible: "object", + isFrozen: "object", + isSealed: "object", + keys: "object", + preventExtensions: "object", + seal: "object", + setPrototypeOf: "object, prototype", + __proto__: null, + }, + + Array: { + from: "arrayLike, [mapFunction], [thisArg]", + isArray: "object", + of: "[...values]", + __proto__: null, + }, + + ArrayBuffer: { + isView: "object", + transfer: "oldBuffer, [newByteLength=length]", + __proto__: null, + }, + + Number: { + isFinite: "value", + isInteger: "value", + isNaN: "value", + isSafeInteger: "value", + parseFloat: "string", + parseInt: "string, [radix]", + __proto__: null, + }, + + Math: { + abs: "x", + acos: "x", + acosh: "x", + asin: "x", + asinh: "x", + atan2: "y, x", + atan: "x", + atanh: "x", + cbrt: "x", + ceil: "x", + clz32: "x", + cos: "x", + cosh: "x", + exp: "x", + expm1: "x", + floor: "x", + fround: "x", + hypot: "[...x]", + imul: "x", + log: "x", + log1p: "x", + log2: "x", + log10: "x", + max: "[...x]", + min: "[...x]", + pow: "x, y", + round: "x", + sign: "x", + sin: "x", + sinh: "x", + sqrt: "x", + tan: "x", + tanh: "x", + trunc: "x", + __proto__: null, + }, + + JSON: { + parse: "text, [reviver]", + stringify: "value, [replacer], [space]", + __proto__: null, + }, + + Date: { + parse: "dateString", + UTC: "year, [month], [day], [hour], [minute], [second], [ms]", + __proto__: null, + }, + + Promise: { + all: "iterable", + race: "iterable", + reject: "reason", + resolve: "value", + __proto__: null, + }, + + Reflect: { + apply: "target, thisArgument, argumentsList", + construct: "target, argumentsList, [newTarget=target]", + defineProperty: "target, propertyKey, attributes", + deleteProperty: "target, propertyKey", + enumerate: "target", + get: "target, propertyKey, [receiver]", + getOwnPropertyDescriptor: "target, propertyKey", + getPrototypeOf: "target", + has: "target, propertyKey", + isExtensible: "target", + ownKeys: "target", + preventExtensions: "target", + set: "target, propertyKey, value, [receiver]", + setPrototypeOf: "target, prototype", + __proto__: null, + }, + + String: { + fromCharCode: "...codeUnits", + fromCodePoint: "...codePoints", + raw: "template, ...substitutions", + __proto__: null, + }, + + Symbol: { + for: "key", + keyFor: "symbol", + __proto__: null, + }, + + Console: { + assert: "condition, [message], [...values]", + count: "[label]", + debug: "message, [...values]", + dir: "object", + dirxml: "object", + error: "message, [...values]", + group: "[name]", + groupCollapsed: "[name]", + groupEnd: "[name]", + info: "message, [...values]", + log: "message, [...values]", + profile: "name", + profileEnd: "name", + table: "data, [columns]", + takeHeapSnapshot: "[label]", + time: "name = \"default\"", + timeEnd: "name = \"default\"", + timeStamp: "[label]", + trace: "message, [...values]", + warn: "message, [...values]", + __proto__: null, + }, + + // Autogenerated DOM Interface static methods. + + IDBKeyRangeConstructor: { + bound: "lower, upper, [lowerOpen], [upperOpen]", + lowerBound: "lower, [open]", + only: "value", + upperBound: "upper, [open]", + __proto__: null, + }, + + MediaSourceConstructor: { + isTypeSupported: "type", + __proto__: null, + }, + + MediaStreamTrackConstructor: { + getSources: "callback", + __proto__: null, + }, + + NotificationConstructor: { + requestPermission: "[callback]", + __proto__: null, + }, + + URLConstructor: { + createObjectURL: "blob", + revokeObjectURL: "url", + __proto__: null, + }, + + WebKitMediaKeysConstructor: { + isTypeSupported: "keySystem, [type]", + __proto__: null, + }, +}; + +WebInspector.NativePrototypeFunctionParameters = { + + // Built-in JavaScript objects. + // FIXME: TypedArrays (Int8Array, etc), + + Object: { + __defineGetter__: "propertyName, getterFunction", + __defineSetter__: "propertyName, setterFunction", + __lookupGetter__: "propertyName", + __lookupSetter__: "propertyName", + hasOwnProperty: "propertyName", + isPrototypeOf: "property", + propertyIsEnumerable: "propertyName", + __proto__: null, + }, + + Array: { + concat: "value, ...", + copyWithin: "targetIndex, startIndex, [endIndex=length]", + every: "callback, [thisArg]", + fill: "value, [startIndex=0], [endIndex=length]", + filter: "callback, [thisArg]", + find: "callback, [thisArg]", + findIndex: "callback, [thisArg]", + forEach: "callback, [thisArg]", + includes: "searchValue, [startIndex=0]", + indexOf: "searchValue, [startIndex=0]", + join: "[separator=\",\"]", + lastIndexOf: "searchValue, [startIndex=length]", + map: "callback, [thisArg]", + push: "value, ...", + reduce: "callback, [initialValue]", + reduceRight: "callback, [initialValue]", + slice: "[startIndex=0], [endIndex=length]", + some: "callback, [thisArg]", + sort: "[compareFunction]", + splice: "startIndex, [deleteCount=0], ...itemsToAdd", + __proto__: null, + }, + + ArrayBuffer: { + slice: "startIndex, [endIndex=byteLength]", + __proto__: null, + }, + + DataView: { + setInt8: "byteOffset, value", + setInt16: "byteOffset, value, [littleEndian=false]", + setInt23: "byteOffset, value, [littleEndian=false]", + setUint8: "byteOffset, value", + setUint16: "byteOffset, value, [littleEndian=false]", + setUint32: "byteOffset, value, [littleEndian=false]", + setFloat32: "byteOffset, value, [littleEndian=false]", + setFloat64: "byteOffset, value, [littleEndian=false]", + getInt8: "byteOffset", + getInt16: "byteOffset, [littleEndian=false]", + getInt23: "byteOffset, [littleEndian=false]", + getUint8: "byteOffset", + getUint16: "byteOffset, [littleEndian=false]", + getUint32: "byteOffset, [littleEndian=false]", + getFloat32: "byteOffset, [littleEndian=false]", + getFloat64: "byteOffset, [littleEndian=false]", + __proto__: null, + }, + + Date: { + setDate: "day", + setFullYear: "year, [month=getMonth()], [day=getDate()]", + setHours: "hours, [minutes=getMinutes()], [seconds=getSeconds()], [ms=getMilliseconds()]", + setMilliseconds: "ms", + setMinutes: "minutes, [seconds=getSeconds()], [ms=getMilliseconds()]", + setMonth: "month, [day=getDate()]", + setSeconds: "seconds, [ms=getMilliseconds()]", + setTime: "time", + setUTCDate: "day", + setUTCFullYear: "year, [month=getUTCMonth()], [day=getUTCDate()]", + setUTCHours: "hours, [minutes=getUTCMinutes()], [seconds=getUTCSeconds()], [ms=getUTCMilliseconds()]", + setUTCMilliseconds: "ms", + setUTCMinutes: "minutes, [seconds=getUTCSeconds()], [ms=getUTCMilliseconds()]", + setUTCMonth: "month, [day=getUTCDate()]", + setUTCSeconds: "seconds, [ms=getUTCMilliseconds()]", + setUTCTime: "time", + setYear: "year", + __proto__: null, + }, + + Function: { + apply: "thisObject, [argumentsArray]", + bind: "thisObject, ...arguments", + call: "thisObject, ...arguments", + __proto__: null, + }, + + Map: { + delete: "key", + forEach: "callback, [thisArg]", + get: "key", + has: "key", + set: "key, value", + __proto__: null, + }, + + Number: { + toExponential: "[digits]", + toFixed: "[digits]", + toPrecision: "[significantDigits]", + toString: "[radix=10]", + __proto__: null, + }, + + RegExp: { + compile: "pattern, flags", + exec: "string", + test: "string", + __proto__: null, + }, + + Set: { + delete: "value", + forEach: "callback, [thisArg]", + has: "value", + add: "value", + __proto__: null, + }, + + String: { + charAt: "index", + charCodeAt: "index", + codePoints: "index", + concat: "string, ...", + includes: "searchValue, [startIndex=0]", + indexOf: "searchValue, [startIndex=0]", + lastIndexOf: "searchValue, [startIndex=length]", + localeCompare: "string", + match: "regex", + repeat: "count", + replace: "regex|string, replaceString|replaceHandler, [flags]", + search: "regex", + slice: "startIndex, [endIndex=length]", + split: "[separator], [limit]", + substr: "startIndex, [numberOfCharacters]", + substring: "startIndex, [endIndex=length]", + __proto__: null, + }, + + WeakMap: { + delete: "key", + get: "key", + has: "key", + set: "key, value", + __proto__: null, + }, + + WeakSet: { + delete: "value", + has: "value", + add: "value", + __proto__: null, + }, + + Promise: { + catch: "rejectionHandler", + then: "resolvedHandler, rejectionHandler", + __proto__: null, + }, + + Generator: { + next: "value", + return: "value", + throw: "exception", + __proto__: null, + }, + + // Curated DOM Interfaces. + + Element: { + closest: "selectors", + getAttribute: "attributeName", + getAttributeNS: "namespace, attributeName", + getAttributeNode: "attributeName", + getAttributeNodeNS: "namespace, attributeName", + hasAttribute: "attributeName", + hasAttributeNS: "namespace, attributeName", + matches: "selector", + removeAttribute: "attributeName", + removeAttributeNS: "namespace, attributeName", + removeAttributeNode: "attributeName", + scrollByLines: "[lines]", + scrollByPages: "[pages]", + scrollIntoView: "[alignWithTop]", + scrollIntoViewIfNeeded: "[centerIfNeeded]", + setAttribute: "name, value", + setAttributeNS: "namespace, name, value", + setAttributeNode: "attributeNode", + setAttributeNodeNS: "namespace, attributeNode", + webkitMatchesSelector: "selectors", + __proto__: null, + }, + + Node: { + appendChild: "child", + cloneNode: "[deep]", + compareDocumentPosition: "[node]", + contains: "[node]", + insertBefore: "insertElement, referenceElement", + isDefaultNamespace: "[namespace]", + isEqualNode: "[node]", + lookupNamespaceURI: "prefix", + removeChild: "node", + replaceChild: "newChild, oldChild", + __proto__: null, + }, + + Window: { + alert: "[message]", + atob: "encodedData", + btoa: "stringToEncode", + cancelAnimationFrame: "id", + clearInterval: "intervalId", + clearTimeout: "timeoutId", + confirm: "[message]", + find: "string, [caseSensitive], [backwards], [wrapAround]", + getComputedStyle: "[element], [pseudoElement]", + getMatchedCSSRules: "[element], [pseudoElement]", + matchMedia: "mediaQueryString", + moveBy: "[deltaX], [deltaY]", + moveTo: "[screenX], [screenY]", + open: "url, windowName, [featuresString]", + openDatabase: "name, version, displayName, estimatedSize, [creationCallback]", + postMessage: "message, targetOrigin, [...transferables]", + prompt: "[message], [value]", + requestAnimationFrame: "callback", + resizeBy: "[deltaX], [deltaY]", + resizeTo: "[width], [height]", + scrollBy: "[deltaX], [deltaY]", + scrollTo: "[x], [y]", + setInterval: "func, [delay], [...params]", + setTimeout: "func, [delay], [...params]", + showModalDialog: "url, [arguments], [options]", + __proto__: null, + }, + + Document: { + adoptNode: "[node]", + caretRangeFromPoint: "[x], [y]", + createAttribute: "attributeName", + createAttributeNS: "namespace, qualifiedName", + createCDATASection: "data", + createComment: "data", + createElement: "tagName", + createElementNS: "namespace, qualifiedName", + createEntityReference: "name", + createEvent: "type", + createExpression: "xpath, resolver", + createNSResolver: "node", + createNodeIterator: "root, whatToShow, filter", + createProcessingInstruction: "target, data", + createTextNode: "data", + createTreeWalker: "root, whatToShow, filter, entityReferenceExpansion", + elementFromPoint: "x, y", + evaluate: "xpath, contextNode, namespaceResolver, resultType, result", + execCommand: "command, userInterface, value", + getCSSCanvasContext: "contextId, name, width, height", + getElementById: "id", + getElementsByName: "name", + getOverrideStyle: "[element], [pseudoElement]", + importNode: "node, deep", + queryCommandEnabled: "command", + queryCommandIndeterm: "command", + queryCommandState: "command", + queryCommandSupported: "command", + queryCommandValue: "command", + __proto__: null, + }, + + // Autogenerated DOM Interfaces. + + ANGLEInstancedArrays: { + drawArraysInstancedANGLE: "mode, first, count, primcount", + drawElementsInstancedANGLE: "mode, count, type, offset, primcount", + vertexAttribDivisorANGLE: "index, divisor", + __proto__: null, + }, + + AnalyserNode: { + getByteFrequencyData: "array", + getByteTimeDomainData: "array", + getFloatFrequencyData: "array", + __proto__: null, + }, + + AudioBuffer: { + getChannelData: "channelIndex", + __proto__: null, + }, + + AudioBufferCallback: { + handleEvent: "audioBuffer", + __proto__: null, + }, + + AudioBufferSourceNode: { + noteGrainOn: "when, grainOffset, grainDuration", + noteOff: "when", + noteOn: "when", + start: "[when], [grainOffset], [grainDuration]", + stop: "[when]", + __proto__: null, + }, + + AudioListener: { + setOrientation: "x, y, z, xUp, yUp, zUp", + setPosition: "x, y, z", + setVelocity: "x, y, z", + __proto__: null, + }, + + AudioNode: { + connect: "destination, [output], [input]", + disconnect: "[output]", + __proto__: null, + }, + + AudioParam: { + cancelScheduledValues: "startTime", + exponentialRampToValueAtTime: "value, time", + linearRampToValueAtTime: "value, time", + setTargetAtTime: "target, time, timeConstant", + setTargetValueAtTime: "targetValue, time, timeConstant", + setValueAtTime: "value, time", + setValueCurveAtTime: "values, time, duration", + __proto__: null, + }, + + AudioTrackList: { + getTrackById: "id", + item: "index", + __proto__: null, + }, + + BiquadFilterNode: { + getFrequencyResponse: "frequencyHz, magResponse, phaseResponse", + __proto__: null, + }, + + Blob: { + slice: "[start], [end], [contentType]", + __proto__: null, + }, + + CSS: { + supports: "property, value", + __proto__: null, + }, + + CSSKeyframesRule: { + appendRule: "[rule]", + deleteRule: "[key]", + findRule: "[key]", + insertRule: "[rule]", + __proto__: null, + }, + + CSSMediaRule: { + deleteRule: "[index]", + insertRule: "[rule], [index]", + __proto__: null, + }, + + CSSPrimitiveValue: { + getFloatValue: "[unitType]", + setFloatValue: "[unitType], [floatValue]", + setStringValue: "[stringType], [stringValue]", + __proto__: null, + }, + + CSSRuleList: { + item: "[index]", + __proto__: null, + }, + + CSSStyleDeclaration: { + getPropertyCSSValue: "[propertyName]", + getPropertyPriority: "[propertyName]", + getPropertyShorthand: "[propertyName]", + getPropertyValue: "[propertyName]", + isPropertyImplicit: "[propertyName]", + item: "[index]", + removeProperty: "[propertyName]", + setProperty: "[propertyName], [value], [priority]", + __proto__: null, + }, + + CSSStyleSheet: { + addRule: "[selector], [style], [index]", + deleteRule: "[index]", + insertRule: "[rule], [index]", + removeRule: "[index]", + __proto__: null, + }, + + CSSSupportsRule: { + deleteRule: "[index]", + insertRule: "[rule], [index]", + __proto__: null, + }, + + CSSValueList: { + item: "[index]", + __proto__: null, + }, + + CanvasGradient: { + addColorStop: "[offset], [color]", + __proto__: null, + }, + + CanvasRenderingContext2D: { + arc: "x, y, radius, startAngle, endAngle, [anticlockwise]", + arcTo: "x1, y1, x2, y2, radius", + bezierCurveTo: "cp1x, cp1y, cp2x, cp2y, x, y", + clearRect: "x, y, width, height", + clip: "path, [winding]", + createImageData: "imagedata", + createLinearGradient: "x0, y0, x1, y1", + createPattern: "canvas, repetitionType", + createRadialGradient: "x0, y0, r0, x1, y1, r1", + drawFocusIfNeeded: "element", + drawImage: "image, x, y", + drawImageFromRect: "image, [sx], [sy], [sw], [sh], [dx], [dy], [dw], [dh], [compositeOperation]", + ellipse: "x, y, radiusX, radiusY, rotation, startAngle, endAngle, [anticlockwise]", + fill: "path, [winding]", + fillRect: "x, y, width, height", + fillText: "text, x, y, [maxWidth]", + getImageData: "sx, sy, sw, sh", + isPointInPath: "path, x, y, [winding]", + isPointInStroke: "path, x, y", + lineTo: "x, y", + measureText: "text", + moveTo: "x, y", + putImageData: "imagedata, dx, dy", + quadraticCurveTo: "cpx, cpy, x, y", + rect: "x, y, width, height", + rotate: "angle", + scale: "sx, sy", + setAlpha: "[alpha]", + setCompositeOperation: "[compositeOperation]", + setFillColor: "color, [alpha]", + setLineCap: "[cap]", + setLineDash: "dash", + setLineJoin: "[join]", + setLineWidth: "[width]", + setMiterLimit: "[limit]", + setShadow: "width, height, blur, [color], [alpha]", + setStrokeColor: "color, [alpha]", + setTransform: "m11, m12, m21, m22, dx, dy", + stroke: "path", + strokeRect: "x, y, width, height", + strokeText: "text, x, y, [maxWidth]", + transform: "m11, m12, m21, m22, dx, dy", + translate: "tx, ty", + webkitGetImageDataHD: "sx, sy, sw, sh", + webkitPutImageDataHD: "imagedata, dx, dy", + __proto__: null, + }, + + CharacterData: { + appendData: "[data]", + deleteData: "[offset], [length]", + insertData: "[offset], [data]", + replaceData: "[offset], [length], [data]", + substringData: "[offset], [length]", + __proto__: null, + }, + + ClientRectList: { + item: "[index]", + __proto__: null, + }, + + CommandLineAPIHost: { + copyText: "text", + databaseId: "database", + getEventListeners: "node", + inspect: "objectId, hints", + storageId: "storage", + __proto__: null, + }, + + CompositionEvent: { + initCompositionEvent: "[typeArg], [canBubbleArg], [cancelableArg], [viewArg], [dataArg]", + __proto__: null, + }, + + Crypto: { + getRandomValues: "array", + __proto__: null, + }, + + CustomElementRegistry: { + define: "name, constructor", + get: "name", + whenDefined: "name", + __proto__: null, + }, + + CustomEvent: { + initCustomEvent: "[typeArg], [canBubbleArg], [cancelableArg], [detailArg]", + __proto__: null, + }, + + DOMApplicationCache: { + /* EventTarget */ + __proto__: null, + }, + + DOMImplementation: { + createCSSStyleSheet: "[title], [media]", + createDocument: "[namespaceURI], [qualifiedName], [doctype]", + createDocumentType: "[qualifiedName], [publicId], [systemId]", + createHTMLDocument: "[title]", + hasFeature: "[feature], [version]", + __proto__: null, + }, + + DOMParser: { + parseFromString: "[str], [contentType]", + __proto__: null, + }, + + DOMStringList: { + contains: "[string]", + item: "[index]", + __proto__: null, + }, + + DOMTokenList: { + add: "tokens...", + contains: "token", + item: "index", + remove: "tokens...", + toggle: "token, [force]", + __proto__: null, + }, + + DataTransfer: { + clearData: "[type]", + getData: "type", + setData: "type, data", + setDragImage: "image, x, y", + __proto__: null, + }, + + DataTransferItem: { + getAsString: "[callback]", + __proto__: null, + }, + + DataTransferItemList: { + add: "file", + item: "[index]", + __proto__: null, + }, + + Database: { + changeVersion: "oldVersion, newVersion, [callback], [errorCallback], [successCallback]", + readTransaction: "callback, [errorCallback], [successCallback]", + transaction: "callback, [errorCallback], [successCallback]", + __proto__: null, + }, + + DatabaseCallback: { + handleEvent: "database", + __proto__: null, + }, + + DedicatedWorkerGlobalScope: { + postMessage: "message, [messagePorts]", + __proto__: null, + }, + + DeviceMotionEvent: { + initDeviceMotionEvent: "[type], [bubbles], [cancelable], [acceleration], [accelerationIncludingGravity], [rotationRate], [interval]", + __proto__: null, + }, + + DeviceOrientationEvent: { + initDeviceOrientationEvent: "[type], [bubbles], [cancelable], [alpha], [beta], [gamma], [absolute]", + __proto__: null, + }, + + DocumentFragment: { + getElementById: "id", + querySelector: "selectors", + querySelectorAll: "selectors", + __proto__: null, + }, + + Event: { + initEvent: "type, canBubble, cancelable", + __proto__: null, + }, + + FileList: { + item: "index", + __proto__: null, + }, + + FileReader: { + readAsArrayBuffer: "blob", + readAsBinaryString: "blob", + readAsDataURL: "blob", + readAsText: "blob, [encoding]", + __proto__: null, + }, + + FileReaderSync: { + readAsArrayBuffer: "blob", + readAsBinaryString: "blob", + readAsDataURL: "blob", + readAsText: "blob, [encoding]", + __proto__: null, + }, + + FontFaceSet: { + add: "font", + check: "font, [text=\" \"]", + delete: "font", + load: "font, [text=\" \"]", + __proto__: null, + }, + + FormData: { + append: "[name], [value], [filename]", + __proto__: null, + }, + + Geolocation: { + clearWatch: "watchID", + getCurrentPosition: "successCallback, [errorCallback], [options]", + watchPosition: "successCallback, [errorCallback], [options]", + __proto__: null, + }, + + HTMLAllCollection: { + item: "[index]", + namedItem: "name", + tags: "name", + __proto__: null, + }, + + HTMLButtonElement: { + setCustomValidity: "error", + __proto__: null, + }, + + HTMLCanvasElement: { + getContext: "contextId", + toDataURL: "[type]", + __proto__: null, + }, + + HTMLCollection: { + item: "[index]", + namedItem: "[name]", + __proto__: null, + }, + + HTMLDocument: { + write: "[html]", + writeln: "[html]", + __proto__: null, + }, + + HTMLElement: { + insertAdjacentElement: "[position], [element]", + insertAdjacentHTML: "[position], [html]", + insertAdjacentText: "[position], [text]", + __proto__: null, + }, + + HTMLFieldSetElement: { + setCustomValidity: "error", + __proto__: null, + }, + + HTMLFormControlsCollection: { + namedItem: "[name]", + __proto__: null, + }, + + HTMLInputElement: { + setCustomValidity: "error", + setRangeText: "replacement", + setSelectionRange: "start, end, [direction]", + stepDown: "[n]", + stepUp: "[n]", + __proto__: null, + }, + + HTMLKeygenElement: { + setCustomValidity: "error", + __proto__: null, + }, + + HTMLMediaElement: { + addTextTrack: "kind, [label], [language]", + canPlayType: "[type], [keySystem]", + fastSeek: "time", + webkitAddKey: "keySystem, key, [initData], [sessionId]", + webkitCancelKeyRequest: "keySystem, [sessionId]", + webkitGenerateKeyRequest: "keySystem, [initData]", + webkitSetMediaKeys: "mediaKeys", + __proto__: null, + }, + + HTMLObjectElement: { + setCustomValidity: "error", + __proto__: null, + }, + + HTMLOptionsCollection: { + add: "element, [before]", + namedItem: "[name]", + remove: "[index]", + __proto__: null, + }, + + HTMLOutputElement: { + setCustomValidity: "error", + __proto__: null, + }, + + HTMLSelectElement: { + add: "element, [before]", + item: "index", + namedItem: "[name]", + setCustomValidity: "error", + __proto__: null, + }, + + HTMLSlotElement: { + assignedNodes: "[options]", + __proto__: null, + }, + + HTMLTableElement: { + deleteRow: "index", + insertRow: "[index]", + __proto__: null, + }, + + HTMLTableRowElement: { + deleteCell: "index", + insertCell: "[index]", + __proto__: null, + }, + + HTMLTableSectionElement: { + deleteRow: "index", + insertRow: "[index]", + __proto__: null, + }, + + HTMLTextAreaElement: { + setCustomValidity: "error", + setRangeText: "replacement", + setSelectionRange: "[start], [end], [direction]", + __proto__: null, + }, + + HTMLVideoElement: { + webkitSetPresentationMode: "mode", + webkitSupportsPresentationMode: "mode", + __proto__: null, + }, + + HashChangeEvent: { + initHashChangeEvent: "[type], [canBubble], [cancelable], [oldURL], [newURL]", + __proto__: null, + }, + + History: { + go: "[distance]", + pushState: "data, title, [url]", + replaceState: "data, title, [url]", + __proto__: null, + }, + + IDBCursor: { + advance: "count", + continue: "[key]", + update: "value", + __proto__: null, + }, + + IDBDatabase: { + createObjectStore: "name, [options]", + deleteObjectStore: "name", + transaction: "storeName, [mode]", + __proto__: null, + }, + + IDBFactory: { + cmp: "first, second", + deleteDatabase: "name", + open: "name, [version]", + __proto__: null, + }, + + IDBIndex: { + count: "[range]", + get: "key", + getKey: "key", + openCursor: "[range], [direction]", + openKeyCursor: "[range], [direction]", + __proto__: null, + }, + + IDBObjectStore: { + add: "value, [key]", + count: "[range]", + createIndex: "name, keyPath, [options]", + delete: "keyRange", + deleteIndex: "name", + get: "key", + index: "name", + openCursor: "[range], [direction]", + put: "value, [key]", + __proto__: null, + }, + + IDBTransaction: { + objectStore: "name", + __proto__: null, + }, + + KeyboardEvent: { + initKeyboardEvent: "[type], [canBubble], [cancelable], [view], [keyIdentifier], [location], [ctrlKey], [altKey], [shiftKey], [metaKey], [altGraphKey]", + __proto__: null, + }, + + Location: { + assign: "[url]", + reload: "[force=false]", + replace: "[url]", + __proto__: null, + }, + + MediaController: { + /* EventTarget */ + __proto__: null, + }, + + MediaControlsHost: { + displayNameForTrack: "track", + mediaUIImageData: "partID", + setSelectedTextTrack: "track", + sortedTrackListForMenu: "trackList", + __proto__: null, + }, + + MediaList: { + appendMedium: "[newMedium]", + deleteMedium: "[oldMedium]", + item: "[index]", + __proto__: null, + }, + + MediaQueryList: { + addListener: "[listener]", + removeListener: "[listener]", + __proto__: null, + }, + + MediaQueryListListener: { + queryChanged: "[list]", + __proto__: null, + }, + + MediaSource: { + addSourceBuffer: "type", + endOfStream: "[error]", + removeSourceBuffer: "buffer", + __proto__: null, + }, + + MediaStreamTrack: { + applyConstraints: "constraints", + __proto__: null, + }, + + MediaStreamTrackSourcesCallback: { + handleEvent: "sources", + __proto__: null, + }, + + MessageEvent: { + initMessageEvent: "[typeArg], [canBubbleArg], [cancelableArg], [dataArg], [originArg], [lastEventIdArg], [sourceArg], [messagePorts]", + webkitInitMessageEvent: "[typeArg], [canBubbleArg], [cancelableArg], [dataArg], [originArg], [lastEventIdArg], [sourceArg], [transferables]", + __proto__: null, + }, + + MessagePort: { + /* EventTarget */ + __proto__: null, + }, + + MimeTypeArray: { + item: "[index]", + namedItem: "[name]", + __proto__: null, + }, + + MouseEvent: { + initMouseEvent: "[type], [canBubble], [cancelable], [view], [detail], [screenX], [screenY], [clientX], [clientY], [ctrlKey], [altKey], [shiftKey], [metaKey], [button], [relatedTarget]", + __proto__: null, + }, + + MutationEvent: { + initMutationEvent: "[type], [canBubble], [cancelable], [relatedNode], [prevValue], [newValue], [attrName], [attrChange]", + __proto__: null, + }, + + MutationObserver: { + observe: "target, options", + __proto__: null, + }, + + NamedNodeMap: { + getNamedItem: "[name]", + getNamedItemNS: "[namespaceURI], [localName]", + item: "[index]", + removeNamedItem: "[name]", + removeNamedItemNS: "[namespaceURI], [localName]", + setNamedItem: "[node]", + setNamedItemNS: "[node]", + __proto__: null, + }, + + Navigator: { + getUserMedia: "options, successCallback, errorCallback", + __proto__: null, + }, + + NavigatorUserMediaErrorCallback: { + handleEvent: "error", + __proto__: null, + }, + + NavigatorUserMediaSuccessCallback: { + handleEvent: "stream", + __proto__: null, + }, + + NodeFilter: { + acceptNode: "[n]", + __proto__: null, + }, + + NodeList: { + item: "index", + __proto__: null, + }, + + Notification: { + /* EventTarget */ + __proto__: null, + }, + + NotificationCenter: { + createNotification: "iconUrl, title, body", + requestPermission: "[callback]", + __proto__: null, + }, + + NotificationPermissionCallback: { + handleEvent: "permission", + __proto__: null, + }, + + OESVertexArrayObject: { + bindVertexArrayOES: "[arrayObject]", + deleteVertexArrayOES: "[arrayObject]", + isVertexArrayOES: "[arrayObject]", + __proto__: null, + }, + + OscillatorNode: { + noteOff: "when", + noteOn: "when", + setPeriodicWave: "wave", + start: "[when]", + stop: "[when]", + __proto__: null, + }, + + Path2D: { + addPath: "path, [transform]", + arc: "[x], [y], [radius], [startAngle], [endAngle], [anticlockwise]", + arcTo: "[x1], [y1], [x2], [y2], [radius]", + bezierCurveTo: "[cp1x], [cp1y], [cp2x], [cp2y], [x], [y]", + ellipse: "x, y, radiusX, radiusY, rotation, startAngle, endAngle, [anticlockwise]", + lineTo: "[x], [y]", + moveTo: "[x], [y]", + quadraticCurveTo: "[cpx], [cpy], [x], [y]", + rect: "[x], [y], [width], [height]", + __proto__: null, + }, + + Performance: { + clearMarks: "[name]", + clearMeasures: "name", + getEntriesByName: "name, [type]", + getEntriesByType: "type", + mark: "name", + measure: "name, [startMark], [endMark]", + __proto__: null, + }, + + PerformanceObserver: { + observe: "options", + __proto__: null, + }, + + PerformanceObserverEntryList: { + getEntriesByName: "name, [type]", + getEntriesByType: "type", + __proto__: null, + }, + + Plugin: { + item: "[index]", + namedItem: "[name]", + __proto__: null, + }, + + PluginArray: { + item: "[index]", + namedItem: "[name]", + refresh: "[reload]", + __proto__: null, + }, + + PositionCallback: { + handleEvent: "position", + __proto__: null, + }, + + PositionErrorCallback: { + handleEvent: "error", + __proto__: null, + }, + + QuickTimePluginReplacement: { + postEvent: "eventName", + __proto__: null, + }, + + RTCDTMFSender: { + insertDTMF: "tones, [duration], [interToneGap]", + __proto__: null, + }, + + RTCDataChannel: { + send: "data", + __proto__: null, + }, + + RTCPeerConnectionErrorCallback: { + handleEvent: "error", + __proto__: null, + }, + + RTCSessionDescriptionCallback: { + handleEvent: "sdp", + __proto__: null, + }, + + RTCStatsCallback: { + handleEvent: "response", + __proto__: null, + }, + + RTCStatsReport: { + stat: "name", + __proto__: null, + }, + + RTCStatsResponse: { + namedItem: "[name]", + __proto__: null, + }, + + Range: { + collapse: "[toStart]", + compareBoundaryPoints: "[how], [sourceRange]", + compareNode: "[refNode]", + comparePoint: "[refNode], [offset]", + createContextualFragment: "[html]", + expand: "[unit]", + insertNode: "[newNode]", + intersectsNode: "[refNode]", + isPointInRange: "[refNode], [offset]", + selectNode: "[refNode]", + selectNodeContents: "[refNode]", + setEnd: "[refNode], [offset]", + setEndAfter: "[refNode]", + setEndBefore: "[refNode]", + setStart: "[refNode], [offset]", + setStartAfter: "[refNode]", + setStartBefore: "[refNode]", + surroundContents: "[newParent]", + __proto__: null, + }, + + ReadableStream: { + cancel: "reason", + pipeThrough: "dest, options", + pipeTo: "streams, options", + __proto__: null, + }, + + WritableStream: { + abort: "reason", + close: "", + write: "chunk", + __proto__: null, + }, + + RequestAnimationFrameCallback: { + handleEvent: "highResTime", + __proto__: null, + }, + + SQLResultSetRowList: { + item: "index", + __proto__: null, + }, + + SQLStatementCallback: { + handleEvent: "transaction, resultSet", + __proto__: null, + }, + + SQLStatementErrorCallback: { + handleEvent: "transaction, error", + __proto__: null, + }, + + SQLTransaction: { + executeSql: "sqlStatement, arguments, [callback], [errorCallback]", + __proto__: null, + }, + + SQLTransactionCallback: { + handleEvent: "transaction", + __proto__: null, + }, + + SQLTransactionErrorCallback: { + handleEvent: "error", + __proto__: null, + }, + + SVGAngle: { + convertToSpecifiedUnits: "unitType", + newValueSpecifiedUnits: "unitType, valueInSpecifiedUnits", + __proto__: null, + }, + + SVGAnimationElement: { + beginElementAt: "[offset]", + endElementAt: "[offset]", + hasExtension: "[extension]", + __proto__: null, + }, + + SVGColor: { + setColor: "colorType, rgbColor, iccColor", + setRGBColor: "rgbColor", + setRGBColorICCColor: "rgbColor, iccColor", + __proto__: null, + }, + + SVGCursorElement: { + hasExtension: "[extension]", + __proto__: null, + }, + + SVGDocument: { + createEvent: "[eventType]", + __proto__: null, + }, + + SVGElement: { + getPresentationAttribute: "[name]", + __proto__: null, + }, + + SVGFEDropShadowElement: { + setStdDeviation: "[stdDeviationX], [stdDeviationY]", + __proto__: null, + }, + + SVGFEGaussianBlurElement: { + setStdDeviation: "[stdDeviationX], [stdDeviationY]", + __proto__: null, + }, + + SVGFEMorphologyElement: { + setRadius: "[radiusX], [radiusY]", + __proto__: null, + }, + + SVGFilterElement: { + setFilterRes: "[filterResX], [filterResY]", + __proto__: null, + }, + + SVGGraphicsElement: { + getTransformToElement: "[element]", + hasExtension: "[extension]", + __proto__: null, + }, + + SVGLength: { + convertToSpecifiedUnits: "unitType", + newValueSpecifiedUnits: "unitType, valueInSpecifiedUnits", + __proto__: null, + }, + + SVGLengthList: { + appendItem: "item", + getItem: "index", + initialize: "item", + insertItemBefore: "item, index", + removeItem: "index", + replaceItem: "item, index", + __proto__: null, + }, + + SVGMarkerElement: { + setOrientToAngle: "[angle]", + __proto__: null, + }, + + SVGMaskElement: { + hasExtension: "[extension]", + __proto__: null, + }, + + SVGMatrix: { + multiply: "secondMatrix", + rotate: "angle", + rotateFromVector: "x, y", + scale: "scaleFactor", + scaleNonUniform: "scaleFactorX, scaleFactorY", + skewX: "angle", + skewY: "angle", + translate: "x, y", + __proto__: null, + }, + + SVGNumberList: { + appendItem: "item", + getItem: "index", + initialize: "item", + insertItemBefore: "item, index", + removeItem: "index", + replaceItem: "item, index", + __proto__: null, + }, + + SVGPaint: { + setPaint: "paintType, uri, rgbColor, iccColor", + setUri: "uri", + __proto__: null, + }, + + SVGPathElement: { + createSVGPathSegArcAbs: "[x], [y], [r1], [r2], [angle], [largeArcFlag], [sweepFlag]", + createSVGPathSegArcRel: "[x], [y], [r1], [r2], [angle], [largeArcFlag], [sweepFlag]", + createSVGPathSegCurvetoCubicAbs: "[x], [y], [x1], [y1], [x2], [y2]", + createSVGPathSegCurvetoCubicRel: "[x], [y], [x1], [y1], [x2], [y2]", + createSVGPathSegCurvetoCubicSmoothAbs: "[x], [y], [x2], [y2]", + createSVGPathSegCurvetoCubicSmoothRel: "[x], [y], [x2], [y2]", + createSVGPathSegCurvetoQuadraticAbs: "[x], [y], [x1], [y1]", + createSVGPathSegCurvetoQuadraticRel: "[x], [y], [x1], [y1]", + createSVGPathSegCurvetoQuadraticSmoothAbs: "[x], [y]", + createSVGPathSegCurvetoQuadraticSmoothRel: "[x], [y]", + createSVGPathSegLinetoAbs: "[x], [y]", + createSVGPathSegLinetoHorizontalAbs: "[x]", + createSVGPathSegLinetoHorizontalRel: "[x]", + createSVGPathSegLinetoRel: "[x], [y]", + createSVGPathSegLinetoVerticalAbs: "[y]", + createSVGPathSegLinetoVerticalRel: "[y]", + createSVGPathSegMovetoAbs: "[x], [y]", + createSVGPathSegMovetoRel: "[x], [y]", + getPathSegAtLength: "[distance]", + getPointAtLength: "[distance]", + __proto__: null, + }, + + SVGPathSegList: { + appendItem: "newItem", + getItem: "index", + initialize: "newItem", + insertItemBefore: "newItem, index", + removeItem: "index", + replaceItem: "newItem, index", + __proto__: null, + }, + + SVGPatternElement: { + hasExtension: "[extension]", + __proto__: null, + }, + + SVGPoint: { + matrixTransform: "matrix", + __proto__: null, + }, + + SVGPointList: { + appendItem: "item", + getItem: "index", + initialize: "item", + insertItemBefore: "item, index", + removeItem: "index", + replaceItem: "item, index", + __proto__: null, + }, + + SVGSVGElement: { + checkEnclosure: "[element], [rect]", + checkIntersection: "[element], [rect]", + createSVGTransformFromMatrix: "[matrix]", + getElementById: "[elementId]", + getEnclosureList: "[rect], [referenceElement]", + getIntersectionList: "[rect], [referenceElement]", + setCurrentTime: "[seconds]", + suspendRedraw: "[maxWaitMilliseconds]", + unsuspendRedraw: "[suspendHandleId]", + __proto__: null, + }, + + SVGStringList: { + appendItem: "item", + getItem: "index", + initialize: "item", + insertItemBefore: "item, index", + removeItem: "index", + replaceItem: "item, index", + __proto__: null, + }, + + SVGTextContentElement: { + getCharNumAtPosition: "[point]", + getEndPositionOfChar: "[offset]", + getExtentOfChar: "[offset]", + getRotationOfChar: "[offset]", + getStartPositionOfChar: "[offset]", + getSubStringLength: "[offset], [length]", + selectSubString: "[offset], [length]", + __proto__: null, + }, + + SVGTransform: { + setMatrix: "matrix", + setRotate: "angle, cx, cy", + setScale: "sx, sy", + setSkewX: "angle", + setSkewY: "angle", + setTranslate: "tx, ty", + __proto__: null, + }, + + SVGTransformList: { + appendItem: "item", + createSVGTransformFromMatrix: "matrix", + getItem: "index", + initialize: "item", + insertItemBefore: "item, index", + removeItem: "index", + replaceItem: "item, index", + __proto__: null, + }, + + SecurityPolicy: { + allowsConnectionTo: "url", + allowsFontFrom: "url", + allowsFormAction: "url", + allowsFrameFrom: "url", + allowsImageFrom: "url", + allowsMediaFrom: "url", + allowsObjectFrom: "url", + allowsPluginType: "type", + allowsScriptFrom: "url", + allowsStyleFrom: "url", + __proto__: null, + }, + + Selection: { + addRange: "[range]", + collapse: "[node], [index]", + containsNode: "[node], [allowPartial]", + extend: "[node], [offset]", + getRangeAt: "[index]", + modify: "[alter], [direction], [granularity]", + selectAllChildren: "[node]", + setBaseAndExtent: "[baseNode], [baseOffset], [extentNode], [extentOffset]", + setPosition: "[node], [offset]", + __proto__: null, + }, + + SourceBuffer: { + appendBuffer: "data", + remove: "start, end", + __proto__: null, + }, + + SourceBufferList: { + item: "index", + __proto__: null, + }, + + SpeechSynthesis: { + speak: "utterance", + __proto__: null, + }, + + SpeechSynthesisUtterance: { + /* EventTarget */ + __proto__: null, + }, + + Storage: { + getItem: "key", + key: "index", + removeItem: "key", + setItem: "key, data", + __proto__: null, + }, + + StorageErrorCallback: { + handleEvent: "error", + __proto__: null, + }, + + StorageEvent: { + initStorageEvent: "[typeArg], [canBubbleArg], [cancelableArg], [keyArg], [oldValueArg], [newValueArg], [urlArg], [storageAreaArg]", + __proto__: null, + }, + + StorageInfo: { + queryUsageAndQuota: "storageType, [usageCallback], [errorCallback]", + requestQuota: "storageType, newQuotaInBytes, [quotaCallback], [errorCallback]", + __proto__: null, + }, + + StorageQuota: { + queryUsageAndQuota: "usageCallback, [errorCallback]", + requestQuota: "newQuotaInBytes, [quotaCallback], [errorCallback]", + __proto__: null, + }, + + StorageQuotaCallback: { + handleEvent: "grantedQuotaInBytes", + __proto__: null, + }, + + StorageUsageCallback: { + handleEvent: "currentUsageInBytes, currentQuotaInBytes", + __proto__: null, + }, + + StringCallback: { + handleEvent: "data", + __proto__: null, + }, + + StyleMedia: { + matchMedium: "[mediaquery]", + __proto__: null, + }, + + StyleSheetList: { + item: "[index]", + __proto__: null, + }, + + Text: { + replaceWholeText: "[content]", + splitText: "offset", + __proto__: null, + }, + + TextEvent: { + initTextEvent: "[typeArg], [canBubbleArg], [cancelableArg], [viewArg], [dataArg]", + __proto__: null, + }, + + TextTrack: { + addCue: "cue", + addRegion: "region", + removeCue: "cue", + removeRegion: "region", + __proto__: null, + }, + + TextTrackCue: { + /* EventTarget */ + __proto__: null, + }, + + TextTrackCueList: { + getCueById: "id", + item: "index", + __proto__: null, + }, + + TextTrackList: { + getTrackById: "id", + item: "index", + __proto__: null, + }, + + TimeRanges: { + end: "index", + start: "index", + __proto__: null, + }, + + TouchEvent: { + initTouchEvent: "[touches], [targetTouches], [changedTouches], [type], [view], [screenX], [screenY], [clientX], [clientY], [ctrlKey], [altKey], [shiftKey], [metaKey]", + __proto__: null, + }, + + TouchList: { + item: "index", + __proto__: null, + }, + + UIEvent: { + initUIEvent: "[type], [canBubble], [cancelable], [view], [detail]", + __proto__: null, + }, + + UserMessageHandler: { + postMessage: "message", + __proto__: null, + }, + + VTTRegionList: { + getRegionById: "id", + item: "index", + __proto__: null, + }, + + VideoTrackList: { + getTrackById: "id", + item: "index", + __proto__: null, + }, + + WebGL2RenderingContext: { + beginQuery: "target, query", + beginTransformFeedback: "primitiveMode", + bindBufferBase: "target, index, buffer", + bindBufferRange: "target, index, buffer, offset, size", + bindSampler: "unit, sampler", + bindTransformFeedback: "target, id", + bindVertexArray: "vertexArray", + blitFramebuffer: "srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter", + clearBufferfi: "buffer, drawbuffer, depth, stencil", + clearBufferfv: "buffer, drawbuffer, value", + clearBufferiv: "buffer, drawbuffer, value", + clearBufferuiv: "buffer, drawbuffer, value", + clientWaitSync: "sync, flags, timeout", + compressedTexImage3D: "target, level, internalformat, width, height, depth, border, imageSize, data", + compressedTexSubImage3D: "target, level, xoffset, yoffset, zoffset, width, height, depth, format, imageSize, data", + copyBufferSubData: "readTarget, writeTarget, readOffset, writeOffset, size", + copyTexSubImage3D: "target, level, xoffset, yoffset, zoffset, x, y, width, height", + deleteQuery: "query", + deleteSampler: "sampler", + deleteSync: "sync", + deleteTransformFeedback: "id", + deleteVertexArray: "vertexArray", + drawArraysInstanced: "mode, first, count, instanceCount", + drawBuffers: "buffers", + drawElementsInstanced: "mode, count, type, offset, instanceCount", + drawRangeElements: "mode, start, end, count, type, offset", + endQuery: "target", + fenceSync: "condition, flags", + framebufferTextureLayer: "target, attachment, texture, level, layer", + getActiveUniformBlockName: "program, uniformBlockIndex", + getActiveUniformBlockParameter: "program, uniformBlockIndex, pname", + getActiveUniforms: "program, uniformIndices, pname", + getBufferSubData: "target, offset, returnedData", + getFragDataLocation: "program, name", + getIndexedParameter: "target, index", + getInternalformatParameter: "target, internalformat, pname", + getQuery: "target, pname", + getQueryParameter: "query, pname", + getSamplerParameter: "sampler, pname", + getSyncParameter: "sync, pname", + getTransformFeedbackVarying: "program, index", + getUniformBlockIndex: "program, uniformBlockName", + getUniformIndices: "program, uniformNames", + invalidateFramebuffer: "target, attachments", + invalidateSubFramebuffer: "target, attachments, x, y, width, height", + isQuery: "query", + isSampler: "sampler", + isSync: "sync", + isTransformFeedback: "id", + isVertexArray: "vertexArray", + readBuffer: "src", + renderbufferStorageMultisample: "target, samples, internalformat, width, height", + samplerParameterf: "sampler, pname, param", + samplerParameteri: "sampler, pname, param", + texImage3D: "target, level, internalformat, width, height, depth, border, format, type, pixels", + texStorage2D: "target, levels, internalformat, width, height", + texStorage3D: "target, levels, internalformat, width, height, depth", + texSubImage3D: "target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, pixels", + transformFeedbackVaryings: "program, varyings, bufferMode", + uniform1ui: "location, v0", + uniform1uiv: "location, value", + uniform2ui: "location, v0, v1", + uniform2uiv: "location, value", + uniform3ui: "location, v0, v1, v2", + uniform3uiv: "location, value", + uniform4ui: "location, v0, v1, v2, v3", + uniform4uiv: "location, value", + uniformBlockBinding: "program, uniformBlockIndex, uniformBlockBinding", + uniformMatrix2x3fv: "location, transpose, value", + uniformMatrix2x4fv: "location, transpose, value", + uniformMatrix3x2fv: "location, transpose, value", + uniformMatrix3x4fv: "location, transpose, value", + uniformMatrix4x2fv: "location, transpose, value", + uniformMatrix4x3fv: "location, transpose, value", + vertexAttribDivisor: "index, divisor", + vertexAttribI4i: "index, x, y, z, w", + vertexAttribI4iv: "index, v", + vertexAttribI4ui: "index, x, y, z, w", + vertexAttribI4uiv: "index, v", + vertexAttribIPointer: "index, size, type, stride, offset", + waitSync: "sync, flags, timeout", + __proto__: null, + }, + + WebGLDebugShaders: { + getTranslatedShaderSource: "shader", + __proto__: null, + }, + + WebGLDrawBuffers: { + drawBuffersWEBGL: "buffers", + __proto__: null, + }, + + WebGLRenderingContextBase: { + activeTexture: "texture", + attachShader: "program, shader", + bindAttribLocation: "program, index, name", + bindBuffer: "target, buffer", + bindFramebuffer: "target, framebuffer", + bindRenderbuffer: "target, renderbuffer", + bindTexture: "target, texture", + blendColor: "red, green, blue, alpha", + blendEquation: "mode", + blendEquationSeparate: "modeRGB, modeAlpha", + blendFunc: "sfactor, dfactor", + blendFuncSeparate: "srcRGB, dstRGB, srcAlpha, dstAlpha", + bufferData: "target, data, usage", + bufferSubData: "target, offset, data", + checkFramebufferStatus: "target", + clear: "mask", + clearColor: "red, green, blue, alpha", + clearDepth: "depth", + clearStencil: "s", + colorMask: "red, green, blue, alpha", + compileShader: "shader", + compressedTexImage2D: "target, level, internalformat, width, height, border, data", + compressedTexSubImage2D: "target, level, xoffset, yoffset, width, height, format, data", + copyTexImage2D: "target, level, internalformat, x, y, width, height, border", + copyTexSubImage2D: "target, level, xoffset, yoffset, x, y, width, height", + createShader: "type", + cullFace: "mode", + deleteBuffer: "buffer", + deleteFramebuffer: "framebuffer", + deleteProgram: "program", + deleteRenderbuffer: "renderbuffer", + deleteShader: "shader", + deleteTexture: "texture", + depthFunc: "func", + depthMask: "flag", + depthRange: "zNear, zFar", + detachShader: "program, shader", + disable: "cap", + disableVertexAttribArray: "index", + drawArrays: "mode, first, count", + drawElements: "mode, count, type, offset", + enable: "cap", + enableVertexAttribArray: "index", + framebufferRenderbuffer: "target, attachment, renderbuffertarget, renderbuffer", + framebufferTexture2D: "target, attachment, textarget, texture, level", + frontFace: "mode", + generateMipmap: "target", + getActiveAttrib: "program, index", + getActiveUniform: "program, index", + getAttachedShaders: "program", + getAttribLocation: "program, name", + getBufferParameter: "target, pname", + getExtension: "name", + getFramebufferAttachmentParameter: "target, attachment, pname", + getParameter: "pname", + getProgramInfoLog: "program", + getProgramParameter: "program, pname", + getRenderbufferParameter: "target, pname", + getShaderInfoLog: "shader", + getShaderParameter: "shader, pname", + getShaderPrecisionFormat: "shadertype, precisiontype", + getShaderSource: "shader", + getTexParameter: "target, pname", + getUniform: "program, location", + getUniformLocation: "program, name", + getVertexAttrib: "index, pname", + getVertexAttribOffset: "index, pname", + hint: "target, mode", + isBuffer: "buffer", + isEnabled: "cap", + isFramebuffer: "framebuffer", + isProgram: "program", + isRenderbuffer: "renderbuffer", + isShader: "shader", + isTexture: "texture", + lineWidth: "width", + linkProgram: "program", + pixelStorei: "pname, param", + polygonOffset: "factor, units", + readPixels: "x, y, width, height, format, type, pixels", + renderbufferStorage: "target, internalformat, width, height", + sampleCoverage: "value, invert", + scissor: "x, y, width, height", + shaderSource: "shader, string", + stencilFunc: "func, ref, mask", + stencilFuncSeparate: "face, func, ref, mask", + stencilMask: "mask", + stencilMaskSeparate: "face, mask", + stencilOp: "fail, zfail, zpass", + stencilOpSeparate: "face, fail, zfail, zpass", + texImage2D: "target, level, internalformat, width, height, border, format, type, pixels", + texParameterf: "target, pname, param", + texParameteri: "target, pname, param", + texSubImage2D: "target, level, xoffset, yoffset, width, height, format, type, pixels", + uniform1f: "location, x", + uniform1fv: "location, v", + uniform1i: "location, x", + uniform1iv: "location, v", + uniform2f: "location, x, y", + uniform2fv: "location, v", + uniform2i: "location, x, y", + uniform2iv: "location, v", + uniform3f: "location, x, y, z", + uniform3fv: "location, v", + uniform3i: "location, x, y, z", + uniform3iv: "location, v", + uniform4f: "location, x, y, z, w", + uniform4fv: "location, v", + uniform4i: "location, x, y, z, w", + uniform4iv: "location, v", + uniformMatrix2fv: "location, transpose, array", + uniformMatrix3fv: "location, transpose, array", + uniformMatrix4fv: "location, transpose, array", + useProgram: "program", + validateProgram: "program", + vertexAttrib1f: "indx, x", + vertexAttrib1fv: "indx, values", + vertexAttrib2f: "indx, x, y", + vertexAttrib2fv: "indx, values", + vertexAttrib3f: "indx, x, y, z", + vertexAttrib3fv: "indx, values", + vertexAttrib4f: "indx, x, y, z, w", + vertexAttrib4fv: "indx, values", + vertexAttribPointer: "indx, size, type, normalized, stride, offset", + viewport: "x, y, width, height", + __proto__: null, + }, + + WebKitCSSMatrix: { + multiply: "[secondMatrix]", + rotate: "[rotX], [rotY], [rotZ]", + rotateAxisAngle: "[x], [y], [z], [angle]", + scale: "[scaleX], [scaleY], [scaleZ]", + setMatrixValue: "[string]", + skewX: "[angle]", + skewY: "[angle]", + translate: "[x], [y], [z]", + __proto__: null, + }, + + WebKitMediaKeySession: { + update: "key", + __proto__: null, + }, + + WebKitMediaKeys: { + createSession: "[type], [initData]", + __proto__: null, + }, + + WebKitNamedFlow: { + getRegionsByContent: "contentNode", + __proto__: null, + }, + + WebKitNamedFlowCollection: { + item: "index", + namedItem: "name", + __proto__: null, + }, + + WebKitSubtleCrypto: { + decrypt: "algorithm, key, data", + digest: "algorithm, data", + encrypt: "algorithm, key, data", + exportKey: "format, key", + generateKey: "algorithm, [extractable], [keyUsages]", + importKey: "format, keyData, algorithm, [extractable], [keyUsages]", + sign: "algorithm, key, data", + unwrapKey: "format, wrappedKey, unwrappingKey, unwrapAlgorithm, unwrappedKeyAlgorithm, [extractable], [keyUsages]", + verify: "algorithm, key, signature, data", + wrapKey: "format, key, wrappingKey, wrapAlgorithm", + __proto__: null, + }, + + WebSocket: { + close: "[code], [reason]", + send: "data", + __proto__: null, + }, + + WheelEvent: { + initWebKitWheelEvent: "[wheelDeltaX], [wheelDeltaY], [view], [screenX], [screenY], [clientX], [clientY], [ctrlKey], [altKey], [shiftKey], [metaKey]", + __proto__: null, + }, + + Worker: { + postMessage: "message, [messagePorts]", + __proto__: null, + }, + + WorkerGlobalScope: { + clearInterval: "[handle]", + clearTimeout: "[handle]", + setInterval: "handler, [timeout]", + setTimeout: "handler, [timeout]", + __proto__: null, + }, + + XMLHttpRequest: { + getResponseHeader: "header", + open: "method, url, [async], [user], [password]", + overrideMimeType: "override", + setRequestHeader: "header, value", + __proto__: null, + }, + + XMLHttpRequestUpload: { + /* EventTarget */ + __proto__: null, + }, + + XMLSerializer: { + serializeToString: "[node]", + __proto__: null, + }, + + XPathEvaluator: { + createExpression: "[expression], [resolver]", + createNSResolver: "[nodeResolver]", + evaluate: "[expression], [contextNode], [resolver], [type], [inResult]", + __proto__: null, + }, + + XPathExpression: { + evaluate: "[contextNode], [type], [inResult]", + __proto__: null, + }, + + XPathNSResolver: { + lookupNamespaceURI: "[prefix]", + __proto__: null, + }, + + XPathResult: { + snapshotItem: "[index]", + __proto__: null, + }, + + XSLTProcessor: { + getParameter: "namespaceURI, localName", + importStylesheet: "[stylesheet]", + removeParameter: "namespaceURI, localName", + setParameter: "namespaceURI, localName, value", + transformToDocument: "[source]", + transformToFragment: "[source], [docVal]", + __proto__: null, + }, + + webkitAudioContext: { + createBuffer: "numberOfChannels, numberOfFrames, sampleRate", + createChannelMerger: "[numberOfInputs]", + createChannelSplitter: "[numberOfOutputs]", + createDelay: "[maxDelayTime]", + createDelayNode: "[maxDelayTime]", + createJavaScriptNode: "bufferSize, [numberOfInputChannels], [numberOfOutputChannels]", + createMediaElementSource: "mediaElement", + createPeriodicWave: "real, imag", + createScriptProcessor: "bufferSize, [numberOfInputChannels], [numberOfOutputChannels]", + decodeAudioData: "audioData, successCallback, [errorCallback]", + __proto__: null, + }, + + webkitAudioPannerNode: { + setOrientation: "x, y, z", + setPosition: "x, y, z", + setVelocity: "x, y, z", + __proto__: null, + }, + + webkitMediaStream: { + addTrack: "track", + getTrackById: "trackId", + removeTrack: "track", + __proto__: null, + }, + + webkitRTCPeerConnection: { + addIceCandidate: "candidate, successCallback, failureCallback", + addStream: "stream", + createAnswer: "successCallback, failureCallback, [answerOptions]", + createDTMFSender: "track", + createDataChannel: "label, [options]", + createOffer: "successCallback, failureCallback, [offerOptions]", + getStats: "successCallback, failureCallback, [selector]", + getStreamById: "streamId", + removeStream: "stream", + setLocalDescription: "description, successCallback, failureCallback", + setRemoteDescription: "description, successCallback, failureCallback", + updateIce: "configuration", + __proto__: null, + }, + + EventTarget: { + addEventListener: "type, listener, [useCapture=false]", + removeEventListener: "type, listener, [useCapture=false]", + dispatchEvent: "event", + __proto__: null, + }, +}; + +(function() { + // COMPATIBILITY (iOS 9): EventTarget properties were on instances, now there + // is an actual EventTarget prototype in the chain. + var EventTarget = WebInspector.NativePrototypeFunctionParameters.EventTarget; + var eventTargetTypes = [ + "Node", "Window", + "AudioNode", "AudioTrackList", "DOMApplicationCache", "FileReader", + "MediaController", "MediaStreamTrack", "MessagePort", "Notification", "RTCDTMFSender", + "SpeechSynthesisUtterance", "TextTrack", "TextTrackCue", "TextTrackList", + "VideoTrackList", "WebKitMediaKeySession", "WebKitNamedFlow", "WebSocket", + "WorkerGlobalScope", "XMLHttpRequest", "webkitMediaStream", "webkitRTCPeerConnection" + ]; + for (var type of eventTargetTypes) + Object.assign(WebInspector.NativePrototypeFunctionParameters[type], EventTarget); + + var ElementQueries = { + getElementsByClassName: "classNames", + getElementsByTagName: "tagName", + getElementsByTagNameNS: "namespace, localName", + querySelector: "selectors", + querySelectorAll: "selectors", + }; + Object.assign(WebInspector.NativePrototypeFunctionParameters.Element, ElementQueries); + Object.assign(WebInspector.NativePrototypeFunctionParameters.Document, ElementQueries); + + var ChildNode = { + after: "[node|string]...", + before: "[node|string]...", + replaceWith: "[node|string]...", + }; + Object.assign(WebInspector.NativePrototypeFunctionParameters.Element, ChildNode); + Object.assign(WebInspector.NativePrototypeFunctionParameters.CharacterData, ChildNode); + + var ParentNode = { + append: "[node|string]...", + prepend: "[node|string]...", + }; + Object.assign(WebInspector.NativePrototypeFunctionParameters.Element, ParentNode); + Object.assign(WebInspector.NativePrototypeFunctionParameters.Document, ParentNode); + Object.assign(WebInspector.NativePrototypeFunctionParameters.DocumentFragment, ParentNode); + + // COMPATIBILITY (iOS 9): window.console used to be a Console object instance, + // now it is just a namespace object on the global object. + WebInspector.NativePrototypeFunctionParameters.Console = WebInspector.NativeConstructorFunctionParameters.Console; + +})(); diff --git a/Source/WebInspectorUI/UserInterface/Models/NetworkInstrument.js b/Source/WebInspectorUI/UserInterface/Models/NetworkInstrument.js new file mode 100644 index 000000000..ad8f990f8 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/NetworkInstrument.js @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2015 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. + */ + +WebInspector.NetworkInstrument = class NetworkInstrument extends WebInspector.Instrument +{ + // Protected + + get timelineRecordType() + { + return WebInspector.TimelineRecord.Type.Network; + } + + startInstrumentation(initiatedByBackend) + { + // Nothing to do, network instrumentation is always happening. + } + + stopInstrumentation(initiatedByBackend) + { + // Nothing to do, network instrumentation is always happening. + } +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/NetworkTimeline.js b/Source/WebInspectorUI/UserInterface/Models/NetworkTimeline.js new file mode 100644 index 000000000..450ff2c5f --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/NetworkTimeline.js @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2014 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. + */ + +WebInspector.NetworkTimeline = class NetworkTimeline extends WebInspector.Timeline +{ + // Public + + recordForResource(resource) + { + console.assert(resource instanceof WebInspector.Resource); + + return this._resourceRecordMap.get(resource) || null; + } + + reset(suppressEvents) + { + this._resourceRecordMap = new Map; + + super.reset(suppressEvents); + } + + addRecord(record) + { + console.assert(record instanceof WebInspector.ResourceTimelineRecord); + + // Don't allow duplicate records for a resource. + if (this._resourceRecordMap.has(record.resource)) + return; + + this._resourceRecordMap.set(record.resource, record); + + super.addRecord(record); + } +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/ObjectPreview.js b/Source/WebInspectorUI/UserInterface/Models/ObjectPreview.js new file mode 100644 index 000000000..f8d0d8328 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/ObjectPreview.js @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2015 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. + */ + +WebInspector.ObjectPreview = class ObjectPreview extends WebInspector.Object +{ + constructor(type, subtype, description, lossless, overflow, properties, entries, size) + { + super(); + + console.assert(type); + console.assert(typeof lossless === "boolean"); + console.assert(!properties || !properties.length || properties[0] instanceof WebInspector.PropertyPreview); + console.assert(!entries || !entries.length || entries[0] instanceof WebInspector.CollectionEntryPreview); + + this._type = type; + this._subtype = subtype; + this._description = description || ""; + this._lossless = lossless; + this._overflow = overflow || false; + this._size = size; + + this._properties = properties || null; + this._entries = entries || null; + } + + // Static + + // Runtime.ObjectPreview. + static fromPayload(payload) + { + if (payload.properties) + payload.properties = payload.properties.map(WebInspector.PropertyPreview.fromPayload); + if (payload.entries) + payload.entries = payload.entries.map(WebInspector.CollectionEntryPreview.fromPayload); + + if (payload.subtype === "array") { + // COMPATIBILITY (iOS 8): Runtime.ObjectPreview did not have size property, + // instead it was tacked onto the end of the description, like "Array[#]". + var match = payload.description.match(/\[(\d+)\]$/); + if (match) { + payload.size = parseInt(match[1]); + payload.description = payload.description.replace(/\[\d+\]$/, ""); + } + } + + return new WebInspector.ObjectPreview(payload.type, payload.subtype, payload.description, payload.lossless, payload.overflow, payload.properties, payload.entries, payload.size); + } + + // Public + + get type() { return this._type; } + get subtype() { return this._subtype; } + get description() { return this._description; } + get lossless() { return this._lossless; } + get overflow() { return this._overflow; } + get propertyPreviews() { return this._properties; } + get collectionEntryPreviews() { return this._entries; } + get size() { return this._size; } + + hasSize() + { + return this._size !== undefined && (this._subtype === "array" || this._subtype === "set" || this._subtype === "map" || this._subtype === "weakmap" || this._subtype === "weakset"); + } +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/Probe.js b/Source/WebInspectorUI/UserInterface/Models/Probe.js new file mode 100644 index 000000000..a6ee37af5 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/Probe.js @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2013 University of Washington. All rights reserved. + * Copyright (C) 2014 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 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 + * HOLDER 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.ProbeSample = class ProbeSample extends WebInspector.Object +{ + constructor(sampleId, batchId, elapsedTime, object) + { + super(); + + console.assert(object instanceof WebInspector.RemoteObject); + + this.sampleId = sampleId; + this.batchId = batchId; + this.timestamp = elapsedTime; + this.object = object; + } +}; + +WebInspector.Probe = class Probe extends WebInspector.Object +{ + constructor(id, breakpoint, expression) + { + super(); + + console.assert(id); + console.assert(breakpoint instanceof WebInspector.Breakpoint); + + this._id = id; + this._breakpoint = breakpoint; + this._expression = expression; + this._samples = []; + } + + // Public + + get id() + { + return this._id; + } + + get breakpoint() + { + return this._breakpoint; + } + + get expression() + { + return this._expression; + } + + set expression(value) + { + if (this._expression === value) + return; + + var data = {oldValue: this._expression, newValue: value}; + this._expression = value; + this.clearSamples(); + this.dispatchEventToListeners(WebInspector.Probe.Event.ExpressionChanged, data); + } + + get samples() + { + return this._samples.slice(); + } + + clearSamples() + { + this._samples = []; + this.dispatchEventToListeners(WebInspector.Probe.Event.SamplesCleared); + } + + addSample(sample) + { + console.assert(sample instanceof WebInspector.ProbeSample, "Wrong object type passed as probe sample: ", sample); + this._samples.push(sample); + this.dispatchEventToListeners(WebInspector.Probe.Event.SampleAdded, sample); + } +}; + +WebInspector.Probe.Event = { + ExpressionChanged: "probe-object-expression-changed", + SampleAdded: "probe-object-sample-added", + SamplesCleared: "probe-object-samples-cleared" +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/ProbeSet.js b/Source/WebInspectorUI/UserInterface/Models/ProbeSet.js new file mode 100644 index 000000000..bf7f4be46 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/ProbeSet.js @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2013 University of Washington. All rights reserved. + * Copyright (C) 2014 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 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 + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// A ProbeSet clusters Probes from the same Breakpoint and their samples. + +WebInspector.ProbeSet = class ProbeSet extends WebInspector.Object +{ + constructor(breakpoint) + { + super(); + + console.assert(breakpoint instanceof WebInspector.Breakpoint, "Unknown breakpoint argument: ", breakpoint); + + this._breakpoint = breakpoint; + this._probes = []; + this._probesByIdentifier = new Map; + + this._createDataTable(); + + WebInspector.Frame.addEventListener(WebInspector.Frame.Event.MainResourceDidChange, this._mainResourceChanged, this); + WebInspector.Probe.addEventListener(WebInspector.Probe.Event.SampleAdded, this._sampleCollected, this); + WebInspector.Breakpoint.addEventListener(WebInspector.Breakpoint.Event.ResolvedStateDidChange, this._breakpointResolvedStateDidChange, this); + } + + // Public + + get breakpoint() { return this._breakpoint; } + get probes() { return this._probes.slice(); } + get dataTable() { return this._dataTable; } + + clear() + { + this._breakpoint.clearActions(WebInspector.BreakpointAction.Type.Probe); + } + + clearSamples() + { + for (var probe of this._probes) + probe.clearSamples(); + + var oldTable = this._dataTable; + this._createDataTable(); + this.dispatchEventToListeners(WebInspector.ProbeSet.Event.SamplesCleared, {oldTable}); + } + + createProbe(expression) + { + this.breakpoint.createAction(WebInspector.BreakpointAction.Type.Probe, null, expression); + } + + addProbe(probe) + { + console.assert(probe instanceof WebInspector.Probe, "Tried to add non-probe ", probe, " to probe group", this); + console.assert(probe.breakpoint === this.breakpoint, "Probe and ProbeSet must have same breakpoint.", probe, this); + + this._probes.push(probe); + this._probesByIdentifier.set(probe.id, probe); + + this.dataTable.addProbe(probe); + this.dispatchEventToListeners(WebInspector.ProbeSet.Event.ProbeAdded, probe); + } + + removeProbe(probe) + { + console.assert(probe instanceof WebInspector.Probe, "Tried to remove non-probe ", probe, " to probe group", this); + console.assert(this._probes.indexOf(probe) !== -1, "Tried to remove probe", probe, " not in group ", this); + console.assert(this._probesByIdentifier.has(probe.id), "Tried to remove probe", probe, " not in group ", this); + + this._probes.splice(this._probes.indexOf(probe), 1); + this._probesByIdentifier.delete(probe.id); + this.dataTable.removeProbe(probe); + this.dispatchEventToListeners(WebInspector.ProbeSet.Event.ProbeRemoved, probe); + } + + willRemove() + { + console.assert(!this._probes.length, "ProbeSet.willRemove called, but probes still associated with group: ", this._probes); + + WebInspector.Frame.removeEventListener(WebInspector.Frame.Event.MainResourceDidChange, this._mainResourceChanged, this); + WebInspector.Probe.removeEventListener(WebInspector.Probe.Event.SampleAdded, this._sampleCollected, this); + WebInspector.Breakpoint.removeEventListener(WebInspector.Breakpoint.Event.ResolvedStateDidChange, this._breakpointResolvedStateDidChange, this); + } + + // Private + + _mainResourceChanged() + { + this.dataTable.mainResourceChanged(); + } + + _createDataTable() + { + if (this.dataTable) + this.dataTable.willRemove(); + + this._dataTable = new WebInspector.ProbeSetDataTable(this); + } + + _sampleCollected(event) + { + var sample = event.data; + console.assert(sample instanceof WebInspector.ProbeSample, "Tried to add non-sample to probe group: ", sample); + + var probe = event.target; + if (!this._probesByIdentifier.has(probe.id)) + return; + + console.assert(this.dataTable); + this.dataTable.addSampleForProbe(probe, sample); + this.dispatchEventToListeners(WebInspector.ProbeSet.Event.SampleAdded, {probe, sample}); + } + + _breakpointResolvedStateDidChange(event) + { + this.dispatchEventToListeners(WebInspector.ProbeSet.Event.ResolvedStateDidChange); + } +}; + +WebInspector.ProbeSet.Event = { + ProbeAdded: "probe-set-probe-added", + ProbeRemoved: "probe-set-probe-removed", + ResolvedStateDidChange: "probe-set-resolved-state-did-change", + SampleAdded: "probe-set-sample-added", + SamplesCleared: "probe-set-samples-cleared", +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/ProbeSetDataFrame.js b/Source/WebInspectorUI/UserInterface/Models/ProbeSetDataFrame.js new file mode 100644 index 000000000..35c13fe6a --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/ProbeSetDataFrame.js @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2013 University of Washington. All rights reserved. + * Copyright (C) 2014 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 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 + * HOLDER 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.ProbeSetDataFrame = class ProbeSetDataFrame extends WebInspector.Object +{ + constructor(index) + { + super(); + + this._count = 0; + this._index = index; + this._separator = false; + } + + // Static + + static compare(a, b) + { + console.assert(a instanceof WebInspector.ProbeSetDataFrame, a); + console.assert(b instanceof WebInspector.ProbeSetDataFrame, b); + + return a.index - b.index; + } + + // Public + + get key() + { + return String(this._index); + } + + get count() + { + return this._count; + } + + get index() + { + return this._index; + } + + get isSeparator() + { + return this._separator; + } + + // The last data frame before a main frame navigation is marked as a "separator" frame. + set isSeparator(value) + { + this._separator = !!value; + } + + addSampleForProbe(probe, sample) + { + this[probe.id] = sample; + this._count++; + } + + missingKeys(probeSet) + { + return probeSet.probes.filter(function(probe) { + return !this.hasOwnProperty(probe.id); + }, this); + } + + isComplete(probeSet) + { + return !this.missingKeys(probeSet).length; + } + + fillMissingValues(probeSet) + { + for (var key of this.missingKeys(probeSet)) + this[key] = WebInspector.ProbeSetDataFrame.MissingValue; + } +}; + +WebInspector.ProbeSetDataFrame.MissingValue = "?"; diff --git a/Source/WebInspectorUI/UserInterface/Models/ProbeSetDataTable.js b/Source/WebInspectorUI/UserInterface/Models/ProbeSetDataTable.js new file mode 100644 index 000000000..4dcee25f8 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/ProbeSetDataTable.js @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2013 University of Washington. All rights reserved. + * Copyright (C) 2014 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 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 + * HOLDER 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.ProbeSetDataTable = class ProbeSetDataTable extends WebInspector.Object +{ + constructor(probeSet) + { + super(); + + this._probeSet = probeSet; + this._frames = []; + this._previousBatchIdentifier = WebInspector.ProbeSetDataTable.SentinelValue; + } + + // Public + + get frames() + { + return this._frames.slice(); + } + + get separators() + { + return this._frames.filter(function(frame) { return frame.isSeparator; }); + } + + willRemove() + { + this.dispatchEventToListeners(WebInspector.ProbeSetDataTable.Event.WillRemove); + this._frames = []; + delete this._probeSet; + } + + mainResourceChanged() + { + this.addSeparator(); + } + + addSampleForProbe(probe, sample) + { + // Eagerly save the frame if the batch identifier differs, or we know the frame is full. + // Create a new frame when the batch identifier differs. + if (sample.batchId !== this._previousBatchIdentifier) { + if (this._openFrame) { + this._openFrame.fillMissingValues(this._probeSet); + this.addFrame(this._openFrame); + } + this._openFrame = this.createFrame(); + this._previousBatchIdentifier = sample.batchId; + } + + console.assert(this._openFrame, "Should always have an open frame before adding sample.", this, probe, sample); + this._openFrame.addSampleForProbe(probe, sample); + if (this._openFrame.count === this._probeSet.probes.length) { + this.addFrame(this._openFrame); + this._openFrame = null; + } + } + + addProbe(probe) + { + for (var frame of this.frames) + if (!frame[probe.id]) + frame[probe.id] = WebInspector.ProbeSetDataTable.UnknownValue; + } + + removeProbe(probe) + { + for (var frame of this.frames) + delete frame[probe.id]; + } + + // Protected - can be overridden by subclasses. + + createFrame() + { + return new WebInspector.ProbeSetDataFrame(this._frames.length); + } + + addFrame(frame) + { + this._frames.push(frame); + this.dispatchEventToListeners(WebInspector.ProbeSetDataTable.Event.FrameInserted, frame); + } + + addSeparator() + { + // Separators must be associated with a frame. + if (!this._frames.length) + return; + + var previousFrame = this._frames.lastValue; + // Don't send out duplicate events for adjacent separators. + if (previousFrame.isSeparator) + return; + + previousFrame.isSeparator = true; + this.dispatchEventToListeners(WebInspector.ProbeSetDataTable.Event.SeparatorInserted, previousFrame); + } +}; + +WebInspector.ProbeSetDataTable.Event = { + FrameInserted: "probe-set-data-table-frame-inserted", + SeparatorInserted: "probe-set-data-table-separator-inserted", + WillRemove: "probe-set-data-table-will-remove" +}; + +WebInspector.ProbeSetDataTable.SentinelValue = -1; +WebInspector.ProbeSetDataTable.UnknownValue = "?"; diff --git a/Source/WebInspectorUI/UserInterface/Models/Profile.js b/Source/WebInspectorUI/UserInterface/Models/Profile.js new file mode 100644 index 000000000..b927c9903 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/Profile.js @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2014 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. + */ + +WebInspector.Profile = class Profile extends WebInspector.Object +{ + constructor(topDownRootNodes) + { + super(); + + topDownRootNodes = topDownRootNodes || []; + + console.assert(topDownRootNodes instanceof Array); + console.assert(topDownRootNodes.reduce(function(previousValue, node) { return previousValue && node instanceof WebInspector.ProfileNode; }, true)); + + this._topDownRootNodes = topDownRootNodes; + } + + // Public + + get topDownRootNodes() + { + return this._topDownRootNodes; + } + + get bottomUpRootNodes() + { + // FIXME: Implement. + return []; + } +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/ProfileNode.js b/Source/WebInspectorUI/UserInterface/Models/ProfileNode.js new file mode 100644 index 000000000..e6e952245 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/ProfileNode.js @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2014 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. + */ + +WebInspector.ProfileNode = class ProfileNode extends WebInspector.Object +{ + constructor(id, type, functionName, sourceCodeLocation, callInfo, calls, childNodes) + { + super(); + + childNodes = childNodes || []; + + console.assert(id); + console.assert(!calls || calls instanceof Array); + console.assert(!calls || calls.length >= 1); + console.assert(!calls || calls.every((call) => call instanceof WebInspector.ProfileNodeCall)); + console.assert(childNodes instanceof Array); + console.assert(childNodes.every((node) => node instanceof WebInspector.ProfileNode)); + + this._id = id; + this._type = type || WebInspector.ProfileNode.Type.Function; + this._functionName = functionName || null; + this._sourceCodeLocation = sourceCodeLocation || null; + this._calls = calls || null; + this._callInfo = callInfo || null; + this._childNodes = childNodes; + this._parentNode = null; + this._previousSibling = null; + this._nextSibling = null; + this._computedTotalTimes = false; + + if (this._callInfo) { + this._startTime = this._callInfo.startTime; + this._endTime = this._callInfo.endTime; + this._totalTime = this._callInfo.totalTime; + this._callCount = this._callInfo.callCount; + } + + for (var i = 0; i < this._childNodes.length; ++i) + this._childNodes[i].establishRelationships(this, this._childNodes[i - 1], this._childNodes[i + 1]); + + if (this._calls) { + for (var i = 0; i < this._calls.length; ++i) + this._calls[i].establishRelationships(this, this._calls[i - 1], this._calls[i + 1]); + } + } + + // Public + + get id() + { + return this._id; + } + + get type() + { + return this._type; + } + + get functionName() + { + return this._functionName; + } + + get sourceCodeLocation() + { + return this._sourceCodeLocation; + } + + get startTime() + { + if (this._startTime === undefined) + this._startTime = Math.max(0, this._calls[0].startTime); + return this._startTime; + } + + get endTime() + { + if (this._endTime === undefined) + this._endTime = Math.min(this._calls.lastValue.endTime, Infinity); + return this._endTime; + } + + get selfTime() + { + this._computeTotalTimesIfNeeded(); + return this._selfTime; + } + + get totalTime() + { + this._computeTotalTimesIfNeeded(); + return this._totalTime; + } + + get callInfo() + { + return this._callInfo; + } + + get calls() + { + return this._calls; + } + + get previousSibling() + { + return this._previousSibling; + } + + get nextSibling() + { + return this._nextSibling; + } + + get parentNode() + { + return this._parentNode; + } + + get childNodes() + { + return this._childNodes; + } + + computeCallInfoForTimeRange(rangeStartTime, rangeEndTime) + { + console.assert(typeof rangeStartTime === "number"); + console.assert(typeof rangeEndTime === "number"); + + // With aggregate call info we can't accurately partition self/total/average time + // in partial ranges because we don't know exactly when each call started. So we + // always return the entire range. + if (this._callInfo) { + if (this._selfTime === undefined) { + var childNodesTotalTime = 0; + for (var childNode of this._childNodes) + childNodesTotalTime += childNode.totalTime; + this._selfTime = this._totalTime - childNodesTotalTime; + // selfTime can negative or very close to zero due to floating point error. + // Since we show at most four decimal places, treat anything less as zero. + if (this._selfTime < 0.0001) + this._selfTime = 0.0; + } + + return { + callCount: this._callCount, + startTime: this._startTime, + endTime: this._endTime, + selfTime: this._selfTime, + totalTime: this._totalTime, + averageTime: (this._selfTime / this._callCount), + }; + } + + // COMPATIBILITY (iOS 8): Profiles included per-call information and can be finely partitioned. + // Compute that below by iterating over all the calls / children for the time range. + + var recordCallCount = true; + var callCount = 0; + + function totalTimeInRange(previousValue, call) + { + if (rangeStartTime > call.endTime || rangeEndTime < call.startTime) + return previousValue; + + if (recordCallCount) + ++callCount; + + return previousValue + Math.min(call.endTime, rangeEndTime) - Math.max(rangeStartTime, call.startTime); + } + + var startTime = Math.max(rangeStartTime, this._calls[0].startTime); + var endTime = Math.min(this._calls.lastValue.endTime, rangeEndTime); + var totalTime = this._calls.reduce(totalTimeInRange, 0); + + recordCallCount = false; + + var childNodesTotalTime = 0; + for (var childNode of this._childNodes) + childNodesTotalTime += childNode.calls.reduce(totalTimeInRange, 0); + + var selfTime = totalTime - childNodesTotalTime; + var averageTime = selfTime / callCount; + + return {startTime, endTime, totalTime, selfTime, callCount, averageTime}; + } + + traverseNextProfileNode(stayWithin) + { + var profileNode = this._childNodes[0]; + if (profileNode) + return profileNode; + + if (this === stayWithin) + return null; + + profileNode = this._nextSibling; + if (profileNode) + return profileNode; + + profileNode = this; + while (profileNode && !profileNode.nextSibling && profileNode.parentNode !== stayWithin) + profileNode = profileNode.parentNode; + + if (!profileNode) + return null; + + return profileNode.nextSibling; + } + + saveIdentityToCookie(cookie) + { + cookie[WebInspector.ProfileNode.TypeCookieKey] = this._type || null; + cookie[WebInspector.ProfileNode.FunctionNameCookieKey] = this._functionName || null; + cookie[WebInspector.ProfileNode.SourceCodeURLCookieKey] = this._sourceCodeLocation ? this._sourceCodeLocation.sourceCode.url ? this._sourceCodeLocation.sourceCode.url.hash : null : null; + cookie[WebInspector.ProfileNode.SourceCodeLocationLineCookieKey] = this._sourceCodeLocation ? this._sourceCodeLocation.lineNumber : null; + cookie[WebInspector.ProfileNode.SourceCodeLocationColumnCookieKey] = this._sourceCodeLocation ? this._sourceCodeLocation.columnNumber : null; + } + + // Protected + + establishRelationships(parentNode, previousSibling, nextSibling) + { + this._parentNode = parentNode || null; + this._previousSibling = previousSibling || null; + this._nextSibling = nextSibling || null; + } + + // Private + + _computeTotalTimesIfNeeded() + { + if (this._computedTotalTimes) + return; + + this._computedTotalTimes = true; + + var info = this.computeCallInfoForTimeRange(0, Infinity); + this._startTime = info.startTime; + this._endTime = info.endTime; + this._selfTime = info.selfTime; + this._totalTime = info.totalTime; + } +}; + +WebInspector.ProfileNode.Type = { + Function: "profile-node-type-function", + Program: "profile-node-type-program" +}; + +WebInspector.ProfileNode.TypeIdentifier = "profile-node"; +WebInspector.ProfileNode.TypeCookieKey = "profile-node-type"; +WebInspector.ProfileNode.FunctionNameCookieKey = "profile-node-function-name"; +WebInspector.ProfileNode.SourceCodeURLCookieKey = "profile-node-source-code-url"; +WebInspector.ProfileNode.SourceCodeLocationLineCookieKey = "profile-node-source-code-location-line"; +WebInspector.ProfileNode.SourceCodeLocationColumnCookieKey = "profile-node-source-code-location-column"; diff --git a/Source/WebInspectorUI/UserInterface/Models/ProfileNodeCall.js b/Source/WebInspectorUI/UserInterface/Models/ProfileNodeCall.js new file mode 100644 index 000000000..7f74c6598 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/ProfileNodeCall.js @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2014 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. + */ + +WebInspector.ProfileNodeCall = class ProfileNodeCall extends WebInspector.Object +{ + constructor(startTime, totalTime) + { + super(); + + console.assert(startTime); + + this._startTime = startTime; + this._totalTime = totalTime || 0; + this._parentNode = null; + this._previousSibling = null; + this._nextSibling = null; + } + + // Public + + get startTime() + { + return this._startTime; + } + + get totalTime() + { + return this._totalTime; + } + + get endTime() + { + return this._startTime + this._totalTime; + } + + // Protected + + establishRelationships(parentNode, previousSibling, nextSibling) + { + this._parentNode = parentNode || null; + this._previousSibling = previousSibling || null; + this._nextSibling = nextSibling || null; + } +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/PropertyDescriptor.js b/Source/WebInspectorUI/UserInterface/Models/PropertyDescriptor.js new file mode 100644 index 000000000..28de2d3f2 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/PropertyDescriptor.js @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2015 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. + */ + +WebInspector.PropertyDescriptor = class PropertyDescriptor extends WebInspector.Object +{ + constructor(descriptor, symbol, isOwnProperty, wasThrown, nativeGetter, isInternalProperty) + { + super(); + + console.assert(descriptor); + console.assert(descriptor.name); + console.assert(!descriptor.value || descriptor.value instanceof WebInspector.RemoteObject); + console.assert(!descriptor.get || descriptor.get instanceof WebInspector.RemoteObject); + console.assert(!descriptor.set || descriptor.set instanceof WebInspector.RemoteObject); + console.assert(!symbol || symbol instanceof WebInspector.RemoteObject); + + this._name = descriptor.name; + this._value = descriptor.value; + this._hasValue = "value" in descriptor; + this._get = descriptor.get; + this._set = descriptor.set; + this._symbol = symbol; + + this._writable = descriptor.writable || false; + this._configurable = descriptor.configurable || false; + this._enumerable = descriptor.enumerable || false; + + this._own = isOwnProperty || false; + this._wasThrown = wasThrown || false; + this._nativeGetterValue = nativeGetter || false; + this._internal = isInternalProperty || false; + } + + // Static + + // Runtime.PropertyDescriptor or Runtime.InternalPropertyDescriptor (second argument). + static fromPayload(payload, internal, target) + { + if (payload.value) + payload.value = WebInspector.RemoteObject.fromPayload(payload.value, target); + if (payload.get) + payload.get = WebInspector.RemoteObject.fromPayload(payload.get, target); + if (payload.set) + payload.set = WebInspector.RemoteObject.fromPayload(payload.set, target); + + if (payload.symbol) + payload.symbol = WebInspector.RemoteObject.fromPayload(payload.symbol, target); + + if (internal) { + console.assert(payload.value); + payload.writable = payload.configurable = payload.enumerable = false; + payload.isOwn = true; + } + + return new WebInspector.PropertyDescriptor(payload, payload.symbol, payload.isOwn, payload.wasThrown, payload.nativeGetter, internal); + } + + // Public + + get name() { return this._name; } + get value() { return this._value; } + get get() { return this._get; } + get set() { return this._set; } + get writable() { return this._writable; } + get configurable() { return this._configurable; } + get enumerable() { return this._enumerable; } + get symbol() { return this._symbol; } + get isOwnProperty() { return this._own; } + get wasThrown() { return this._wasThrown; } + get nativeGetter() { return this._nativeGetterValue; } + get isInternalProperty() { return this._internal; } + + hasValue() + { + return this._hasValue; + } + + hasGetter() + { + return this._get && this._get.type === "function"; + } + + hasSetter() + { + return this._set && this._set.type === "function"; + } + + isIndexProperty() + { + return !isNaN(Number(this._name)); + } + + isSymbolProperty() + { + return !!this._symbol; + } +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/PropertyPath.js b/Source/WebInspectorUI/UserInterface/Models/PropertyPath.js new file mode 100644 index 000000000..a2d9c72fe --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/PropertyPath.js @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2015 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. + */ + +WebInspector.PropertyPath = class PropertyPath extends WebInspector.Object +{ + constructor(object, pathComponent, parent, isPrototype) + { + super(); + + console.assert(object instanceof WebInspector.RemoteObject || object === null); + console.assert(!pathComponent || typeof pathComponent === "string"); + console.assert(!parent || parent instanceof WebInspector.PropertyPath); + console.assert(!parent || pathComponent.length); + + // We allow property pathes with null objects as end-caps only. + // Disallow appending to a PropertyPath with null objects. + if (parent && !parent.object) + throw new Error("Attempted to append to a PropertyPath with null object."); + + this._object = object; + this._pathComponent = typeof pathComponent === "string" ? pathComponent : null; + this._parent = parent || null; + this._isPrototype = isPrototype || false; + } + + // Static + + static emptyPropertyPathForScope(object) + { + return new WebInspector.PropertyPath(object, WebInspector.PropertyPath.SpecialPathComponent.EmptyPathComponentForScope); + } + + // Public + + get object() { return this._object; } + get parent() { return this._parent; } + get isPrototype() { return this._isPrototype; } + get pathComponent() { return this._pathComponent; } + + get rootObject() + { + return this._parent ? this._parent.rootObject : this._object; + } + + get lastNonPrototypeObject() + { + if (!this._parent) + return this._object; + + var p = this._parent; + while (p) { + if (!p.isPrototype) + break; + if (!p.parent) + break; + p = p.parent; + } + + return p.object; + } + + get fullPath() + { + var components = []; + for (var p = this; p && p.pathComponent; p = p.parent) + components.push(p.pathComponent); + components.reverse(); + return components.join(""); + } + + get reducedPath() + { + // The display path for a value should not include __proto__. + // The path for "foo.__proto__.bar.__proto__.x" is better shown as "foo.bar.x". + // FIXME: We should keep __proto__ if this property was overridden. + var components = []; + + var p = this; + + // Include trailing __proto__s. + for (; p && p.isPrototype; p = p.parent) + components.push(p.pathComponent); + + // Skip other __proto__s. + for (; p && p.pathComponent; p = p.parent) { + if (p.isPrototype) + continue; + components.push(p.pathComponent); + } + + components.reverse(); + return components.join(""); + } + + displayPath(type) + { + return type === WebInspector.PropertyPath.Type.Value ? this.reducedPath : this.fullPath; + } + + isRoot() + { + return !this._parent; + } + + isScope() + { + return this._pathComponent === WebInspector.PropertyPath.SpecialPathComponent.EmptyPathComponentForScope; + } + + isPathComponentImpossible() + { + return this._pathComponent && this._pathComponent.startsWith("@"); + } + + isFullPathImpossible() + { + if (this.isPathComponentImpossible()) + return true; + + if (this._parent) + return this._parent.isFullPathImpossible(); + + return false; + } + + appendPropertyName(object, propertyName) + { + var isPrototype = propertyName === "__proto__"; + + if (this.isScope()) + return new WebInspector.PropertyPath(object, propertyName, this, isPrototype); + + var component = this._canPropertyNameBeDotAccess(propertyName) ? "." + propertyName : "[" + doubleQuotedString(propertyName) + "]"; + return new WebInspector.PropertyPath(object, component, this, isPrototype); + } + + appendPropertySymbol(object, symbolName) + { + var component = WebInspector.PropertyPath.SpecialPathComponent.SymbolPropertyName + (symbolName.length ? "(" + symbolName + ")" : ""); + return new WebInspector.PropertyPath(object, component, this); + } + + appendInternalPropertyName(object, propertyName) + { + var component = WebInspector.PropertyPath.SpecialPathComponent.InternalPropertyName + "[" + propertyName + "]"; + return new WebInspector.PropertyPath(object, component, this); + } + + appendGetterPropertyName(object, propertyName) + { + var component = ".__lookupGetter__(" + doubleQuotedString(propertyName) + ")"; + return new WebInspector.PropertyPath(object, component, this); + } + + appendSetterPropertyName(object, propertyName) + { + var component = ".__lookupSetter__(" + doubleQuotedString(propertyName) + ")"; + return new WebInspector.PropertyPath(object, component, this); + } + + appendArrayIndex(object, indexString) + { + var component = "[" + indexString + "]"; + return new WebInspector.PropertyPath(object, component, this); + } + + appendMapKey(object) + { + var component = WebInspector.PropertyPath.SpecialPathComponent.MapKey; + return new WebInspector.PropertyPath(object, component, this); + } + + appendMapValue(object, keyObject) + { + console.assert(!keyObject || keyObject instanceof WebInspector.RemoteObject); + + if (keyObject && keyObject.hasValue()) { + if (keyObject.type === "string") { + var component = ".get(" + doubleQuotedString(keyObject.description) + ")"; + return new WebInspector.PropertyPath(object, component, this); + } + + var component = ".get(" + keyObject.description + ")"; + return new WebInspector.PropertyPath(object, component, this); + } + + var component = WebInspector.PropertyPath.SpecialPathComponent.MapValue; + return new WebInspector.PropertyPath(object, component, this); + } + + appendSetIndex(object) + { + var component = WebInspector.PropertyPath.SpecialPathComponent.SetIndex; + return new WebInspector.PropertyPath(object, component, this); + } + + appendSymbolProperty(object) + { + var component = WebInspector.PropertyPath.SpecialPathComponent.SymbolPropertyName; + return new WebInspector.PropertyPath(object, component, this); + } + + appendPropertyDescriptor(object, descriptor, type) + { + console.assert(descriptor instanceof WebInspector.PropertyDescriptor); + + if (descriptor.isInternalProperty) + return this.appendInternalPropertyName(object, descriptor.name); + if (descriptor.symbol) + return this.appendSymbolProperty(object); + + if (type === WebInspector.PropertyPath.Type.Getter) + return this.appendGetterPropertyName(object, descriptor.name); + if (type === WebInspector.PropertyPath.Type.Setter) + return this.appendSetterPropertyName(object, descriptor.name); + + console.assert(type === WebInspector.PropertyPath.Type.Value); + + if (this._object.subtype === "array" && !isNaN(parseInt(descriptor.name))) + return this.appendArrayIndex(object, descriptor.name); + + return this.appendPropertyName(object, descriptor.name); + } + + // Private + + _canPropertyNameBeDotAccess(propertyName) + { + return /^(?![0-9])\w+$/.test(propertyName); + } +}; + +WebInspector.PropertyPath.SpecialPathComponent = { + InternalPropertyName: "@internal", + SymbolPropertyName: "@symbol", + MapKey: "@mapkey", + MapValue: "@mapvalue", + SetIndex: "@setindex", + EmptyPathComponentForScope: "", +}; + +WebInspector.PropertyPath.Type = { + Value: "value", + Getter: "getter", + Setter: "setter", +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/PropertyPreview.js b/Source/WebInspectorUI/UserInterface/Models/PropertyPreview.js new file mode 100644 index 000000000..8ff4dfcb6 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/PropertyPreview.js @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2015 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. + */ + +WebInspector.PropertyPreview = class PropertyPreview extends WebInspector.Object +{ + constructor(name, type, subtype, value, valuePreview, isInternalProperty) + { + super(); + + console.assert(typeof name === "string"); + console.assert(type); + console.assert(!value || typeof value === "string"); + console.assert(!valuePreview || valuePreview instanceof WebInspector.ObjectPreview); + + this._name = name; + this._type = type; + this._subtype = subtype; + this._value = value; + this._valuePreview = valuePreview; + this._internal = isInternalProperty; + } + + // Static + + // Runtime.PropertyPreview. + static fromPayload(payload) + { + if (payload.valuePreview) + payload.valuePreview = WebInspector.ObjectPreview.fromPayload(payload.valuePreview); + + return new WebInspector.PropertyPreview(payload.name, payload.type, payload.subtype, payload.value, payload.valuePreview, payload.internal); + } + + // Public + + get name() { return this._name; } + get type() { return this._type; } + get subtype() { return this._subtype; } + get value() { return this._value; } + get valuePreview() { return this._valuePreview; } + get internal() { return this._internal; } +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/RenderingFrameTimelineRecord.js b/Source/WebInspectorUI/UserInterface/Models/RenderingFrameTimelineRecord.js new file mode 100644 index 000000000..d6b502fe0 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/RenderingFrameTimelineRecord.js @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2015 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. + */ + +WebInspector.RenderingFrameTimelineRecord = class RenderingFrameTimelineRecord extends WebInspector.TimelineRecord +{ + constructor(startTime, endTime) + { + super(WebInspector.TimelineRecord.Type.RenderingFrame, startTime, endTime); + + this._durationByTaskType = new Map; + this._frameIndex = -1; + } + + // Static + + static resetFrameIndex() + { + WebInspector.RenderingFrameTimelineRecord._nextFrameIndex = 0; + } + + static displayNameForTaskType(taskType) + { + switch (taskType) { + case WebInspector.RenderingFrameTimelineRecord.TaskType.Script: + return WebInspector.UIString("Script"); + case WebInspector.RenderingFrameTimelineRecord.TaskType.Layout: + return WebInspector.UIString("Layout"); + case WebInspector.RenderingFrameTimelineRecord.TaskType.Paint: + return WebInspector.UIString("Paint"); + case WebInspector.RenderingFrameTimelineRecord.TaskType.Other: + return WebInspector.UIString("Other"); + } + } + + static taskTypeForTimelineRecord(record) + { + switch (record.type) { + case WebInspector.TimelineRecord.Type.Script: + return WebInspector.RenderingFrameTimelineRecord.TaskType.Script; + case WebInspector.TimelineRecord.Type.Layout: + if (record.eventType === WebInspector.LayoutTimelineRecord.EventType.Paint || record.eventType === WebInspector.LayoutTimelineRecord.EventType.Composite) + return WebInspector.RenderingFrameTimelineRecord.TaskType.Paint; + return WebInspector.RenderingFrameTimelineRecord.TaskType.Layout; + default: + console.error("Unsupported timeline record type: " + record.type); + return null; + } + } + + // Public + + get frameIndex() + { + return this._frameIndex; + } + + get frameNumber() + { + return this._frameIndex + 1; + } + + setupFrameIndex() + { + console.assert(this._frameIndex === -1, "Frame index should only be set once."); + if (this._frameIndex >= 0) + return; + this._frameIndex = WebInspector.RenderingFrameTimelineRecord._nextFrameIndex++; + } + + durationForTask(taskType) + { + if (this._durationByTaskType.has(taskType)) + return this._durationByTaskType.get(taskType); + + var duration; + if (taskType === WebInspector.RenderingFrameTimelineRecord.TaskType.Other) + duration = this._calculateDurationRemainder(); + else { + duration = this.children.reduce(function(previousValue, currentValue) { + if (taskType !== WebInspector.RenderingFrameTimelineRecord.taskTypeForTimelineRecord(currentValue)) + return previousValue; + + var currentDuration = currentValue.duration; + if (currentValue.usesActiveStartTime) + currentDuration -= currentValue.inactiveDuration; + return previousValue + currentDuration; + }, 0); + + if (taskType === WebInspector.RenderingFrameTimelineRecord.TaskType.Script) { + // Layout events synchronously triggered from JavaScript must be subtracted from the total + // script time, to prevent the time from being counted twice. + duration -= this.children.reduce(function(previousValue, currentValue) { + if (currentValue.type === WebInspector.TimelineRecord.Type.Layout && (currentValue.sourceCodeLocation || currentValue.callFrames)) + return previousValue + currentValue.duration; + return previousValue; + }, 0); + } + } + + this._durationByTaskType.set(taskType, duration); + return duration; + } + + // Private + + _calculateDurationRemainder() + { + return Object.keys(WebInspector.RenderingFrameTimelineRecord.TaskType).reduce((previousValue, key) => { + let taskType = WebInspector.RenderingFrameTimelineRecord.TaskType[key]; + if (taskType === WebInspector.RenderingFrameTimelineRecord.TaskType.Other) + return previousValue; + return previousValue - this.durationForTask(taskType); + }, this.duration); + } +}; + +WebInspector.RenderingFrameTimelineRecord.TaskType = { + Script: "rendering-frame-timeline-record-script", + Layout: "rendering-frame-timeline-record-layout", + Paint: "rendering-frame-timeline-record-paint", + Other: "rendering-frame-timeline-record-other" +}; + +WebInspector.RenderingFrameTimelineRecord.TypeIdentifier = "rendering-frame-timeline-record"; + +WebInspector.RenderingFrameTimelineRecord._nextFrameIndex = 0; diff --git a/Source/WebInspectorUI/UserInterface/Models/ReplayDashboard.js b/Source/WebInspectorUI/UserInterface/Models/ReplayDashboard.js new file mode 100644 index 000000000..64629df32 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/ReplayDashboard.js @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2014 University of Washington. 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. + */ + +WebInspector.ReplayDashboard = class ReplayDashboard extends WebInspector.Object +{ +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/ReplaySession.js b/Source/WebInspectorUI/UserInterface/Models/ReplaySession.js new file mode 100644 index 000000000..e1966cd88 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/ReplaySession.js @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2013 University of Washington. All rights reserved. + * Copyright (C) 2014 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. + */ + + +WebInspector.ReplaySession = class ReplaySession extends WebInspector.Object +{ + constructor(identifier) + { + super(); + + this.identifier = identifier; + this._segments = []; + this._timestamp = null; + } + + // Static + + static fromPayload(identifier, payload) + { + var session = new WebInspector.ReplaySession(identifier); + session._updateFromPayload(payload); + return session; + } + + // Public + + get segments() + { + return this._segments.slice(); + } + + segmentsChanged() + { + // The replay manager won't update the session's list of segments nor create a new session. + ReplayAgent.getSessionData(this.identifier) + .then(this._updateFromPayload.bind(this)); + } + + // Private + + _updateFromPayload(payload) + { + var session = payload.session; + console.assert(session.id === this.identifier); + + var segmentIds = session.segments; + var oldSegments = this._segments; + var pendingSegments = []; + for (var segmentId of segmentIds) + pendingSegments.push(WebInspector.replayManager.getSegment(segmentId)); + + var session = this; + Promise.all(pendingSegments).then( + function(segmentsArray) { + session._segments = segmentsArray; + session.dispatchEventToListeners(WebInspector.ReplaySession.Event.SegmentsChanged, {oldSegments}); + }, + function(error) { + console.error("Problem resolving segments: ", error); + } + ); + } +}; + +WebInspector.ReplaySession.Event = { + SegmentsChanged: "replay-session-segments-changed", +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/ReplaySessionSegment.js b/Source/WebInspectorUI/UserInterface/Models/ReplaySessionSegment.js new file mode 100644 index 000000000..569020eda --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/ReplaySessionSegment.js @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2013 University of Washington. All rights reserved. + * Copyright (C) 2014 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. + */ + + +WebInspector.IncompleteSessionSegment = class IncompleteSessionSegment extends WebInspector.Object +{ + constructor(identifier) + { + super(); + + this.identifier = identifier; + this._timestamp = Date.now(); + } + + // Public + + get isComplete() + { + return false; + } +}; + +WebInspector.ReplaySessionSegment = class ReplaySessionSegment extends WebInspector.Object +{ + constructor(identifier, payload) + { + super(); + + var segment = payload.segment; + console.assert(identifier === segment.id); + + this.identifier = identifier; + this._timestamp = segment.timestamp; + + this._queues = segment.queues; + } + + // Public + + get isComplete() + { + return true; + } +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/Resource.js b/Source/WebInspectorUI/UserInterface/Models/Resource.js new file mode 100644 index 000000000..29e5a7d6a --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/Resource.js @@ -0,0 +1,804 @@ +/* + * Copyright (C) 2013 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. 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. + */ + +WebInspector.Resource = class Resource extends WebInspector.SourceCode +{ + constructor(url, mimeType, type, loaderIdentifier, targetId, requestIdentifier, requestMethod, requestHeaders, requestData, requestSentTimestamp, initiatorSourceCodeLocation, originalRequestWillBeSentTimestamp) + { + super(); + + console.assert(url); + + if (type in WebInspector.Resource.Type) + type = WebInspector.Resource.Type[type]; + + this._url = url; + this._urlComponents = null; + this._mimeType = mimeType; + this._mimeTypeComponents = null; + this._type = type || WebInspector.Resource.typeFromMIMEType(mimeType); + this._loaderIdentifier = loaderIdentifier || null; + this._requestIdentifier = requestIdentifier || null; + this._requestMethod = requestMethod || null; + this._requestData = requestData || null; + this._requestHeaders = requestHeaders || {}; + this._responseHeaders = {}; + this._parentFrame = null; + this._initiatorSourceCodeLocation = initiatorSourceCodeLocation || null; + this._initiatedResources = []; + this._originalRequestWillBeSentTimestamp = originalRequestWillBeSentTimestamp || null; + this._requestSentTimestamp = requestSentTimestamp || NaN; + this._responseReceivedTimestamp = NaN; + this._lastRedirectReceivedTimestamp = NaN; + this._lastDataReceivedTimestamp = NaN; + this._finishedOrFailedTimestamp = NaN; + this._finishThenRequestContentPromise = null; + this._size = NaN; + this._transferSize = NaN; + this._cached = false; + this._timingData = new WebInspector.ResourceTimingData(this); + this._target = targetId ? WebInspector.targetManager.targetForIdentifier(targetId) : WebInspector.mainTarget; + + if (this._initiatorSourceCodeLocation && this._initiatorSourceCodeLocation.sourceCode instanceof WebInspector.Resource) + this._initiatorSourceCodeLocation.sourceCode.addInitiatedResource(this); + } + + // Static + + static typeFromMIMEType(mimeType) + { + if (!mimeType) + return WebInspector.Resource.Type.Other; + + mimeType = parseMIMEType(mimeType).type; + + if (mimeType in WebInspector.Resource._mimeTypeMap) + return WebInspector.Resource._mimeTypeMap[mimeType]; + + if (mimeType.startsWith("image/")) + return WebInspector.Resource.Type.Image; + + if (mimeType.startsWith("font/")) + return WebInspector.Resource.Type.Font; + + return WebInspector.Resource.Type.Other; + } + + static displayNameForType(type, plural) + { + switch (type) { + case WebInspector.Resource.Type.Document: + if (plural) + return WebInspector.UIString("Documents"); + return WebInspector.UIString("Document"); + case WebInspector.Resource.Type.Stylesheet: + if (plural) + return WebInspector.UIString("Stylesheets"); + return WebInspector.UIString("Stylesheet"); + case WebInspector.Resource.Type.Image: + if (plural) + return WebInspector.UIString("Images"); + return WebInspector.UIString("Image"); + case WebInspector.Resource.Type.Font: + if (plural) + return WebInspector.UIString("Fonts"); + return WebInspector.UIString("Font"); + case WebInspector.Resource.Type.Script: + if (plural) + return WebInspector.UIString("Scripts"); + return WebInspector.UIString("Script"); + case WebInspector.Resource.Type.XHR: + if (plural) + return WebInspector.UIString("XHRs"); + return WebInspector.UIString("XHR"); + case WebInspector.Resource.Type.Fetch: + if (plural) + return WebInspector.UIString("Fetches"); + return WebInspector.UIString("Fetch"); + case WebInspector.Resource.Type.WebSocket: + if (plural) + return WebInspector.UIString("Sockets"); + return WebInspector.UIString("Socket"); + case WebInspector.Resource.Type.Other: + return WebInspector.UIString("Other"); + default: + console.error("Unknown resource type: ", type); + return null; + } + } + + // Public + + get target() { return this._target; } + get type() { return this._type; } + get timingData() { return this._timingData; } + + get url() + { + return this._url; + } + + get urlComponents() + { + if (!this._urlComponents) + this._urlComponents = parseURL(this._url); + return this._urlComponents; + } + + get displayName() + { + return WebInspector.displayNameForURL(this._url, this.urlComponents); + } + + get displayURL() + { + const isMultiLine = true; + const dataURIMaxSize = 64; + return WebInspector.truncateURL(this._url, isMultiLine, dataURIMaxSize); + } + + get initiatorSourceCodeLocation() + { + return this._initiatorSourceCodeLocation; + } + + get initiatedResources() + { + return this._initiatedResources; + } + + get originalRequestWillBeSentTimestamp() + { + return this._originalRequestWillBeSentTimestamp; + } + + get mimeType() + { + return this._mimeType; + } + + get mimeTypeComponents() + { + if (!this._mimeTypeComponents) + this._mimeTypeComponents = parseMIMEType(this._mimeType); + return this._mimeTypeComponents; + } + + get syntheticMIMEType() + { + // Resources are often transferred with a MIME-type that doesn't match the purpose the + // resource was loaded for, which is what WebInspector.Resource.Type represents. + // This getter generates a MIME-type, if needed, that matches the resource type. + + // If the type matches the Resource.Type of the MIME-type, then return the actual MIME-type. + if (this._type === WebInspector.Resource.typeFromMIMEType(this._mimeType)) + return this._mimeType; + + // Return the default MIME-types for the Resource.Type, since the current MIME-type + // does not match what is expected for the Resource.Type. + switch (this._type) { + case WebInspector.Resource.Type.Document: + return "text/html"; + case WebInspector.Resource.Type.Stylesheet: + return "text/css"; + case WebInspector.Resource.Type.Script: + return "text/javascript"; + } + + // Return the actual MIME-type since we don't have a better synthesized one to return. + return this._mimeType; + } + + createObjectURL() + { + // If content is not available, fallback to using original URL. + // The client may try to revoke it, but nothing will happen. + if (!this.content) + return this._url; + + var content = this.content; + console.assert(content instanceof Blob, content); + + return URL.createObjectURL(content); + } + + isMainResource() + { + return this._parentFrame ? this._parentFrame.mainResource === this : false; + } + + addInitiatedResource(resource) + { + if (!(resource instanceof WebInspector.Resource)) + return; + + this._initiatedResources.push(resource); + + this.dispatchEventToListeners(WebInspector.Resource.Event.InitiatedResourcesDidChange); + } + + get parentFrame() + { + return this._parentFrame; + } + + get loaderIdentifier() + { + return this._loaderIdentifier; + } + + get requestIdentifier() + { + return this._requestIdentifier; + } + + get finished() + { + return this._finished; + } + + get failed() + { + return this._failed; + } + + get canceled() + { + return this._canceled; + } + + get requestMethod() + { + return this._requestMethod; + } + + get requestData() + { + return this._requestData; + } + + get requestDataContentType() + { + return this._requestHeaders.valueForCaseInsensitiveKey("Content-Type") || null; + } + + get requestHeaders() + { + return this._requestHeaders; + } + + get responseHeaders() + { + return this._responseHeaders; + } + + get requestSentTimestamp() + { + return this._requestSentTimestamp; + } + + get lastRedirectReceivedTimestamp() + { + return this._lastRedirectReceivedTimestamp; + } + + get responseReceivedTimestamp() + { + return this._responseReceivedTimestamp; + } + + get lastDataReceivedTimestamp() + { + return this._lastDataReceivedTimestamp; + } + + get finishedOrFailedTimestamp() + { + return this._finishedOrFailedTimestamp; + } + + get firstTimestamp() + { + return this.timingData.startTime || this.lastRedirectReceivedTimestamp || this.responseReceivedTimestamp || this.lastDataReceivedTimestamp || this.finishedOrFailedTimestamp; + } + + get lastTimestamp() + { + return this.timingData.responseEnd || this.lastDataReceivedTimestamp || this.responseReceivedTimestamp || this.lastRedirectReceivedTimestamp || this.requestSentTimestamp; + } + + get duration() + { + return this.timingData.responseEnd - this.timingData.requestStart; + } + + get latency() + { + return this.timingData.responseStart - this.timingData.requestStart; + } + + get receiveDuration() + { + return this.timingData.responseEnd - this.timingData.responseStart; + } + + get cached() + { + return this._cached; + } + + get statusCode() + { + return this._statusCode; + } + + get statusText() + { + return this._statusText; + } + + get size() + { + return this._size; + } + + get encodedSize() + { + if (!isNaN(this._transferSize)) + 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. + + return Number(this._responseHeaders.valueForCaseInsensitiveKey("Content-Length") || this._size); + } + + get transferSize() + { + if (this.statusCode === 304) // Not modified + return this._responseHeadersSize; + + if (this._cached) + return 0; + + return this._responseHeadersSize + this.encodedSize; + } + + get compressed() + { + var contentEncoding = this._responseHeaders.valueForCaseInsensitiveKey("Content-Encoding"); + return contentEncoding && /\b(?:gzip|deflate)\b/.test(contentEncoding); + } + + get scripts() + { + return this._scripts || []; + } + + scriptForLocation(sourceCodeLocation) + { + console.assert(!(this instanceof WebInspector.SourceMapResource)); + console.assert(sourceCodeLocation.sourceCode === this, "SourceCodeLocation must be in this Resource"); + if (sourceCodeLocation.sourceCode !== this) + return null; + + var lineNumber = sourceCodeLocation.lineNumber; + var columnNumber = sourceCodeLocation.columnNumber; + for (var i = 0; i < this._scripts.length; ++i) { + var script = this._scripts[i]; + if (script.range.startLine <= lineNumber && script.range.endLine >= lineNumber) { + if (script.range.startLine === lineNumber && columnNumber < script.range.startColumn) + continue; + if (script.range.endLine === lineNumber && columnNumber > script.range.endColumn) + continue; + return script; + } + } + + return null; + } + + updateForRedirectResponse(url, requestHeaders, elapsedTime) + { + console.assert(!this._finished); + console.assert(!this._failed); + console.assert(!this._canceled); + + var oldURL = this._url; + + this._url = url; + this._requestHeaders = requestHeaders || {}; + this._lastRedirectReceivedTimestamp = elapsedTime || NaN; + + if (oldURL !== url) { + // Delete the URL components so the URL is re-parsed the next time it is requested. + this._urlComponents = null; + + this.dispatchEventToListeners(WebInspector.Resource.Event.URLDidChange, {oldURL}); + } + + this.dispatchEventToListeners(WebInspector.Resource.Event.RequestHeadersDidChange); + this.dispatchEventToListeners(WebInspector.Resource.Event.TimestampsDidChange); + } + + updateForResponse(url, mimeType, type, responseHeaders, statusCode, statusText, elapsedTime, timingData) + { + console.assert(!this._finished); + console.assert(!this._failed); + console.assert(!this._canceled); + + var oldURL = this._url; + var oldMIMEType = this._mimeType; + var oldType = this._type; + + if (type in WebInspector.Resource.Type) + type = WebInspector.Resource.Type[type]; + + this._url = url; + this._mimeType = mimeType; + this._type = type || WebInspector.Resource.typeFromMIMEType(mimeType); + this._statusCode = statusCode; + this._statusText = statusText; + this._responseHeaders = responseHeaders || {}; + this._responseReceivedTimestamp = elapsedTime || NaN; + this._timingData = WebInspector.ResourceTimingData.fromPayload(timingData, this); + + this._responseHeadersSize = String(this._statusCode).length + this._statusText.length + 12; // Extra length is for "HTTP/1.1 ", " ", and "\r\n". + for (var name in this._responseHeaders) + this._responseHeadersSize += name.length + this._responseHeaders[name].length + 4; // Extra length is for ": ", and "\r\n". + + if (statusCode === 304 && !this._cached) + this.markAsCached(); + + if (oldURL !== url) { + // Delete the URL components so the URL is re-parsed the next time it is requested. + this._urlComponents = null; + + this.dispatchEventToListeners(WebInspector.Resource.Event.URLDidChange, {oldURL}); + } + + if (oldMIMEType !== mimeType) { + // Delete the MIME-type components so the MIME-type is re-parsed the next time it is requested. + this._mimeTypeComponents = null; + + this.dispatchEventToListeners(WebInspector.Resource.Event.MIMETypeDidChange, {oldMIMEType}); + } + + if (oldType !== type) + this.dispatchEventToListeners(WebInspector.Resource.Event.TypeDidChange, {oldType}); + + console.assert(isNaN(this._size)); + console.assert(isNaN(this._transferSize)); + + // The transferSize becomes 0 when status is 304 or Content-Length is available, so + // notify listeners of that change. + if (statusCode === 304 || this._responseHeaders.valueForCaseInsensitiveKey("Content-Length")) + this.dispatchEventToListeners(WebInspector.Resource.Event.TransferSizeDidChange); + + this.dispatchEventToListeners(WebInspector.Resource.Event.ResponseReceived); + this.dispatchEventToListeners(WebInspector.Resource.Event.TimestampsDidChange); + } + + canRequestContent() + { + return this._finished; + } + + requestContentFromBackend() + { + // If we have the requestIdentifier we can get the actual response for this specific resource. + // Otherwise the content will be cached resource data, which might not exist anymore. + if (this._requestIdentifier) + return NetworkAgent.getResponseBody(this._requestIdentifier); + + // There is no request identifier or frame to request content from. + if (this._parentFrame) + return PageAgent.getResourceContent(this._parentFrame.id, this._url); + + return Promise.reject(new Error("Content request failed.")); + } + + increaseSize(dataLength, elapsedTime) + { + console.assert(dataLength >= 0); + + if (isNaN(this._size)) + this._size = 0; + + var previousSize = this._size; + + this._size += dataLength; + + this._lastDataReceivedTimestamp = elapsedTime || NaN; + + this.dispatchEventToListeners(WebInspector.Resource.Event.SizeDidChange, {previousSize}); + + // The transferSize is based off of size when status is not 304 or Content-Length is missing. + if (isNaN(this._transferSize) && this._statusCode !== 304 && !this._responseHeaders.valueForCaseInsensitiveKey("Content-Length")) + this.dispatchEventToListeners(WebInspector.Resource.Event.TransferSizeDidChange); + } + + increaseTransferSize(encodedDataLength) + { + console.assert(encodedDataLength >= 0); + + if (isNaN(this._transferSize)) + this._transferSize = 0; + this._transferSize += encodedDataLength; + + this.dispatchEventToListeners(WebInspector.Resource.Event.TransferSizeDidChange); + } + + markAsCached() + { + this._cached = true; + + this.dispatchEventToListeners(WebInspector.Resource.Event.CacheStatusDidChange); + + // The transferSize is starts returning 0 when cached is true, unless status is 304. + if (this._statusCode !== 304) + this.dispatchEventToListeners(WebInspector.Resource.Event.TransferSizeDidChange); + } + + markAsFinished(elapsedTime) + { + console.assert(!this._failed); + console.assert(!this._canceled); + + this._finished = true; + this._finishedOrFailedTimestamp = elapsedTime || NaN; + this._timingData.markResponseEndTime(elapsedTime || NaN); + + if (this._finishThenRequestContentPromise) + this._finishThenRequestContentPromise = null; + + this.dispatchEventToListeners(WebInspector.Resource.Event.LoadingDidFinish); + this.dispatchEventToListeners(WebInspector.Resource.Event.TimestampsDidChange); + } + + markAsFailed(canceled, elapsedTime) + { + console.assert(!this._finished); + + this._failed = true; + this._canceled = canceled; + this._finishedOrFailedTimestamp = elapsedTime || NaN; + + this.dispatchEventToListeners(WebInspector.Resource.Event.LoadingDidFail); + this.dispatchEventToListeners(WebInspector.Resource.Event.TimestampsDidChange); + } + + revertMarkAsFinished() + { + console.assert(!this._failed); + console.assert(!this._canceled); + console.assert(this._finished); + + this._finished = false; + this._finishedOrFailedTimestamp = NaN; + } + + getImageSize(callback) + { + // Throw an error in the case this resource is not an image. + if (this.type !== WebInspector.Resource.Type.Image) + throw "Resource is not an image."; + + // See if we've already computed and cached the image size, + // in which case we can provide them directly. + if (this._imageSize) { + callback(this._imageSize); + return; + } + + var objectURL = null; + + // Event handler for the image "load" event. + function imageDidLoad() + { + URL.revokeObjectURL(objectURL); + + // Cache the image metrics. + this._imageSize = { + width: image.width, + height: image.height + }; + + callback(this._imageSize); + } + + // Create an <img> element that we'll use to load the image resource + // so that we can query its intrinsic size. + var image = new Image; + image.addEventListener("load", imageDidLoad.bind(this), false); + + // Set the image source using an object URL once we've obtained its data. + this.requestContent().then(function(content) { + objectURL = image.src = content.sourceCode.createObjectURL(); + }); + } + + requestContent() + { + if (this._finished) + return super.requestContent(); + + if (this._failed) + return Promise.resolve({error: WebInspector.UIString("An error occurred trying to load the resource.")}); + + if (!this._finishThenRequestContentPromise) { + this._finishThenRequestContentPromise = new Promise((resolve, reject) => { + this.addEventListener(WebInspector.Resource.Event.LoadingDidFinish, resolve); + this.addEventListener(WebInspector.Resource.Event.LoadingDidFail, reject); + }).then(WebInspector.SourceCode.prototype.requestContent.bind(this)); + } + + return this._finishThenRequestContentPromise; + } + + associateWithScript(script) + { + if (!this._scripts) + this._scripts = []; + + this._scripts.push(script); + + if (this._type === WebInspector.Resource.Type.Other || this._type === WebInspector.Resource.Type.XHR) { + let oldType = this._type; + this._type = WebInspector.Resource.Type.Script; + this.dispatchEventToListeners(WebInspector.Resource.Event.TypeDidChange, {oldType}); + } + } + + saveIdentityToCookie(cookie) + { + cookie[WebInspector.Resource.URLCookieKey] = this.url.hash; + cookie[WebInspector.Resource.MainResourceCookieKey] = this.isMainResource(); + } + + generateCURLCommand() + { + function escapeStringPosix(str) { + function escapeCharacter(x) { + let code = x.charCodeAt(0); + let hex = code.toString(16); + if (code < 256) + return "\\x" + hex.padStart(2, "0"); + return "\\u" + hex.padStart(4, "0"); + } + + if (/[^\x20-\x7E]|'/.test(str)) { + // Use ANSI-C quoting syntax. + return "$'" + str.replace(/\\/g, "\\\\") + .replace(/'/g, "\\'") + .replace(/\n/g, "\\n") + .replace(/\r/g, "\\r") + .replace(/[^\x20-\x7E]/g, escapeCharacter) + "'"; + } else { + // Use single quote syntax. + return `'${str}'`; + } + } + + let command = ["curl " + escapeStringPosix(this.url).replace(/[[{}\]]/g, "\\$&")]; + command.push(`-X${this.requestMethod}`); + + for (let key in this.requestHeaders) + command.push("-H " + escapeStringPosix(`${key}: ${this.requestHeaders[key]}`)); + + if (this.requestDataContentType && this.requestMethod !== "GET" && this.requestData) { + if (this.requestDataContentType.match(/^application\/x-www-form-urlencoded\s*(;.*)?$/i)) + command.push("--data " + escapeStringPosix(this.requestData)); + else + command.push("--data-binary " + escapeStringPosix(this.requestData)); + } + + let curlCommand = command.join(" \\\n"); + InspectorFrontendHost.copyText(curlCommand); + return curlCommand; + } +}; + +WebInspector.Resource.TypeIdentifier = "resource"; +WebInspector.Resource.URLCookieKey = "resource-url"; +WebInspector.Resource.MainResourceCookieKey = "resource-is-main-resource"; + +WebInspector.Resource.Event = { + URLDidChange: "resource-url-did-change", + MIMETypeDidChange: "resource-mime-type-did-change", + TypeDidChange: "resource-type-did-change", + RequestHeadersDidChange: "resource-request-headers-did-change", + ResponseReceived: "resource-response-received", + LoadingDidFinish: "resource-loading-did-finish", + LoadingDidFail: "resource-loading-did-fail", + TimestampsDidChange: "resource-timestamps-did-change", + SizeDidChange: "resource-size-did-change", + TransferSizeDidChange: "resource-transfer-size-did-change", + CacheStatusDidChange: "resource-cached-did-change", + InitiatedResourcesDidChange: "resource-initiated-resources-did-change", +}; + +// Keep these in sync with the "ResourceType" enum defined by the "Page" domain. +WebInspector.Resource.Type = { + Document: "resource-type-document", + Stylesheet: "resource-type-stylesheet", + Image: "resource-type-image", + Font: "resource-type-font", + Script: "resource-type-script", + XHR: "resource-type-xhr", + Fetch: "resource-type-fetch", + WebSocket: "resource-type-websocket", + Other: "resource-type-other" +}; + +// This MIME Type map is private, use WebInspector.Resource.typeFromMIMEType(). +WebInspector.Resource._mimeTypeMap = { + "text/html": WebInspector.Resource.Type.Document, + "text/xml": WebInspector.Resource.Type.Document, + "text/plain": WebInspector.Resource.Type.Document, + "application/xhtml+xml": WebInspector.Resource.Type.Document, + "image/svg+xml": WebInspector.Resource.Type.Document, + + "text/css": WebInspector.Resource.Type.Stylesheet, + "text/xsl": WebInspector.Resource.Type.Stylesheet, + "text/x-less": WebInspector.Resource.Type.Stylesheet, + "text/x-sass": WebInspector.Resource.Type.Stylesheet, + "text/x-scss": WebInspector.Resource.Type.Stylesheet, + + "application/pdf": WebInspector.Resource.Type.Image, + + "application/x-font-type1": WebInspector.Resource.Type.Font, + "application/x-font-ttf": WebInspector.Resource.Type.Font, + "application/x-font-woff": WebInspector.Resource.Type.Font, + "application/x-truetype-font": WebInspector.Resource.Type.Font, + + "text/javascript": WebInspector.Resource.Type.Script, + "text/ecmascript": WebInspector.Resource.Type.Script, + "application/javascript": WebInspector.Resource.Type.Script, + "application/ecmascript": WebInspector.Resource.Type.Script, + "application/x-javascript": WebInspector.Resource.Type.Script, + "application/json": WebInspector.Resource.Type.Script, + "application/x-json": WebInspector.Resource.Type.Script, + "text/x-javascript": WebInspector.Resource.Type.Script, + "text/x-json": WebInspector.Resource.Type.Script, + "text/javascript1.1": WebInspector.Resource.Type.Script, + "text/javascript1.2": WebInspector.Resource.Type.Script, + "text/javascript1.3": WebInspector.Resource.Type.Script, + "text/jscript": WebInspector.Resource.Type.Script, + "text/livescript": WebInspector.Resource.Type.Script, + "text/x-livescript": WebInspector.Resource.Type.Script, + "text/typescript": WebInspector.Resource.Type.Script, + "text/x-clojure": WebInspector.Resource.Type.Script, + "text/x-coffeescript": WebInspector.Resource.Type.Script +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/ResourceCollection.js b/Source/WebInspectorUI/UserInterface/Models/ResourceCollection.js new file mode 100644 index 000000000..bd38ad2f5 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/ResourceCollection.js @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2013 Apple Inc. All rights reserved. + * Copyright (C) 2016 Devin Rousso <dcrousso+webkit@gmail.com>. 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. + */ + +WebInspector.ResourceCollection = class ResourceCollection extends WebInspector.Collection +{ + constructor(resourceType) + { + super(WebInspector.ResourceCollection.verifierForType(resourceType)); + + this._resourceType = resourceType || null; + this._resourceURLMap = new Map; + this._resourcesTypeMap = new Map; + } + + // Static + + static verifierForType(type) { + switch (type) { + case WebInspector.Resource.Type.Document: + return WebInspector.ResourceCollection.TypeVerifier.Document; + case WebInspector.Resource.Type.Stylesheet: + return WebInspector.ResourceCollection.TypeVerifier.Stylesheet; + case WebInspector.Resource.Type.Image: + return WebInspector.ResourceCollection.TypeVerifier.Image; + case WebInspector.Resource.Type.Font: + return WebInspector.ResourceCollection.TypeVerifier.Font; + case WebInspector.Resource.Type.Script: + return WebInspector.ResourceCollection.TypeVerifier.Script; + case WebInspector.Resource.Type.XHR: + return WebInspector.ResourceCollection.TypeVerifier.XHR; + case WebInspector.Resource.Type.Fetch: + return WebInspector.ResourceCollection.TypeVerifier.Fetch; + case WebInspector.Resource.Type.WebSocket: + return WebInspector.ResourceCollection.TypeVerifier.WebSocket; + case WebInspector.Resource.Type.Other: + return WebInspector.ResourceCollection.TypeVerifier.Other; + default: + return WebInspector.Collection.TypeVerifier.Resource; + } + } + + // Public + + resourceForURL(url) + { + return this._resourceURLMap.get(url) || null; + } + + resourceCollectionForType(type) + { + if (this._resourceType) { + console.assert(type === this._resourceType); + return this; + } + + let resourcesCollectionForType = this._resourcesTypeMap.get(type); + if (!resourcesCollectionForType) { + resourcesCollectionForType = new WebInspector.ResourceCollection(type); + this._resourcesTypeMap.set(type, resourcesCollectionForType); + } + + return resourcesCollectionForType; + } + + clear() + { + super.clear(); + + this._resourceURLMap.clear(); + + if (!this._resourceType) + this._resourcesTypeMap.clear(); + } + + // Protected + + itemAdded(item) + { + this._associateWithResource(item); + } + + itemRemoved(item) + { + this._disassociateWithResource(item); + } + + itemsCleared(items) + { + const skipRemoval = true; + + for (let item of items) + this._disassociateWithResource(item, skipRemoval); + } + + // Private + + _associateWithResource(resource) + { + this._resourceURLMap.set(resource.url, resource); + + if (!this._resourceType) { + let resourcesCollectionForType = this.resourceCollectionForType(resource.type); + resourcesCollectionForType.add(resource); + } + + resource.addEventListener(WebInspector.Resource.Event.URLDidChange, this._resourceURLDidChange, this); + resource.addEventListener(WebInspector.Resource.Event.TypeDidChange, this._resourceTypeDidChange, this); + } + + _disassociateWithResource(resource, skipRemoval) + { + resource.removeEventListener(WebInspector.Resource.Event.URLDidChange, this._resourceURLDidChange, this); + resource.removeEventListener(WebInspector.Resource.Event.TypeDidChange, this._resourceTypeDidChange, this); + + if (skipRemoval) + return; + + if (!this._resourceType) { + let resourcesCollectionForType = this.resourceCollectionForType(resource.type); + resourcesCollectionForType.remove(resource); + } + + this._resourceURLMap.delete(resource.url); + } + + _resourceURLDidChange(event) + { + let resource = event.target; + console.assert(resource instanceof WebInspector.Resource); + if (!(resource instanceof WebInspector.Resource)) + return; + + let oldURL = event.data.oldURL; + console.assert(oldURL); + if (!oldURL) + return; + + this._resourceURLMap.set(resource.url, resource); + this._resourceURLMap.delete(oldURL); + } + + _resourceTypeDidChange(event) + { + let resource = event.target; + console.assert(resource instanceof WebInspector.Resource); + if (!(resource instanceof WebInspector.Resource)) + return; + + if (this._resourceType) { + console.assert(resource.type !== this._resourceType); + this.remove(resource); + return; + } + + console.assert(event.data.oldType); + + let resourcesWithNewType = this.resourceCollectionForType(resource.type); + resourcesWithNewType.add(resource); + + // It is not necessary to remove the resource from the sub-collection for the old type since + // this is handled by that sub-collection's own _resourceTypeDidChange handler (via the + // above if statement). + } +}; + +WebInspector.ResourceCollection.TypeVerifier = { + Document: (object) => WebInspector.Collection.TypeVerifier.Resource(object) && object.type === WebInspector.Resource.Type.Document, + Stylesheet: (object) => WebInspector.Collection.TypeVerifier.Resource(object) && object.type === WebInspector.Resource.Type.Stylesheet, + Image: (object) => WebInspector.Collection.TypeVerifier.Resource(object) && object.type === WebInspector.Resource.Type.Image, + Font: (object) => WebInspector.Collection.TypeVerifier.Resource(object) && object.type === WebInspector.Resource.Type.Font, + Script: (object) => WebInspector.Collection.TypeVerifier.Resource(object) && object.type === WebInspector.Resource.Type.Script, + XHR: (object) => WebInspector.Collection.TypeVerifier.Resource(object) && object.type === WebInspector.Resource.Type.XHR, + Fetch: (object) => WebInspector.Collection.TypeVerifier.Resource(object) && object.type === WebInspector.Resource.Type.Fetch, + WebSocket: (object) => WebInspector.Collection.TypeVerifier.Resource(object) && object.type === WebInspector.Resource.Type.WebSocket, + Other: (object) => WebInspector.Collection.TypeVerifier.Resource(object) && object.type === WebInspector.Resource.Type.Other, +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/ResourceQueryMatch.js b/Source/WebInspectorUI/UserInterface/Models/ResourceQueryMatch.js new file mode 100644 index 000000000..0668525bc --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/ResourceQueryMatch.js @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2016 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. + */ + +WebInspector.ResourceQueryMatch = class QueryMatch extends WebInspector.Object +{ + constructor(type, index, queryIndex) + { + super(); + + this._type = type; + this._index = index; + this._queryIndex = queryIndex; + this._rank = undefined; + } + + // Public + + get type() { return this._type; } + get index() { return this._index; } + get queryIndex() { return this._queryIndex; } +}; + +WebInspector.ResourceQueryMatch.Type = { + Normal: Symbol("normal"), + Special: Symbol("special") +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/ResourceQueryResult.js b/Source/WebInspectorUI/UserInterface/Models/ResourceQueryResult.js new file mode 100644 index 000000000..c2bf09d37 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/ResourceQueryResult.js @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2016 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. + */ + +WebInspector.ResourceQueryResult = class QueryResult extends WebInspector.Object +{ + constructor(resource, matches, cookie) + { + console.assert(matches.length, "Query matches list can't be empty."); + + super(); + + this._resource = resource; + this._matches = matches; + this._cookie = cookie || null; + } + + // Public + + get resource() { return this._resource; } + get cookie() { return this._cookie; } + + get rank() + { + if (this._rank === undefined) + this._calculateRank(); + + return this._rank; + } + + get matchingTextRanges() + { + if (!this._matchingTextRanges) + this._matchingTextRanges = this._createMatchingTextRanges(); + + return this._matchingTextRanges; + } + + // Private + + _calculateRank() + { + const normalWeight = 10; + const consecutiveWeight = 5; + const specialMultiplier = 5; + + function getMultiplier(match) { + if (match.type === WebInspector.ResourceQueryMatch.Type.Special) + return specialMultiplier; + + return 1; + } + + this._rank = 0; + + let previousMatch = null; + let consecutiveMatchStart = null; + for (let match of this._matches) { + this._rank += normalWeight * getMultiplier(match); + + let consecutive = previousMatch && previousMatch.index === match.index - 1; + if (consecutive) { + if (!consecutiveMatchStart) + consecutiveMatchStart = previousMatch; + + // If the first match in this consecutive series was a special character, give a + // bonus (more likely to match a specific word in the text). Otherwise, multiply + // by the current length of the consecutive sequence (gives priority to fewer + // longer sequences instead of more short sequences). + this._rank += consecutiveWeight * getMultiplier(consecutiveMatchStart) * (match.index - consecutiveMatchStart.index); + } else if (consecutiveMatchStart) + consecutiveMatchStart = null; + + previousMatch = match; + + // The match index is deducted from the total rank, so matches that occur closer to + // the beginning of the string are ranked higher. Increase the amount subtracted if + // the match is special, so as to favor matches towards the beginning of the string. + if (!consecutive) + this._rank -= match.index * getMultiplier(match); + } + } + + _createMatchingTextRanges() + { + if (!this._matches.length) + return []; + + let ranges = []; + let startIndex = this._matches[0].index; + let endIndex = startIndex; + for (let i = 1; i < this._matches.length; ++i) { + let match = this._matches[i]; + + // Increment endIndex for consecutive match. + if (match.index === endIndex + 1) { + endIndex++; + continue; + } + + // Begin a new range when a gap between this match and the previous match is found. + ranges.push(new WebInspector.TextRange(0, startIndex, 0, endIndex + 1)); + startIndex = match.index; + endIndex = startIndex; + } + + ranges.push(new WebInspector.TextRange(0, startIndex, 0, endIndex + 1)); + return ranges; + } + + // Testing + + __test_createMatchesMask() + { + let filename = this._resource.displayName; + let lastIndex = -1; + let result = ""; + + for (let match of this._matches) { + let gap = " ".repeat(match.index - lastIndex - 1); + result += gap; + result += filename[match.index]; + lastIndex = match.index; + } + + return result; + } +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/ResourceTimelineRecord.js b/Source/WebInspectorUI/UserInterface/Models/ResourceTimelineRecord.js new file mode 100644 index 000000000..03a090971 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/ResourceTimelineRecord.js @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2013 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. + */ + +WebInspector.ResourceTimelineRecord = class ResourceTimelineRecord extends WebInspector.TimelineRecord +{ + constructor(resource) + { + super(WebInspector.TimelineRecord.Type.Network); + + this._resource = resource; + this._resource.addEventListener(WebInspector.Resource.Event.TimestampsDidChange, this._dispatchUpdatedEvent, this); + } + + // Public + + get resource() + { + return this._resource; + } + + get updatesDynamically() + { + return true; + } + + get usesActiveStartTime() + { + return true; + } + + get startTime() + { + return this._resource.timingData.startTime; + } + + get activeStartTime() + { + return this._resource.timingData.responseStart; + } + + get endTime() + { + return this._resource.timingData.responseEnd; + } + + // Private + + _dispatchUpdatedEvent() + { + this.dispatchEventToListeners(WebInspector.TimelineRecord.Event.Updated); + } +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/ResourceTimingData.js b/Source/WebInspectorUI/UserInterface/Models/ResourceTimingData.js new file mode 100644 index 000000000..2affdf73a --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/ResourceTimingData.js @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2016 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. + */ + +WebInspector.ResourceTimingData = class ResourceTimingData extends WebInspector.Object +{ + constructor(resource, data) + { + super(); + + data = data || {}; + + console.assert(isNaN(data.domainLookupStart) === isNaN(data.domainLookupEnd)); + console.assert(isNaN(data.connectStart) === isNaN(data.connectEnd)); + + this._resource = resource; + + this._startTime = data.startTime || NaN; + this._domainLookupStart = data.domainLookupStart || NaN; + this._domainLookupEnd = data.domainLookupEnd || NaN; + this._connectStart = data.connectStart || NaN; + this._connectEnd = data.connectEnd || NaN; + this._secureConnectionStart = data.secureConnectionStart || NaN; + this._requestStart = data.requestStart || NaN; + this._responseStart = data.responseStart || NaN; + this._responseEnd = data.responseEnd || NaN; + + if (this._domainLookupStart >= this._domainLookupEnd) + this._domainLookupStart = this._domainLookupEnd = NaN; + + if (this._connectStart >= this._connectEnd) + this._connectStart = this._connectEnd = NaN; + } + + // Static + + static fromPayload(payload, resource) + { + payload = payload || {}; + + // COMPATIBILITY (iOS 10): Resource Timing data was incomplete and incorrect. Do not use it. + // iOS 7 sent a requestTime and iOS 8-9.3 sent a navigationStart time. + if (typeof payload.requestTime === "number" || typeof payload.navigationStart === "number") + payload = {}; + + function offsetToTimestamp(offset) { + return offset > 0 ? payload.startTime + offset / 1000 : NaN; + } + + let data = { + startTime: payload.startTime, + domainLookupStart: offsetToTimestamp(payload.domainLookupStart), + domainLookupEnd: offsetToTimestamp(payload.domainLookupEnd), + connectStart: offsetToTimestamp(payload.connectStart), + connectEnd: offsetToTimestamp(payload.connectEnd), + secureConnectionStart: offsetToTimestamp(payload.secureConnectionStart), + requestStart: offsetToTimestamp(payload.requestStart), + responseStart: offsetToTimestamp(payload.responseStart), + responseEnd: offsetToTimestamp(payload.responseEnd) + }; + + // COMPATIBILITY (iOS 8): connectStart is zero if a secure connection is used. + if (isNaN(data.connectStart) && !isNaN(data.secureConnectionStart)) + data.connectStart = data.secureConnectionStart; + + return new WebInspector.ResourceTimingData(resource, data); + } + + // Public + + get startTime() { return this._startTime || this._resource.requestSentTimestamp; } + get domainLookupStart() { return this._domainLookupStart; } + get domainLookupEnd() { return this._domainLookupEnd; } + get connectStart() { return this._connectStart; } + get connectEnd() { return this._connectEnd; } + get secureConnectionStart() { return this._secureConnectionStart; } + get requestStart() { return this._requestStart || this._resource.requestSentTimestamp; } + get responseStart() { return this._responseStart || this._resource.responseReceivedTimestamp; } + get responseEnd() { return this._responseEnd || this._resource.finishedOrFailedTimestamp; } + + markResponseEndTime(responseEnd) + { + console.assert(typeof responseEnd === "number"); + this._responseEnd = responseEnd; + } +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/Revision.js b/Source/WebInspectorUI/UserInterface/Models/Revision.js new file mode 100644 index 000000000..83c51a7a3 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/Revision.js @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2013 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. + */ + +WebInspector.Revision = class Revision extends WebInspector.Object +{ + // Public + + apply() + { + // Implemented by subclasses. + console.error("Needs to be implemented by a subclass."); + } + + revert() + { + // Implemented by subclasses. + console.error("Needs to be implemented by a subclass."); + } + + copy() + { + // Override by subclasses. + return this; + } +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/ScopeChainNode.js b/Source/WebInspectorUI/UserInterface/Models/ScopeChainNode.js new file mode 100644 index 000000000..8e3811e80 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/ScopeChainNode.js @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2013 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. + */ + +WebInspector.ScopeChainNode = class ScopeChainNode extends WebInspector.Object +{ + constructor(type, objects, name, location, empty) + { + super(); + + console.assert(typeof type === "string"); + console.assert(objects.every((x) => x instanceof WebInspector.RemoteObject)); + + if (type in WebInspector.ScopeChainNode.Type) + type = WebInspector.ScopeChainNode.Type[type]; + + this._type = type || null; + this._objects = objects || []; + this._name = name || ""; + this._location = location || null; + this._empty = empty || false; + } + + // Public + + get type() { return this._type; } + get objects() { return this._objects; } + get name() { return this._name; } + get location() { return this._location; } + get empty() { return this._empty; } + + get hash() + { + if (this._hash) + return this._hash; + + this._hash = this._name; + if (this._location) + this._hash += `:${this._location.scriptId}:${this._location.lineNumber}:${this._location.columnNumber}`; + return this._hash; + } + + convertToLocalScope() + { + this._type = WebInspector.ScopeChainNode.Type.Local; + } +}; + +WebInspector.ScopeChainNode.Type = { + Local: "scope-chain-type-local", + Global: "scope-chain-type-global", + GlobalLexicalEnvironment: "scope-chain-type-global-lexical-environment", + With: "scope-chain-type-with", + Closure: "scope-chain-type-closure", + Catch: "scope-chain-type-catch", + FunctionName: "scope-chain-type-function-name", + Block: "scope-chain-type-block", +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/Script.js b/Source/WebInspectorUI/UserInterface/Models/Script.js new file mode 100644 index 000000000..22626ce47 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/Script.js @@ -0,0 +1,303 @@ +/* + * Copyright (C) 2013 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. + */ + +WebInspector.Script = class Script extends WebInspector.SourceCode +{ + constructor(target, id, range, url, sourceType, injected, sourceURL, sourceMapURL) + { + super(); + + console.assert(id); + console.assert(target instanceof WebInspector.Target); + console.assert(range instanceof WebInspector.TextRange); + + this._target = target; + this._id = id || null; + this._range = range || null; + this._url = url || null; + this._sourceType = sourceType || WebInspector.Script.SourceType.Program; + this._sourceURL = sourceURL || null; + this._sourceMappingURL = sourceMapURL || null; + this._injected = injected || false; + this._dynamicallyAddedScriptElement = false; + this._scriptSyntaxTree = null; + + this._resource = this._resolveResource(); + + // If this Script was a dynamically added <script> to a Document, + // do not associate with the Document resource, instead associate + // with the frame as a dynamic script. + if (this._resource && this._resource.type === WebInspector.Resource.Type.Document && !this._range.startLine && !this._range.startColumn) { + console.assert(this._resource.isMainResource()); + let documentResource = this._resource; + this._resource = null; + this._dynamicallyAddedScriptElement = true; + documentResource.parentFrame.addExtraScript(this); + this._dynamicallyAddedScriptElementNumber = documentResource.parentFrame.extraScriptCollection.items.size; + } else if (this._resource) + this._resource.associateWithScript(this); + + if (isWebInspectorConsoleEvaluationScript(this._sourceURL)) { + // Assign a unique number to the script object so it will stay the same. + this._uniqueDisplayNameNumber = this.constructor._nextUniqueConsoleDisplayNameNumber++; + } + + if (this._sourceMappingURL) + WebInspector.sourceMapManager.downloadSourceMap(this._sourceMappingURL, this._url, this); + } + + // Static + + static resetUniqueDisplayNameNumbers() + { + WebInspector.Script._nextUniqueDisplayNameNumber = 1; + WebInspector.Script._nextUniqueConsoleDisplayNameNumber = 1; + } + + // Public + + get target() { return this._target; } + get id() { return this._id; } + get range() { return this._range; } + get url() { return this._url; } + get sourceType() { return this._sourceType; } + get sourceURL() { return this._sourceURL; } + get sourceMappingURL() { return this._sourceMappingURL; } + get injected() { return this._injected; } + + get contentIdentifier() + { + if (this._url) + return this._url; + + if (!this._sourceURL) + return null; + + // Since reused content identifiers can cause breakpoints + // to show up in completely unrelated files, sourceURLs should + // be unique where possible. The checks below exclude cases + // where sourceURLs are intentionally reused and we would never + // expect a breakpoint to be persisted across sessions. + if (isWebInspectorConsoleEvaluationScript(this._sourceURL)) + return null; + + if (isWebInspectorInternalScript(this._sourceURL)) + return null; + + return this._sourceURL; + } + + get urlComponents() + { + if (!this._urlComponents) + this._urlComponents = parseURL(this._url); + return this._urlComponents; + } + + get mimeType() + { + return this._resource.mimeType; + } + + get displayName() + { + if (this._url && !this._dynamicallyAddedScriptElement) + return WebInspector.displayNameForURL(this._url, this.urlComponents); + + if (isWebInspectorConsoleEvaluationScript(this._sourceURL)) { + console.assert(this._uniqueDisplayNameNumber); + return WebInspector.UIString("Console Evaluation %d").format(this._uniqueDisplayNameNumber); + } + + if (this._sourceURL) { + if (!this._sourceURLComponents) + this._sourceURLComponents = parseURL(this._sourceURL); + return WebInspector.displayNameForURL(this._sourceURL, this._sourceURLComponents); + } + + if (this._dynamicallyAddedScriptElement) + return WebInspector.UIString("Script Element %d").format(this._dynamicallyAddedScriptElementNumber); + + // Assign a unique number to the script object so it will stay the same. + if (!this._uniqueDisplayNameNumber) + this._uniqueDisplayNameNumber = this.constructor._nextUniqueDisplayNameNumber++; + + return WebInspector.UIString("Anonymous Script %d").format(this._uniqueDisplayNameNumber); + } + + get displayURL() + { + const isMultiLine = true; + const dataURIMaxSize = 64; + + if (this._url) + return WebInspector.truncateURL(this._url, isMultiLine, dataURIMaxSize); + if (this._sourceURL) + return WebInspector.truncateURL(this._sourceURL, isMultiLine, dataURIMaxSize); + return null; + } + + get dynamicallyAddedScriptElement() + { + return this._dynamicallyAddedScriptElement; + } + + get anonymous() + { + return !this._resource && !this._url && !this._sourceURL; + } + + get resource() + { + return this._resource; + } + + get scriptSyntaxTree() + { + return this._scriptSyntaxTree; + } + + isMainResource() + { + return this._target.mainResource === this; + } + + requestContentFromBackend() + { + if (!this._id) { + // There is no identifier to request content with. Return false to cause the + // pending callbacks to get null content. + return Promise.reject(new Error("There is no identifier to request content with.")); + } + + return this._target.DebuggerAgent.getScriptSource(this._id); + } + + saveIdentityToCookie(cookie) + { + cookie[WebInspector.Script.URLCookieKey] = this.url; + cookie[WebInspector.Script.DisplayNameCookieKey] = this.displayName; + } + + requestScriptSyntaxTree(callback) + { + if (this._scriptSyntaxTree) { + setTimeout(() => { callback(this._scriptSyntaxTree); }, 0); + return; + } + + var makeSyntaxTreeAndCallCallback = (content) => { + this._makeSyntaxTree(content); + callback(this._scriptSyntaxTree); + }; + + var content = this.content; + if (!content && this._resource && this._resource.type === WebInspector.Resource.Type.Script && this._resource.finished) + content = this._resource.content; + if (content) { + setTimeout(makeSyntaxTreeAndCallCallback, 0, content); + return; + } + + this.requestContent().then(function(parameters) { + makeSyntaxTreeAndCallCallback(parameters.sourceCode.content); + }).catch(function(error) { + makeSyntaxTreeAndCallCallback(null); + }); + } + + // Private + + _resolveResource() + { + // FIXME: We should be able to associate a Script with a Resource through identifiers, + // we shouldn't need to lookup by URL, which is not safe with frames, where there might + // be multiple resources with the same URL. + // <rdar://problem/13373951> Scripts should be able to associate directly with a Resource + + // No URL, no resource. + if (!this._url) + return null; + + let resolver = WebInspector.frameResourceManager; + if (this._target !== WebInspector.mainTarget) + resolver = this._target.resourceCollection; + + try { + // Try with the Script's full URL. + let resource = resolver.resourceForURL(this._url); + if (resource) + return resource; + + // Try with the Script's full decoded URL. + let decodedURL = decodeURI(this._url); + if (decodedURL !== this._url) { + resource = resolver.resourceForURL(decodedURL); + if (resource) + return resource; + } + + // Next try removing any fragment in the original URL. + let urlWithoutFragment = removeURLFragment(this._url); + if (urlWithoutFragment !== this._url) { + resource = resolver.resourceForURL(urlWithoutFragment); + if (resource) + return resource; + } + + // Finally try removing any fragment in the decoded URL. + let decodedURLWithoutFragment = removeURLFragment(decodedURL); + if (decodedURLWithoutFragment !== decodedURL) { + resource = resolver.resourceForURL(decodedURLWithoutFragment); + if (resource) + return resource; + } + } catch (e) { + // Ignore possible URIErrors. + } + + return null; + } + + _makeSyntaxTree(sourceText) + { + if (this._scriptSyntaxTree || !sourceText) + return; + + this._scriptSyntaxTree = new WebInspector.ScriptSyntaxTree(sourceText, this); + } +}; + +WebInspector.Script.SourceType = { + Program: "script-source-type-program", + Module: "script-source-type-module", +}; + +WebInspector.Script.TypeIdentifier = "script"; +WebInspector.Script.URLCookieKey = "script-url"; +WebInspector.Script.DisplayNameCookieKey = "script-display-name"; + +WebInspector.Script._nextUniqueDisplayNameNumber = 1; +WebInspector.Script._nextUniqueConsoleDisplayNameNumber = 1; diff --git a/Source/WebInspectorUI/UserInterface/Models/ScriptInstrument.js b/Source/WebInspectorUI/UserInterface/Models/ScriptInstrument.js new file mode 100644 index 000000000..cea34da36 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/ScriptInstrument.js @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2015 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. + */ + +WebInspector.ScriptInstrument = class ScriptInstrument extends WebInspector.Instrument +{ + // Protected + + get timelineRecordType() + { + return WebInspector.TimelineRecord.Type.Script; + } + + startInstrumentation(initiatedByBackend) + { + // COMPATIBILITY (iOS 9): Legacy backends did not have ScriptProfilerAgent. They use TimelineAgent. + if (!window.ScriptProfilerAgent) { + super.startInstrumentation(); + return; + } + + // FIXME: Make this some UI visible option. + const includeSamples = true; + + if (!initiatedByBackend) + ScriptProfilerAgent.startTracking(includeSamples); + } + + stopInstrumentation(initiatedByBackend) + { + // COMPATIBILITY (iOS 9): Legacy backends did not have ScriptProfilerAgent. They use TimelineAgent. + if (!window.ScriptProfilerAgent) { + super.stopInstrumentation(); + return; + } + + if (!initiatedByBackend) + ScriptProfilerAgent.stopTracking(); + } +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/ScriptSyntaxTree.js b/Source/WebInspectorUI/UserInterface/Models/ScriptSyntaxTree.js new file mode 100644 index 000000000..ab69285f2 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/ScriptSyntaxTree.js @@ -0,0 +1,1146 @@ +/* + * Copyright (C) 2014, 2015 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. + */ + +WebInspector.ScriptSyntaxTree = class ScriptSyntaxTree extends WebInspector.Object +{ + constructor(sourceText, script) + { + super(); + + console.assert(script && script instanceof WebInspector.Script, script); + + this._script = script; + + try { + let sourceType = this._script.sourceType === WebInspector.Script.SourceType.Module ? "module" : "script"; + let esprimaSyntaxTree = esprima.parse(sourceText, {range: true, sourceType}); + this._syntaxTree = this._createInternalSyntaxTree(esprimaSyntaxTree); + this._parsedSuccessfully = true; + } catch (error) { + this._parsedSuccessfully = false; + this._syntaxTree = null; + console.error("Couldn't parse JavaScript File: " + script.url, error); + } + } + + // Public + + get parsedSuccessfully() + { + return this._parsedSuccessfully; + } + + forEachNode(callback) + { + console.assert(this._parsedSuccessfully); + if (!this._parsedSuccessfully) + return; + + this._recurse(this._syntaxTree, callback, this._defaultParserState()); + } + + filter(predicate, startNode) + { + console.assert(startNode && this._parsedSuccessfully); + if (!this._parsedSuccessfully) + return []; + + var nodes = []; + function filter(node, state) + { + if (predicate(node)) + nodes.push(node); + else + state.skipChildNodes = true; + } + + this._recurse(startNode, filter, this._defaultParserState()); + + return nodes; + } + + containersOfOffset(offset) + { + console.assert(this._parsedSuccessfully); + if (!this._parsedSuccessfully) + return []; + + let allNodes = []; + const start = 0; + const end = 1; + + this.forEachNode((node, state) => { + if (node.range[end] < offset) + state.skipChildNodes = true; + if (node.range[start] > offset) + state.shouldStopEarly = true; + if (node.range[start] <= offset && node.range[end] >= offset) + allNodes.push(node); + }); + + return allNodes; + } + + filterByRange(startOffset, endOffset) + { + console.assert(this._parsedSuccessfully); + if (!this._parsedSuccessfully) + return []; + + var allNodes = []; + var start = 0; + var end = 1; + function filterForNodesInRange(node, state) + { + // program start range program end + // [ [ ] ] + // [ ] [ [ ] ] [ ] + + // If a node's range ends before the range we're interested in starts, we don't need to search any of its + // enclosing ranges, because, by definition, those enclosing ranges are contained within this node's range. + if (node.range[end] < startOffset) + state.skipChildNodes = true; + + // We are only interested in nodes whose start position is within our range. + if (startOffset <= node.range[start] && node.range[start] <= endOffset) + allNodes.push(node); + + // Once we see nodes that start beyond our range, we can quit traversing the AST. We can do this safely + // because we know the AST is traversed using depth first search, so it will traverse into enclosing ranges + // before it traverses into adjacent ranges. + if (node.range[start] > endOffset) + state.shouldStopEarly = true; + } + + this.forEachNode(filterForNodesInRange); + + return allNodes; + } + + containsNonEmptyReturnStatement(startNode) + { + console.assert(startNode && this._parsedSuccessfully); + if (!this._parsedSuccessfully) + return false; + + if (startNode.attachments._hasNonEmptyReturnStatement !== undefined) + return startNode.attachments._hasNonEmptyReturnStatement; + + function removeFunctionsFilter(node) + { + return node.type !== WebInspector.ScriptSyntaxTree.NodeType.FunctionExpression + && node.type !== WebInspector.ScriptSyntaxTree.NodeType.FunctionDeclaration + && node.type !== WebInspector.ScriptSyntaxTree.NodeType.ArrowFunctionExpression; + } + + var nodes = this.filter(removeFunctionsFilter, startNode); + var hasNonEmptyReturnStatement = false; + var returnStatementType = WebInspector.ScriptSyntaxTree.NodeType.ReturnStatement; + for (var node of nodes) { + if (node.type === returnStatementType && node.argument) { + hasNonEmptyReturnStatement = true; + break; + } + } + + startNode.attachments._hasNonEmptyReturnStatement = hasNonEmptyReturnStatement; + + return hasNonEmptyReturnStatement; + } + + static functionReturnDivot(node) + { + console.assert(node.type === WebInspector.ScriptSyntaxTree.NodeType.FunctionDeclaration || node.type === WebInspector.ScriptSyntaxTree.NodeType.FunctionExpression || node.type === WebInspector.ScriptSyntaxTree.NodeType.MethodDefinition || node.type === WebInspector.ScriptSyntaxTree.NodeType.ArrowFunctionExpression); + + // COMPATIBILITY (iOS 9): Legacy Backends view the return type as being the opening "{" of the function body. + // After iOS 9, this is to move to the start of the function statement/expression. See below: + // FIXME: Need a better way to determine backend versions. Using DOM.pseudoElement because that was added after iOS 9. + if (!DOMAgent.hasEvent("pseudoElementAdded")) + return node.body.range[0]; + + // "f" in "function". "s" in "set". "g" in "get". First letter in any method name for classes and object literals. + // The "[" for computed methods in classes and object literals. + return node.typeProfilingReturnDivot; + } + + updateTypes(nodesToUpdate, callback) + { + console.assert(RuntimeAgent.getRuntimeTypesForVariablesAtOffsets); + console.assert(Array.isArray(nodesToUpdate) && this._parsedSuccessfully); + + if (!this._parsedSuccessfully) + return; + + var allRequests = []; + var allRequestNodes = []; + var sourceID = this._script.id; + + for (var node of nodesToUpdate) { + switch (node.type) { + case WebInspector.ScriptSyntaxTree.NodeType.FunctionDeclaration: + case WebInspector.ScriptSyntaxTree.NodeType.FunctionExpression: + case WebInspector.ScriptSyntaxTree.NodeType.ArrowFunctionExpression: + for (var param of node.params) { + for (var identifier of this._gatherIdentifiersInDeclaration(param)) { + allRequests.push({ + typeInformationDescriptor: WebInspector.ScriptSyntaxTree.TypeProfilerSearchDescriptor.NormalExpression, + sourceID, + divot: identifier.range[0] + }); + allRequestNodes.push(identifier); + } + } + + allRequests.push({ + typeInformationDescriptor: WebInspector.ScriptSyntaxTree.TypeProfilerSearchDescriptor.FunctionReturn, + sourceID, + divot: WebInspector.ScriptSyntaxTree.functionReturnDivot(node) + }); + allRequestNodes.push(node); + break; + case WebInspector.ScriptSyntaxTree.NodeType.VariableDeclarator: + for (var identifier of this._gatherIdentifiersInDeclaration(node.id)) { + allRequests.push({ + typeInformationDescriptor: WebInspector.ScriptSyntaxTree.TypeProfilerSearchDescriptor.NormalExpression, + sourceID, + divot: identifier.range[0] + }); + allRequestNodes.push(identifier); + } + break; + } + } + + console.assert(allRequests.length === allRequestNodes.length); + + function handleTypes(error, typeInformationArray) + { + if (error) + return; + + console.assert(typeInformationArray.length === allRequests.length); + + for (var i = 0; i < typeInformationArray.length; i++) { + var node = allRequestNodes[i]; + var typeInformation = WebInspector.TypeDescription.fromPayload(typeInformationArray[i]); + if (allRequests[i].typeInformationDescriptor === WebInspector.ScriptSyntaxTree.TypeProfilerSearchDescriptor.FunctionReturn) + node.attachments.returnTypes = typeInformation; + else + node.attachments.types = typeInformation; + } + + callback(allRequestNodes); + } + + this._script.target.RuntimeAgent.getRuntimeTypesForVariablesAtOffsets(allRequests, handleTypes); + } + + // Private + + _gatherIdentifiersInDeclaration(node) + { + function gatherIdentifiers(node) + { + switch (node.type) { + case WebInspector.ScriptSyntaxTree.NodeType.Identifier: + return [node]; + case WebInspector.ScriptSyntaxTree.NodeType.Property: + return gatherIdentifiers(node.value); + case WebInspector.ScriptSyntaxTree.NodeType.ObjectPattern: + var identifiers = []; + for (var property of node.properties) { + for (var identifier of gatherIdentifiers(property)) + identifiers.push(identifier); + } + return identifiers; + case WebInspector.ScriptSyntaxTree.NodeType.ArrayPattern: + var identifiers = []; + for (var element of node.elements) { + for (var identifier of gatherIdentifiers(element)) + identifiers.push(identifier); + } + return identifiers; + case WebInspector.ScriptSyntaxTree.NodeType.AssignmentPattern: + return gatherIdentifiers(node.left); + case WebInspector.ScriptSyntaxTree.NodeType.RestElement: + case WebInspector.ScriptSyntaxTree.NodeType.RestProperty: + return gatherIdentifiers(node.argument); + default: + console.assert(false, "Unexpected node type in variable declarator: " + node.type); + return []; + } + } + + console.assert(node.type === WebInspector.ScriptSyntaxTree.NodeType.Identifier || node.type === WebInspector.ScriptSyntaxTree.NodeType.ObjectPattern || node.type === WebInspector.ScriptSyntaxTree.NodeType.ArrayPattern || node.type === WebInspector.ScriptSyntaxTree.NodeType.RestElement || node.type === WebInspector.ScriptSyntaxTree.NodeType.RestProperty); + + return gatherIdentifiers(node); + } + + _defaultParserState() + { + return { + shouldStopEarly: false, + skipChildNodes: false + }; + } + + _recurse(node, callback, state) + { + if (!node) + return; + + if (state.shouldStopEarly || state.skipChildNodes) + return; + + callback(node, state); + + switch (node.type) { + case WebInspector.ScriptSyntaxTree.NodeType.AssignmentExpression: + this._recurse(node.left, callback, state); + this._recurse(node.right, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.ArrayExpression: + case WebInspector.ScriptSyntaxTree.NodeType.ArrayPattern: + this._recurseArray(node.elements, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.AssignmentPattern: + this._recurse(node.left, callback, state); + this._recurse(node.right, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.AwaitExpression: + this._recurse(node.argument, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.BlockStatement: + this._recurseArray(node.body, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.BinaryExpression: + this._recurse(node.left, callback, state); + this._recurse(node.right, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.BreakStatement: + this._recurse(node.label, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.CatchClause: + this._recurse(node.param, callback, state); + this._recurse(node.body, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.CallExpression: + this._recurse(node.callee, callback, state); + this._recurseArray(node.arguments, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.ClassBody: + this._recurseArray(node.body, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.ClassDeclaration: + case WebInspector.ScriptSyntaxTree.NodeType.ClassExpression: + this._recurse(node.id, callback, state); + this._recurse(node.superClass, callback, state); + this._recurse(node.body, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.ContinueStatement: + this._recurse(node.label, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.DoWhileStatement: + this._recurse(node.body, callback, state); + this._recurse(node.test, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.ExpressionStatement: + this._recurse(node.expression, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.ForStatement: + this._recurse(node.init, callback, state); + this._recurse(node.test, callback, state); + this._recurse(node.update, callback, state); + this._recurse(node.body, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.ForInStatement: + case WebInspector.ScriptSyntaxTree.NodeType.ForOfStatement: + this._recurse(node.left, callback, state); + this._recurse(node.right, callback, state); + this._recurse(node.body, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.FunctionDeclaration: + case WebInspector.ScriptSyntaxTree.NodeType.FunctionExpression: + case WebInspector.ScriptSyntaxTree.NodeType.ArrowFunctionExpression: + this._recurse(node.id, callback, state); + this._recurseArray(node.params, callback, state); + this._recurse(node.body, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.IfStatement: + this._recurse(node.test, callback, state); + this._recurse(node.consequent, callback, state); + this._recurse(node.alternate, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.LabeledStatement: + this._recurse(node.label, callback, state); + this._recurse(node.body, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.LogicalExpression: + this._recurse(node.left, callback, state); + this._recurse(node.right, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.MemberExpression: + this._recurse(node.object, callback, state); + this._recurse(node.property, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.MethodDefinition: + this._recurse(node.key, callback, state); + this._recurse(node.value, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.NewExpression: + this._recurse(node.callee, callback, state); + this._recurseArray(node.arguments, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.ObjectExpression: + case WebInspector.ScriptSyntaxTree.NodeType.ObjectPattern: + this._recurseArray(node.properties, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.Program: + this._recurseArray(node.body, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.Property: + this._recurse(node.key, callback, state); + this._recurse(node.value, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.RestElement: + this._recurse(node.argument, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.RestProperty: + this._recurse(node.argument, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.ReturnStatement: + this._recurse(node.argument, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.SequenceExpression: + this._recurseArray(node.expressions, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.SpreadElement: + this._recurse(node.argument, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.SpreadProperty: + this._recurse(node.argument, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.SwitchStatement: + this._recurse(node.discriminant, callback, state); + this._recurseArray(node.cases, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.SwitchCase: + this._recurse(node.test, callback, state); + this._recurseArray(node.consequent, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.ConditionalExpression: + this._recurse(node.test, callback, state); + this._recurse(node.consequent, callback, state); + this._recurse(node.alternate, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.TaggedTemplateExpression: + this._recurse(node.tag, callback, state); + this._recurse(node.quasi, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.TemplateLiteral: + this._recurseArray(node.quasis, callback, state); + this._recurseArray(node.expressions, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.ThrowStatement: + this._recurse(node.argument, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.TryStatement: + this._recurse(node.block, callback, state); + this._recurse(node.handler, callback, state); + this._recurse(node.finalizer, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.UnaryExpression: + this._recurse(node.argument, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.UpdateExpression: + this._recurse(node.argument, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.VariableDeclaration: + this._recurseArray(node.declarations, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.VariableDeclarator: + this._recurse(node.id, callback, state); + this._recurse(node.init, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.WhileStatement: + this._recurse(node.test, callback, state); + this._recurse(node.body, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.WithStatement: + this._recurse(node.object, callback, state); + this._recurse(node.body, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.YieldExpression: + this._recurse(node.argument, callback, state); + break; + + // Modules. + + case WebInspector.ScriptSyntaxTree.NodeType.ExportAllDeclaration: + this._recurse(node.source, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.ExportNamedDeclaration: + this._recurse(node.declaration, callback, state); + this._recurseArray(node.specifiers, callback, state); + this._recurse(node.source, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.ExportDefaultDeclaration: + this._recurse(node.declaration, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.ExportSpecifier: + this._recurse(node.local, callback, state); + this._recurse(node.exported, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.ImportDeclaration: + this._recurseArray(node.specifiers, callback, state); + this._recurse(node.source, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.ImportDefaultSpecifier: + this._recurse(node.local, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.ImportNamespaceSpecifier: + this._recurse(node.local, callback, state); + break; + case WebInspector.ScriptSyntaxTree.NodeType.ImportSpecifier: + this._recurse(node.imported, callback, state); + this._recurse(node.local, callback, state); + break; + + // All the leaf nodes go here. + case WebInspector.ScriptSyntaxTree.NodeType.DebuggerStatement: + case WebInspector.ScriptSyntaxTree.NodeType.EmptyStatement: + case WebInspector.ScriptSyntaxTree.NodeType.Identifier: + case WebInspector.ScriptSyntaxTree.NodeType.Import: + case WebInspector.ScriptSyntaxTree.NodeType.Literal: + case WebInspector.ScriptSyntaxTree.NodeType.MetaProperty: + case WebInspector.ScriptSyntaxTree.NodeType.Super: + case WebInspector.ScriptSyntaxTree.NodeType.ThisExpression: + case WebInspector.ScriptSyntaxTree.NodeType.TemplateElement: + break; + } + + state.skipChildNodes = false; + } + + _recurseArray(array, callback, state) + { + for (var node of array) + this._recurse(node, callback, state); + } + + // This function translates from esprima's Abstract Syntax Tree to ours. + // Mostly, this is just the identity function. We've added an extra typeProfilingReturnDivot property for functions/methods. + // Our AST complies with the Mozilla parser API: + // https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Parser_API + _createInternalSyntaxTree(node) + { + if (!node) + return null; + + var result = null; + switch (node.type) { + case "ArrayExpression": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.ArrayExpression, + elements: node.elements.map(this._createInternalSyntaxTree, this) + }; + break; + case "ArrayPattern": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.ArrayPattern, + elements: node.elements.map(this._createInternalSyntaxTree, this) + }; + break; + case "ArrowFunctionExpression": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.ArrowFunctionExpression, + id: this._createInternalSyntaxTree(node.id), + params: node.params.map(this._createInternalSyntaxTree, this), + body: this._createInternalSyntaxTree(node.body), + generator: node.generator, + expression: node.expression, // Boolean indicating if the body a single expression or a block statement. + async: node.async, + typeProfilingReturnDivot: node.range[0] + }; + break; + case "AssignmentExpression": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.AssignmentExpression, + operator: node.operator, + left: this._createInternalSyntaxTree(node.left), + right: this._createInternalSyntaxTree(node.right) + }; + break; + case "AssignmentPattern": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.AssignmentPattern, + left: this._createInternalSyntaxTree(node.left), + right: this._createInternalSyntaxTree(node.right), + }; + break; + case "AwaitExpression": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.AwaitExpression, + argument: this._createInternalSyntaxTree(node.argument), + }; + break; + case "BlockStatement": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.BlockStatement, + body: node.body.map(this._createInternalSyntaxTree, this) + }; + break; + case "BinaryExpression": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.BinaryExpression, + operator: node.operator, + left: this._createInternalSyntaxTree(node.left), + right: this._createInternalSyntaxTree(node.right) + }; + break; + case "BreakStatement": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.BreakStatement, + label: this._createInternalSyntaxTree(node.label) + }; + break; + case "CallExpression": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.CallExpression, + callee: this._createInternalSyntaxTree(node.callee), + arguments: node.arguments.map(this._createInternalSyntaxTree, this) + }; + break; + case "CatchClause": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.CatchClause, + param: this._createInternalSyntaxTree(node.param), + body: this._createInternalSyntaxTree(node.body) + }; + break; + case "ClassBody": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.ClassBody, + body: node.body.map(this._createInternalSyntaxTree, this) + }; + break; + case "ClassDeclaration": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.ClassDeclaration, + id: this._createInternalSyntaxTree(node.id), + superClass: this._createInternalSyntaxTree(node.superClass), + body: this._createInternalSyntaxTree(node.body), + }; + break; + case "ClassExpression": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.ClassExpression, + id: this._createInternalSyntaxTree(node.id), + superClass: this._createInternalSyntaxTree(node.superClass), + body: this._createInternalSyntaxTree(node.body), + }; + break; + case "ConditionalExpression": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.ConditionalExpression, + test: this._createInternalSyntaxTree(node.test), + consequent: this._createInternalSyntaxTree(node.consequent), + alternate: this._createInternalSyntaxTree(node.alternate) + }; + break; + case "ContinueStatement": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.ContinueStatement, + label: this._createInternalSyntaxTree(node.label) + }; + break; + case "DoWhileStatement": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.DoWhileStatement, + body: this._createInternalSyntaxTree(node.body), + test: this._createInternalSyntaxTree(node.test) + }; + break; + case "DebuggerStatement": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.DebuggerStatement + }; + break; + case "EmptyStatement": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.EmptyStatement + }; + break; + case "ExpressionStatement": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.ExpressionStatement, + expression: this._createInternalSyntaxTree(node.expression) + }; + break; + case "ForStatement": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.ForStatement, + init: this._createInternalSyntaxTree(node.init), + test: this._createInternalSyntaxTree(node.test), + update: this._createInternalSyntaxTree(node.update), + body: this._createInternalSyntaxTree(node.body) + }; + break; + case "ForInStatement": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.ForInStatement, + left: this._createInternalSyntaxTree(node.left), + right: this._createInternalSyntaxTree(node.right), + body: this._createInternalSyntaxTree(node.body) + }; + break; + case "ForOfStatement": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.ForOfStatement, + left: this._createInternalSyntaxTree(node.left), + right: this._createInternalSyntaxTree(node.right), + body: this._createInternalSyntaxTree(node.body) + }; + break; + case "FunctionDeclaration": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.FunctionDeclaration, + id: this._createInternalSyntaxTree(node.id), + params: node.params.map(this._createInternalSyntaxTree, this), + body: this._createInternalSyntaxTree(node.body), + generator: node.generator, + async: node.async, + typeProfilingReturnDivot: node.range[0] + }; + break; + case "FunctionExpression": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.FunctionExpression, + id: this._createInternalSyntaxTree(node.id), + params: node.params.map(this._createInternalSyntaxTree, this), + body: this._createInternalSyntaxTree(node.body), + generator: node.generator, + async: node.async, + typeProfilingReturnDivot: node.range[0] // This may be overridden in the Property AST node. + }; + break; + case "Identifier": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.Identifier, + name: node.name + }; + break; + case "IfStatement": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.IfStatement, + test: this._createInternalSyntaxTree(node.test), + consequent: this._createInternalSyntaxTree(node.consequent), + alternate: this._createInternalSyntaxTree(node.alternate) + }; + break; + case "Literal": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.Literal, + value: node.value, + raw: node.raw + }; + break; + case "LabeledStatement": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.LabeledStatement, + label: this._createInternalSyntaxTree(node.label), + body: this._createInternalSyntaxTree(node.body) + }; + break; + case "LogicalExpression": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.LogicalExpression, + left: this._createInternalSyntaxTree(node.left), + right: this._createInternalSyntaxTree(node.right), + operator: node.operator + }; + break; + case "MemberExpression": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.MemberExpression, + object: this._createInternalSyntaxTree(node.object), + property: this._createInternalSyntaxTree(node.property), + computed: node.computed + }; + break; + case "MetaProperty": + // i.e: new.target produces {meta: "new", property: "target"} + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.MetaProperty, + meta: this._createInternalSyntaxTree(node.meta), + property: this._createInternalSyntaxTree(node.property), + }; + break; + case "MethodDefinition": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.MethodDefinition, + key: this._createInternalSyntaxTree(node.key), + value: this._createInternalSyntaxTree(node.value), + computed: node.computed, + kind: node.kind, + static: node.static + }; + result.value.typeProfilingReturnDivot = node.range[0]; // "g" in "get" or "s" in "set" or "[" in "['computed']" or "m" in "methodName". + break; + case "NewExpression": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.NewExpression, + callee: this._createInternalSyntaxTree(node.callee), + arguments: node.arguments.map(this._createInternalSyntaxTree, this) + }; + break; + case "ObjectExpression": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.ObjectExpression, + properties: node.properties.map(this._createInternalSyntaxTree, this) + }; + break; + case "ObjectPattern": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.ObjectPattern, + properties: node.properties.map(this._createInternalSyntaxTree, this) + }; + break; + case "Program": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.Program, + sourceType: node.sourceType, + body: node.body.map(this._createInternalSyntaxTree, this) + }; + break; + case "Property": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.Property, + key: this._createInternalSyntaxTree(node.key), + value: this._createInternalSyntaxTree(node.value), + kind: node.kind, + method: node.method, + computed: node.computed + }; + if (result.kind === "get" || result.kind === "set" || result.method) + result.value.typeProfilingReturnDivot = node.range[0]; // "g" in "get" or "s" in "set" or "[" in "['computed']" method or "m" in "methodName". + break; + case "RestElement": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.RestElement, + argument: this._createInternalSyntaxTree(node.argument) + }; + break; + case "RestProperty": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.RestProperty, + argument: this._createInternalSyntaxTree(node.argument), + }; + break; + case "ReturnStatement": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.ReturnStatement, + argument: this._createInternalSyntaxTree(node.argument) + }; + break; + case "SequenceExpression": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.SequenceExpression, + expressions: node.expressions.map(this._createInternalSyntaxTree, this) + }; + break; + case "SpreadElement": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.SpreadElement, + argument: this._createInternalSyntaxTree(node.argument), + }; + break; + case "SpreadProperty": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.SpreadProperty, + argument: this._createInternalSyntaxTree(node.argument), + }; + break; + case "Super": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.Super + }; + break; + case "SwitchStatement": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.SwitchStatement, + discriminant: this._createInternalSyntaxTree(node.discriminant), + cases: node.cases.map(this._createInternalSyntaxTree, this) + }; + break; + case "SwitchCase": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.SwitchCase, + test: this._createInternalSyntaxTree(node.test), + consequent: node.consequent.map(this._createInternalSyntaxTree, this) + }; + break; + case "TaggedTemplateExpression": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.TaggedTemplateExpression, + tag: this._createInternalSyntaxTree(node.tag), + quasi: this._createInternalSyntaxTree(node.quasi) + }; + break; + case "TemplateElement": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.TemplateElement, + value: node.value, + tail: node.tail + }; + break; + case "TemplateLiteral": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.TemplateLiteral, + quasis: node.quasis.map(this._createInternalSyntaxTree, this), + expressions: node.expressions.map(this._createInternalSyntaxTree, this) + }; + break; + case "ThisExpression": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.ThisExpression + }; + break; + case "ThrowStatement": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.ThrowStatement, + argument: this._createInternalSyntaxTree(node.argument) + }; + break; + case "TryStatement": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.TryStatement, + block: this._createInternalSyntaxTree(node.block), + handler: this._createInternalSyntaxTree(node.handler), + finalizer: this._createInternalSyntaxTree(node.finalizer) + }; + break; + case "UnaryExpression": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.UnaryExpression, + operator: node.operator, + argument: this._createInternalSyntaxTree(node.argument) + }; + break; + case "UpdateExpression": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.UpdateExpression, + operator: node.operator, + prefix: node.prefix, + argument: this._createInternalSyntaxTree(node.argument) + }; + break; + case "VariableDeclaration": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.VariableDeclaration, + declarations: node.declarations.map(this._createInternalSyntaxTree, this), + kind: node.kind + }; + break; + case "VariableDeclarator": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.VariableDeclarator, + id: this._createInternalSyntaxTree(node.id), + init: this._createInternalSyntaxTree(node.init) + }; + break; + case "WhileStatement": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.WhileStatement, + test: this._createInternalSyntaxTree(node.test), + body: this._createInternalSyntaxTree(node.body) + }; + break; + case "WithStatement": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.WithStatement, + object: this._createInternalSyntaxTree(node.object), + body: this._createInternalSyntaxTree(node.body) + }; + break; + case "YieldExpression": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.YieldExpression, + argument: this._createInternalSyntaxTree(node.argument), + delegate: node.delegate + }; + break; + + // Modules. + + case "ExportAllDeclaration": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.ExportAllDeclaration, + source: this._createInternalSyntaxTree(node.source), + }; + break; + case "ExportNamedDeclaration": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.ExportNamedDeclaration, + declaration: this._createInternalSyntaxTree(node.declaration), + specifiers: node.specifiers.map(this._createInternalSyntaxTree, this), + source: this._createInternalSyntaxTree(node.source), + }; + break; + case "ExportDefaultDeclaration": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.ExportDefaultDeclaration, + declaration: this._createInternalSyntaxTree(node.declaration), + }; + break; + case "ExportSpecifier": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.ExportSpecifier, + local: this._createInternalSyntaxTree(node.local), + exported: this._createInternalSyntaxTree(node.exported), + }; + break; + case "Import": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.Import, + }; + break; + case "ImportDeclaration": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.ImportDeclaration, + specifiers: node.specifiers.map(this._createInternalSyntaxTree, this), + source: this._createInternalSyntaxTree(node.source), + }; + break; + case "ImportDefaultSpecifier": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.ImportDefaultSpecifier, + local: this._createInternalSyntaxTree(node.local), + }; + break; + case "ImportNamespaceSpecifier": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.ImportNamespaceSpecifier, + local: this._createInternalSyntaxTree(node.local), + }; + break; + case "ImportSpecifier": + result = { + type: WebInspector.ScriptSyntaxTree.NodeType.ImportSpecifier, + imported: this._createInternalSyntaxTree(node.imported), + local: this._createInternalSyntaxTree(node.local), + }; + break; + + default: + console.error("Unsupported Syntax Tree Node: " + node.type, node); + return null; + } + + result.range = node.range; + // This is an object for which you can add fields to an AST node without worrying about polluting the syntax-related fields of the node. + result.attachments = {}; + + return result; + } +}; + +// This should be kept in sync with an enum in JavaSciptCore/runtime/TypeProfiler.h +WebInspector.ScriptSyntaxTree.TypeProfilerSearchDescriptor = { + NormalExpression: 1, + FunctionReturn: 2 +}; + +WebInspector.ScriptSyntaxTree.NodeType = { + ArrayExpression: Symbol("array-expression"), + ArrayPattern: Symbol("array-pattern"), + ArrowFunctionExpression: Symbol("arrow-function-expression"), + AssignmentExpression: Symbol("assignment-expression"), + AssignmentPattern: Symbol("assignment-pattern"), + AwaitExpression: Symbol("await-expression"), + BinaryExpression: Symbol("binary-expression"), + BlockStatement: Symbol("block-statement"), + BreakStatement: Symbol("break-statement"), + CallExpression: Symbol("call-expression"), + CatchClause: Symbol("catch-clause"), + ClassBody: Symbol("class-body"), + ClassDeclaration: Symbol("class-declaration"), + ClassExpression: Symbol("class-expression"), + ConditionalExpression: Symbol("conditional-expression"), + ContinueStatement: Symbol("continue-statement"), + DebuggerStatement: Symbol("debugger-statement"), + DoWhileStatement: Symbol("do-while-statement"), + EmptyStatement: Symbol("empty-statement"), + ExportAllDeclaration: Symbol("export-all-declaration"), + ExportDefaultDeclaration: Symbol("export-default-declaration"), + ExportNamedDeclaration: Symbol("export-named-declaration"), + ExportSpecifier: Symbol("export-specifier"), + ExpressionStatement: Symbol("expression-statement"), + ForInStatement: Symbol("for-in-statement"), + ForOfStatement: Symbol("for-of-statement"), + ForStatement: Symbol("for-statement"), + FunctionDeclaration: Symbol("function-declaration"), + FunctionExpression: Symbol("function-expression"), + Identifier: Symbol("identifier"), + IfStatement: Symbol("if-statement"), + Import: Symbol("import"), + ImportDeclaration: Symbol("import-declaration"), + ImportDefaultSpecifier: Symbol("import-default-specifier"), + ImportNamespaceSpecifier: Symbol("import-namespace-specifier"), + ImportSpecifier: Symbol("import-specifier"), + LabeledStatement: Symbol("labeled-statement"), + Literal: Symbol("literal"), + LogicalExpression: Symbol("logical-expression"), + MemberExpression: Symbol("member-expression"), + MetaProperty: Symbol("meta-property"), + MethodDefinition: Symbol("method-definition"), + NewExpression: Symbol("new-expression"), + ObjectExpression: Symbol("object-expression"), + ObjectPattern: Symbol("object-pattern"), + Program: Symbol("program"), + Property: Symbol("property"), + RestElement: Symbol("rest-element"), + RestProperty: Symbol("rest-property"), + ReturnStatement: Symbol("return-statement"), + SequenceExpression: Symbol("sequence-expression"), + SpreadElement: Symbol("spread-element"), + SpreadProperty: Symbol("spread-property"), + Super: Symbol("super"), + SwitchCase: Symbol("switch-case"), + SwitchStatement: Symbol("switch-statement"), + TaggedTemplateExpression: Symbol("tagged-template-expression"), + TemplateElement: Symbol("template-element"), + TemplateLiteral: Symbol("template-literal"), + ThisExpression: Symbol("this-expression"), + ThrowStatement: Symbol("throw-statement"), + TryStatement: Symbol("try-statement"), + UnaryExpression: Symbol("unary-expression"), + UpdateExpression: Symbol("update-expression"), + VariableDeclaration: Symbol("variable-declaration"), + VariableDeclarator: Symbol("variable-declarator"), + WhileStatement: Symbol("while-statement"), + WithStatement: Symbol("with-statement"), + YieldExpression: Symbol("yield-expression"), +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/ScriptTimelineRecord.js b/Source/WebInspectorUI/UserInterface/Models/ScriptTimelineRecord.js new file mode 100644 index 000000000..4a4231d82 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/ScriptTimelineRecord.js @@ -0,0 +1,418 @@ +/* + * Copyright (C) 2013 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. + */ + +WebInspector.ScriptTimelineRecord = class ScriptTimelineRecord extends WebInspector.TimelineRecord +{ + constructor(eventType, startTime, endTime, callFrames, sourceCodeLocation, details, profilePayload) + { + super(WebInspector.TimelineRecord.Type.Script, startTime, endTime, callFrames, sourceCodeLocation); + + console.assert(eventType); + + if (eventType in WebInspector.ScriptTimelineRecord.EventType) + eventType = WebInspector.ScriptTimelineRecord.EventType[eventType]; + + this._eventType = eventType; + this._details = details || ""; + this._profilePayload = profilePayload || null; + this._profile = null; + + // COMPATIBILITY(iOS 9): Before the ScriptProfilerAgent we did not have sample data. Return NaN to match old behavior. + if (!window.ScriptProfilerAgent) + this._callCountOrSamples = NaN; + else { + // NOTE: _callCountOrSamples is being treated as the number of samples. + this._callCountOrSamples = 0; + } + } + + // Public + + get eventType() + { + return this._eventType; + } + + get details() + { + return this._details; + } + + get profile() + { + this._initializeProfileFromPayload(); + return this._profile; + } + + get callCountOrSamples() + { + return this._callCountOrSamples; + } + + isGarbageCollection() + { + return this._eventType === WebInspector.ScriptTimelineRecord.EventType.GarbageCollected; + } + + saveIdentityToCookie(cookie) + { + super.saveIdentityToCookie(cookie); + + cookie[WebInspector.ScriptTimelineRecord.EventTypeCookieKey] = this._eventType; + cookie[WebInspector.ScriptTimelineRecord.DetailsCookieKey] = this._details; + } + + get profilePayload() + { + return this._profilePayload; + } + + set profilePayload(payload) + { + this._profilePayload = payload; + } + + // Private + + _initializeProfileFromPayload(payload) + { + if (this._profile || !this._profilePayload) + return; + + var payload = this._profilePayload; + this._profilePayload = undefined; + + console.assert(payload.rootNodes instanceof Array); + + function profileNodeFromPayload(nodePayload) + { + console.assert("id" in nodePayload); + + if (nodePayload.url) { + var sourceCode = WebInspector.frameResourceManager.resourceForURL(nodePayload.url); + if (!sourceCode) + sourceCode = WebInspector.debuggerManager.scriptsForURL(nodePayload.url, WebInspector.assumingMainTarget())[0]; + + // The lineNumber is 1-based, but we expect 0-based. + var lineNumber = nodePayload.lineNumber - 1; + + var sourceCodeLocation = sourceCode ? sourceCode.createLazySourceCodeLocation(lineNumber, nodePayload.columnNumber) : null; + } + + var isProgramCode = nodePayload.functionName === "(program)"; + var isAnonymousFunction = nodePayload.functionName === "(anonymous function)"; + + var type = isProgramCode ? WebInspector.ProfileNode.Type.Program : WebInspector.ProfileNode.Type.Function; + var functionName = !isProgramCode && !isAnonymousFunction && nodePayload.functionName !== "(unknown)" ? nodePayload.functionName : null; + + // COMPATIBILITY (iOS 8): Timeline.CPUProfileNodes used to include an array of complete + // call information instead of the aggregated "callInfo" data. + var calls = null; + if ("calls" in nodePayload) { + console.assert(nodePayload.calls instanceof Array); + calls = nodePayload.calls.map(profileNodeCallFromPayload); + } + + return new WebInspector.ProfileNode(nodePayload.id, type, functionName, sourceCodeLocation, nodePayload.callInfo, calls, nodePayload.children); + } + + function profileNodeCallFromPayload(nodeCallPayload) + { + console.assert("startTime" in nodeCallPayload); + console.assert("totalTime" in nodeCallPayload); + + var startTime = WebInspector.timelineManager.computeElapsedTime(nodeCallPayload.startTime); + + return new WebInspector.ProfileNodeCall(startTime, nodeCallPayload.totalTime); + } + + var rootNodes = payload.rootNodes; + + // Iterate over the node tree using a stack. Doing this recursively can easily cause a stack overflow. + // We traverse the profile in post-order and convert the payloads in place until we get back to the root. + var stack = [{parent: {children: rootNodes}, index: 0, root: true}]; + while (stack.length) { + var entry = stack.lastValue; + + if (entry.index < entry.parent.children.length) { + var childNodePayload = entry.parent.children[entry.index]; + if (childNodePayload.children && childNodePayload.children.length) + stack.push({parent: childNodePayload, index: 0}); + + ++entry.index; + } else { + if (!entry.root) + entry.parent.children = entry.parent.children.map(profileNodeFromPayload); + else + rootNodes = rootNodes.map(profileNodeFromPayload); + + stack.pop(); + } + } + + // COMPATIBILITY (iOS 9): We only do this when we have ScriptProfilerAgent because before that we didn't have a Sampling Profiler. + if (window.ScriptProfilerAgent) { + for (let i = 0; i < rootNodes.length; i++) + this._callCountOrSamples += rootNodes[i].callInfo.callCount; + } + + this._profile = new WebInspector.Profile(rootNodes); + } +}; + +WebInspector.ScriptTimelineRecord.EventType = { + ScriptEvaluated: "script-timeline-record-script-evaluated", + APIScriptEvaluated: "script-timeline-record-api-script-evaluated", + MicrotaskDispatched: "script-timeline-record-microtask-dispatched", + EventDispatched: "script-timeline-record-event-dispatched", + ProbeSampleRecorded: "script-timeline-record-probe-sample-recorded", + TimerFired: "script-timeline-record-timer-fired", + TimerInstalled: "script-timeline-record-timer-installed", + TimerRemoved: "script-timeline-record-timer-removed", + AnimationFrameFired: "script-timeline-record-animation-frame-fired", + AnimationFrameRequested: "script-timeline-record-animation-frame-requested", + AnimationFrameCanceled: "script-timeline-record-animation-frame-canceled", + ConsoleProfileRecorded: "script-timeline-record-console-profile-recorded", + GarbageCollected: "script-timeline-record-garbage-collected", +}; + +WebInspector.ScriptTimelineRecord.EventType.displayName = function(eventType, details, includeDetailsInMainTitle) +{ + if (details && !WebInspector.ScriptTimelineRecord._eventDisplayNames) { + // These display names are not localized because they closely represent + // the real API name, just with word spaces and Title Case. + + var nameMap = new Map; + nameMap.set("DOMActivate", "DOM Activate"); + nameMap.set("DOMCharacterDataModified", "DOM Character Data Modified"); + nameMap.set("DOMContentLoaded", "DOM Content Loaded"); + nameMap.set("DOMFocusIn", "DOM Focus In"); + nameMap.set("DOMFocusOut", "DOM Focus Out"); + nameMap.set("DOMNodeInserted", "DOM Node Inserted"); + nameMap.set("DOMNodeInsertedIntoDocument", "DOM Node Inserted Into Document"); + nameMap.set("DOMNodeRemoved", "DOM Node Removed"); + nameMap.set("DOMNodeRemovedFromDocument", "DOM Node Removed From Document"); + nameMap.set("DOMSubtreeModified", "DOM Sub-Tree Modified"); + nameMap.set("addsourcebuffer", "Add Source Buffer"); + nameMap.set("addstream", "Add Stream"); + nameMap.set("addtrack", "Add Track"); + nameMap.set("animationend", "Animation End"); + nameMap.set("animationiteration", "Animation Iteration"); + nameMap.set("animationstart", "Animation Start"); + nameMap.set("audioend", "Audio End"); + nameMap.set("audioprocess", "Audio Process"); + nameMap.set("audiostart", "Audio Start"); + nameMap.set("beforecopy", "Before Copy"); + nameMap.set("beforecut", "Before Cut"); + nameMap.set("beforeload", "Before Load"); + nameMap.set("beforepaste", "Before Paste"); + nameMap.set("beforeunload", "Before Unload"); + nameMap.set("canplay", "Can Play"); + nameMap.set("canplaythrough", "Can Play Through"); + nameMap.set("chargingchange", "Charging Change"); + nameMap.set("chargingtimechange", "Charging Time Change"); + nameMap.set("compositionend", "Composition End"); + nameMap.set("compositionstart", "Composition Start"); + nameMap.set("compositionupdate", "Composition Update"); + nameMap.set("contextmenu", "Context Menu"); + nameMap.set("cuechange", "Cue Change"); + nameMap.set("datachannel", "Data Channel"); + nameMap.set("dblclick", "Double Click"); + nameMap.set("devicemotion", "Device Motion"); + nameMap.set("deviceorientation", "Device Orientation"); + nameMap.set("dischargingtimechange", "Discharging Time Change"); + nameMap.set("dragend", "Drag End"); + nameMap.set("dragenter", "Drag Enter"); + nameMap.set("dragleave", "Drag Leave"); + nameMap.set("dragover", "Drag Over"); + nameMap.set("dragstart", "Drag Start"); + nameMap.set("durationchange", "Duration Change"); + nameMap.set("focusin", "Focus In"); + nameMap.set("focusout", "Focus Out"); + nameMap.set("gesturechange", "Gesture Change"); + nameMap.set("gestureend", "Gesture End"); + nameMap.set("gesturescrollend", "Gesture Scroll End"); + nameMap.set("gesturescrollstart", "Gesture Scroll Start"); + nameMap.set("gesturescrollupdate", "Gesture Scroll Update"); + nameMap.set("gesturestart", "Gesture Start"); + nameMap.set("gesturetap", "Gesture Tap"); + nameMap.set("gesturetapdown", "Gesture Tap Down"); + nameMap.set("hashchange", "Hash Change"); + nameMap.set("icecandidate", "ICE Candidate"); + nameMap.set("iceconnectionstatechange", "ICE Connection State Change"); + nameMap.set("keydown", "Key Down"); + nameMap.set("keypress", "Key Press"); + nameMap.set("keyup", "Key Up"); + nameMap.set("levelchange", "Level Change"); + nameMap.set("loadeddata", "Loaded Data"); + nameMap.set("loadedmetadata", "Loaded Metadata"); + nameMap.set("loadend", "Load End"); + nameMap.set("loadingdone", "Loading Done"); + nameMap.set("loadstart", "Load Start"); + nameMap.set("mousedown", "Mouse Down"); + nameMap.set("mouseenter", "Mouse Enter"); + nameMap.set("mouseleave", "Mouse Leave"); + nameMap.set("mousemove", "Mouse Move"); + nameMap.set("mouseout", "Mouse Out"); + nameMap.set("mouseover", "Mouse Over"); + nameMap.set("mouseup", "Mouse Up"); + nameMap.set("mousewheel", "Mouse Wheel"); + nameMap.set("negotiationneeded", "Negotiation Needed"); + nameMap.set("nomatch", "No Match"); + nameMap.set("noupdate", "No Update"); + nameMap.set("orientationchange", "Orientation Change"); + nameMap.set("overflowchanged", "Overflow Changed"); + nameMap.set("pagehide", "Page Hide"); + nameMap.set("pageshow", "Page Show"); + nameMap.set("popstate", "Pop State"); + nameMap.set("ratechange", "Rate Change"); + nameMap.set("readystatechange", "Ready State Change"); + nameMap.set("removesourcebuffer", "Remove Source Buffer"); + nameMap.set("removestream", "Remove Stream"); + nameMap.set("removetrack", "Remove Track"); + nameMap.set("resize", "Resize"); + nameMap.set("securitypolicyviolation", "Security Policy Violation"); + nameMap.set("selectionchange", "Selection Change"); + nameMap.set("selectstart", "Select Start"); + nameMap.set("signalingstatechange", "Signaling State Change"); + nameMap.set("soundend", "Sound End"); + nameMap.set("soundstart", "Sound Start"); + nameMap.set("sourceclose", "Source Close"); + nameMap.set("sourceended", "Source Ended"); + nameMap.set("sourceopen", "Source Open"); + nameMap.set("speechend", "Speech End"); + nameMap.set("speechstart", "Speech Start"); + nameMap.set("textInput", "Text Input"); + nameMap.set("timeupdate", "Time Update"); + nameMap.set("tonechange", "Tone Change"); + nameMap.set("touchcancel", "Touch Cancel"); + nameMap.set("touchend", "Touch End"); + nameMap.set("touchmove", "Touch Move"); + nameMap.set("touchstart", "Touch Start"); + nameMap.set("transitionend", "Transition End"); + nameMap.set("updateend", "Update End"); + nameMap.set("updateready", "Update Ready"); + nameMap.set("updatestart", "Update Start"); + nameMap.set("upgradeneeded", "Upgrade Needed"); + nameMap.set("versionchange", "Version Change"); + nameMap.set("visibilitychange", "Visibility Change"); + nameMap.set("volumechange", "Volume Change"); + nameMap.set("webglcontextcreationerror", "WebGL Context Creation Error"); + nameMap.set("webglcontextlost", "WebGL Context Lost"); + nameMap.set("webglcontextrestored", "WebGL Context Restored"); + nameMap.set("webkitAnimationEnd", "Animation End"); + nameMap.set("webkitAnimationIteration", "Animation Iteration"); + nameMap.set("webkitAnimationStart", "Animation Start"); + nameMap.set("webkitBeforeTextInserted", "Before Text Inserted"); + nameMap.set("webkitEditableContentChanged", "Editable Content Changed"); + nameMap.set("webkitTransitionEnd", "Transition End"); + nameMap.set("webkitaddsourcebuffer", "Add Source Buffer"); + nameMap.set("webkitbeginfullscreen", "Begin Fullscreen"); + nameMap.set("webkitcurrentplaybacktargetiswirelesschanged", "Current Playback Target Is Wireless Changed"); + nameMap.set("webkitdeviceproximity", "Device Proximity"); + nameMap.set("webkitendfullscreen", "End Fullscreen"); + nameMap.set("webkitfullscreenchange", "Fullscreen Change"); + nameMap.set("webkitfullscreenerror", "Fullscreen Error"); + nameMap.set("webkitkeyadded", "Key Added"); + nameMap.set("webkitkeyerror", "Key Error"); + nameMap.set("webkitkeymessage", "Key Message"); + nameMap.set("webkitneedkey", "Need Key"); + nameMap.set("webkitnetworkinfochange", "Network Info Change"); + nameMap.set("webkitplaybacktargetavailabilitychanged", "Playback Target Availability Changed"); + nameMap.set("webkitpointerlockchange", "Pointer Lock Change"); + nameMap.set("webkitpointerlockerror", "Pointer Lock Error"); + nameMap.set("webkitregionlayoutupdate", "Region Layout Update"); // COMPATIBILITY (iOS 7): regionLayoutUpdated was removed and replaced by regionOversetChanged. + nameMap.set("webkitregionoversetchange", "Region Overset Change"); + nameMap.set("webkitremovesourcebuffer", "Remove Source Buffer"); + nameMap.set("webkitresourcetimingbufferfull", "Resource Timing Buffer Full"); + nameMap.set("webkitsourceclose", "Source Close"); + nameMap.set("webkitsourceended", "Source Ended"); + nameMap.set("webkitsourceopen", "Source Open"); + nameMap.set("webkitspeechchange", "Speech Change"); + nameMap.set("writeend", "Write End"); + nameMap.set("writestart", "Write Start"); + + WebInspector.ScriptTimelineRecord._eventDisplayNames = nameMap; + } + + switch (eventType) { + case WebInspector.ScriptTimelineRecord.EventType.ScriptEvaluated: + case WebInspector.ScriptTimelineRecord.EventType.APIScriptEvaluated: + return WebInspector.UIString("Script Evaluated"); + case WebInspector.ScriptTimelineRecord.EventType.MicrotaskDispatched: + return WebInspector.UIString("Microtask Dispatched"); + case WebInspector.ScriptTimelineRecord.EventType.EventDispatched: + if (details && (details instanceof String || typeof details === "string")) { + var eventDisplayName = WebInspector.ScriptTimelineRecord._eventDisplayNames.get(details) || details.capitalize(); + return WebInspector.UIString("%s Event Dispatched").format(eventDisplayName); + } + return WebInspector.UIString("Event Dispatched"); + case WebInspector.ScriptTimelineRecord.EventType.ProbeSampleRecorded: + return WebInspector.UIString("Probe Sample Recorded"); + case WebInspector.ScriptTimelineRecord.EventType.ConsoleProfileRecorded: + if (details && (details instanceof String || typeof details === "string")) + return WebInspector.UIString("“%s” Profile Recorded").format(details); + return WebInspector.UIString("Console Profile Recorded"); + case WebInspector.ScriptTimelineRecord.EventType.GarbageCollected: + console.assert(details); + if (details && (details instanceof WebInspector.GarbageCollection) && includeDetailsInMainTitle) { + switch (details.type) { + case WebInspector.GarbageCollection.Type.Partial: + return WebInspector.UIString("Partial Garbage Collection"); + case WebInspector.GarbageCollection.Type.Full: + return WebInspector.UIString("Full Garbage Collection"); + } + } + return WebInspector.UIString("Garbage Collection"); + case WebInspector.ScriptTimelineRecord.EventType.TimerFired: + if (details && includeDetailsInMainTitle) + return WebInspector.UIString("Timer %s Fired").format(details); + return WebInspector.UIString("Timer Fired"); + case WebInspector.ScriptTimelineRecord.EventType.TimerInstalled: + if (details && includeDetailsInMainTitle) + return WebInspector.UIString("Timer %s Installed").format(details.timerId); + return WebInspector.UIString("Timer Installed"); + case WebInspector.ScriptTimelineRecord.EventType.TimerRemoved: + if (details && includeDetailsInMainTitle) + return WebInspector.UIString("Timer %s Removed").format(details); + return WebInspector.UIString("Timer Removed"); + case WebInspector.ScriptTimelineRecord.EventType.AnimationFrameFired: + if (details && includeDetailsInMainTitle) + return WebInspector.UIString("Animation Frame %s Fired").format(details); + return WebInspector.UIString("Animation Frame Fired"); + case WebInspector.ScriptTimelineRecord.EventType.AnimationFrameRequested: + if (details && includeDetailsInMainTitle) + return WebInspector.UIString("Animation Frame %s Requested").format(details); + return WebInspector.UIString("Animation Frame Requested"); + case WebInspector.ScriptTimelineRecord.EventType.AnimationFrameCanceled: + if (details && includeDetailsInMainTitle) + return WebInspector.UIString("Animation Frame %s Canceled").format(details); + return WebInspector.UIString("Animation Frame Canceled"); + } +}; + +WebInspector.ScriptTimelineRecord.TypeIdentifier = "script-timeline-record"; +WebInspector.ScriptTimelineRecord.EventTypeCookieKey = "script-timeline-record-event-type"; +WebInspector.ScriptTimelineRecord.DetailsCookieKey = "script-timeline-record-details"; diff --git a/Source/WebInspectorUI/UserInterface/Models/SourceCode.js b/Source/WebInspectorUI/UserInterface/Models/SourceCode.js new file mode 100644 index 000000000..29e8f3231 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/SourceCode.js @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2013 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. + */ + +WebInspector.SourceCode = class SourceCode extends WebInspector.Object +{ + constructor() + { + super(); + + this._originalRevision = new WebInspector.SourceCodeRevision(this, null, false); + this._currentRevision = this._originalRevision; + + this._sourceMaps = null; + this._formatterSourceMap = null; + this._requestContentPromise = null; + } + + // Public + + get displayName() + { + // Implemented by subclasses. + console.error("Needs to be implemented by a subclass."); + return ""; + } + + get originalRevision() + { + return this._originalRevision; + } + + get currentRevision() + { + return this._currentRevision; + } + + set currentRevision(revision) + { + console.assert(revision instanceof WebInspector.SourceCodeRevision); + if (!(revision instanceof WebInspector.SourceCodeRevision)) + return; + + console.assert(revision.sourceCode === this); + if (revision.sourceCode !== this) + return; + + this._currentRevision = revision; + + this.dispatchEventToListeners(WebInspector.SourceCode.Event.ContentDidChange); + } + + get content() + { + return this._currentRevision.content; + } + + get url() + { + // To be overridden by subclasses. + } + + get contentIdentifier() + { + // A contentIdentifier is roughly `url || sourceURL` for cases where + // the content is consistent between sessions and not ephemeral. + + // Can be overridden by subclasses if better behavior is possible. + return this.url; + } + + get sourceMaps() + { + return this._sourceMaps || []; + } + + addSourceMap(sourceMap) + { + console.assert(sourceMap instanceof WebInspector.SourceMap); + + if (!this._sourceMaps) + this._sourceMaps = []; + + this._sourceMaps.push(sourceMap); + + this.dispatchEventToListeners(WebInspector.SourceCode.Event.SourceMapAdded); + } + + get formatterSourceMap() + { + return this._formatterSourceMap; + } + + set formatterSourceMap(formatterSourceMap) + { + console.assert(this._formatterSourceMap === null || formatterSourceMap === null); + console.assert(formatterSourceMap === null || formatterSourceMap instanceof WebInspector.FormatterSourceMap); + + this._formatterSourceMap = formatterSourceMap; + + this.dispatchEventToListeners(WebInspector.SourceCode.Event.FormatterDidChange); + } + + requestContent() + { + this._requestContentPromise = this._requestContentPromise || this.requestContentFromBackend().then(this._processContent.bind(this)); + + return this._requestContentPromise; + } + + createSourceCodeLocation(lineNumber, columnNumber) + { + return new WebInspector.SourceCodeLocation(this, lineNumber, columnNumber); + } + + createLazySourceCodeLocation(lineNumber, columnNumber) + { + return new WebInspector.LazySourceCodeLocation(this, lineNumber, columnNumber); + } + + createSourceCodeTextRange(textRange) + { + return new WebInspector.SourceCodeTextRange(this, textRange); + } + + // Protected + + revisionContentDidChange(revision) + { + if (this._ignoreRevisionContentDidChangeEvent) + return; + + if (revision !== this._currentRevision) + return; + + this.handleCurrentRevisionContentChange(); + + this.dispatchEventToListeners(WebInspector.SourceCode.Event.ContentDidChange); + } + + handleCurrentRevisionContentChange() + { + // Implemented by subclasses if needed. + } + + get revisionForRequestedContent() + { + // Implemented by subclasses if needed. + return this._originalRevision; + } + + markContentAsStale() + { + this._requestContentPromise = null; + this._contentReceived = false; + } + + requestContentFromBackend() + { + // Implemented by subclasses. + console.error("Needs to be implemented by a subclass."); + return Promise.reject(new Error("Needs to be implemented by a subclass.")); + } + + get mimeType() + { + // Implemented by subclasses. + console.error("Needs to be implemented by a subclass."); + } + + // Private + + _processContent(parameters) + { + // Different backend APIs return one of `content, `body`, `text`, or `scriptSource`. + var content = parameters.content || parameters.body || parameters.text || parameters.scriptSource; + var error = parameters.error; + if (parameters.base64Encoded) + content = decodeBase64ToBlob(content, this.mimeType); + + var revision = this.revisionForRequestedContent; + + this._ignoreRevisionContentDidChangeEvent = true; + revision.content = content || null; + this._ignoreRevisionContentDidChangeEvent = false; + + // FIXME: Returning the content in this promise is misleading. It may not be current content + // now, and it may become out-dated later on. We should drop content from this promise + // and require clients to ask for the current contents from the sourceCode in the result. + + return Promise.resolve({ + error, + sourceCode: this, + content, + }); + } +}; + +WebInspector.SourceCode.Event = { + ContentDidChange: "source-code-content-did-change", + SourceMapAdded: "source-code-source-map-added", + FormatterDidChange: "source-code-formatter-did-change", + LoadingDidFinish: "source-code-loading-did-finish", + LoadingDidFail: "source-code-loading-did-fail" +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/SourceCodeLocation.js b/Source/WebInspectorUI/UserInterface/Models/SourceCodeLocation.js new file mode 100644 index 000000000..79691700c --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/SourceCodeLocation.js @@ -0,0 +1,464 @@ +/* + * Copyright (C) 2013 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. + */ + +WebInspector.SourceCodeLocation = class SourceCodeLocation extends WebInspector.Object +{ + constructor(sourceCode, lineNumber, columnNumber) + { + super(); + + console.assert(sourceCode === null || sourceCode instanceof WebInspector.SourceCode); + console.assert(!(sourceCode instanceof WebInspector.SourceMapResource)); + console.assert(typeof lineNumber === "number" && !isNaN(lineNumber) && lineNumber >= 0); + console.assert(typeof columnNumber === "number" && !isNaN(columnNumber) && columnNumber >= 0); + + this._sourceCode = sourceCode || null; + this._lineNumber = lineNumber; + this._columnNumber = columnNumber; + this._resolveFormattedLocation(); + + if (this._sourceCode) { + this._sourceCode.addEventListener(WebInspector.SourceCode.Event.SourceMapAdded, this._sourceCodeSourceMapAdded, this); + this._sourceCode.addEventListener(WebInspector.SourceCode.Event.FormatterDidChange, this._sourceCodeFormatterDidChange, this); + } + + this._resetMappedLocation(); + } + + // Public + + isEqual(other) + { + if (!other) + return false; + return this._sourceCode === other._sourceCode && this._lineNumber === other._lineNumber && this._columnNumber === other._columnNumber; + } + + get sourceCode() + { + return this._sourceCode; + } + + set sourceCode(sourceCode) + { + this.setSourceCode(sourceCode); + } + + // Raw line and column in the original source code. + + get lineNumber() + { + return this._lineNumber; + } + + get columnNumber() + { + return this._columnNumber; + } + + position() + { + return new WebInspector.SourceCodePosition(this.lineNumber, this.columnNumber); + } + + // Formatted line and column if the original source code is pretty printed. + // This is the same as the raw location if there is no formatter. + + get formattedLineNumber() + { + return this._formattedLineNumber; + } + + get formattedColumnNumber() + { + return this._formattedColumnNumber; + } + + formattedPosition() + { + return new WebInspector.SourceCodePosition(this.formattedLineNumber, this.formattedColumnNumber); + } + + // Display line and column: + // - Mapped line and column if the original source code has a source map. + // - Otherwise this is the formatted / raw line and column. + + get displaySourceCode() + { + this.resolveMappedLocation(); + return this._mappedResource || this._sourceCode; + } + + get displayLineNumber() + { + this.resolveMappedLocation(); + return isNaN(this._mappedLineNumber) ? this._formattedLineNumber : this._mappedLineNumber; + } + + get displayColumnNumber() + { + this.resolveMappedLocation(); + return isNaN(this._mappedColumnNumber) ? this._formattedColumnNumber : this._mappedColumnNumber; + } + + displayPosition() + { + return new WebInspector.SourceCodePosition(this.displayLineNumber, this.displayColumnNumber); + } + + // User presentable location strings: "file:lineNumber:columnNumber". + + originalLocationString(columnStyle, nameStyle, prefix) + { + return this._locationString(this.sourceCode, this.lineNumber, this.columnNumber, columnStyle, nameStyle, prefix); + } + + formattedLocationString(columnStyle, nameStyle, prefix) + { + return this._locationString(this.sourceCode, this.formattedLineNumber, this.formattedColumn, columnStyle, nameStyle, prefix); + } + + displayLocationString(columnStyle, nameStyle, prefix) + { + return this._locationString(this.displaySourceCode, this.displayLineNumber, this.displayColumnNumber, columnStyle, nameStyle, prefix); + } + + tooltipString() + { + if (!this.hasDifferentDisplayLocation()) + return this.originalLocationString(WebInspector.SourceCodeLocation.ColumnStyle.Shown, WebInspector.SourceCodeLocation.NameStyle.Full); + + var tooltip = WebInspector.UIString("Located at %s").format(this.displayLocationString(WebInspector.SourceCodeLocation.ColumnStyle.Shown, WebInspector.SourceCodeLocation.NameStyle.Full)); + tooltip += "\n" + WebInspector.UIString("Originally %s").format(this.originalLocationString(WebInspector.SourceCodeLocation.ColumnStyle.Shown, WebInspector.SourceCodeLocation.NameStyle.Full)); + return tooltip; + } + + hasMappedLocation() + { + this.resolveMappedLocation(); + return this._mappedResource !== null; + } + + hasFormattedLocation() + { + return this._formattedLineNumber !== this._lineNumber || this._formattedColumnNumber !== this._columnNumber; + } + + hasDifferentDisplayLocation() + { + return this.hasMappedLocation() || this.hasFormattedLocation(); + } + + update(sourceCode, lineNumber, columnNumber) + { + console.assert(sourceCode === this._sourceCode || (this._mappedResource && sourceCode === this._mappedResource)); + console.assert(typeof lineNumber === "number" && !isNaN(lineNumber) && lineNumber >= 0); + console.assert(typeof columnNumber === "number" && !isNaN(columnNumber) && columnNumber >= 0); + + if (sourceCode === this._sourceCode && lineNumber === this._lineNumber && columnNumber === this._columnNumber) + return; + if (this._mappedResource && sourceCode === this._mappedResource && lineNumber === this._mappedLineNumber && columnNumber === this._mappedColumnNumber) + return; + + var newSourceCodeLocation = sourceCode.createSourceCodeLocation(lineNumber, columnNumber); + console.assert(newSourceCodeLocation.sourceCode === this._sourceCode); + + this._makeChangeAndDispatchChangeEventIfNeeded(function() { + this._lineNumber = newSourceCodeLocation._lineNumber; + this._columnNumber = newSourceCodeLocation._columnNumber; + if (newSourceCodeLocation._mappedLocationIsResolved) { + this._mappedLocationIsResolved = true; + this._mappedResource = newSourceCodeLocation._mappedResource; + this._mappedLineNumber = newSourceCodeLocation._mappedLineNumber; + this._mappedColumnNumber = newSourceCodeLocation._mappedColumnNumber; + } + }); + } + + populateLiveDisplayLocationTooltip(element, prefix) + { + prefix = prefix || ""; + + element.title = prefix + this.tooltipString(); + + this.addEventListener(WebInspector.SourceCodeLocation.Event.DisplayLocationChanged, function(event) { + if (this.sourceCode) + element.title = prefix + this.tooltipString(); + }, this); + } + + populateLiveDisplayLocationString(element, propertyName, columnStyle, nameStyle, prefix) + { + var currentDisplay; + + function updateDisplayString(showAlternativeLocation, forceUpdate) + { + if (!forceUpdate && currentDisplay === showAlternativeLocation) + return; + + currentDisplay = showAlternativeLocation; + + if (!showAlternativeLocation) { + element[propertyName] = this.displayLocationString(columnStyle, nameStyle, prefix); + element.classList.toggle(WebInspector.SourceCodeLocation.DisplayLocationClassName, this.hasDifferentDisplayLocation()); + } else if (this.hasDifferentDisplayLocation()) { + element[propertyName] = this.originalLocationString(columnStyle, nameStyle, prefix); + element.classList.remove(WebInspector.SourceCodeLocation.DisplayLocationClassName); + } + } + + function mouseOverOrMove(event) + { + updateDisplayString.call(this, event.metaKey && !event.altKey && !event.shiftKey); + } + + updateDisplayString.call(this, false); + + this.addEventListener(WebInspector.SourceCodeLocation.Event.DisplayLocationChanged, function(event) { + if (this.sourceCode) + updateDisplayString.call(this, currentDisplay, true); + }, this); + + var boundMouseOverOrMove = mouseOverOrMove.bind(this); + element.addEventListener("mouseover", boundMouseOverOrMove); + element.addEventListener("mousemove", boundMouseOverOrMove); + element.addEventListener("mouseout", (event) => { updateDisplayString.call(this, false); }); + } + + // Protected + + setSourceCode(sourceCode) + { + console.assert((this._sourceCode === null && sourceCode instanceof WebInspector.SourceCode) || (this._sourceCode instanceof WebInspector.SourceCode && sourceCode === null)); + + if (sourceCode === this._sourceCode) + return; + + this._makeChangeAndDispatchChangeEventIfNeeded(function() { + if (this._sourceCode) { + this._sourceCode.removeEventListener(WebInspector.SourceCode.Event.SourceMapAdded, this._sourceCodeSourceMapAdded, this); + this._sourceCode.removeEventListener(WebInspector.SourceCode.Event.FormatterDidChange, this._sourceCodeFormatterDidChange, this); + } + + this._sourceCode = sourceCode; + + if (this._sourceCode) { + this._sourceCode.addEventListener(WebInspector.SourceCode.Event.SourceMapAdded, this._sourceCodeSourceMapAdded, this); + this._sourceCode.addEventListener(WebInspector.SourceCode.Event.FormatterDidChange, this._sourceCodeFormatterDidChange, this); + } + }); + } + + resolveMappedLocation() + { + if (this._mappedLocationIsResolved) + return; + + console.assert(this._mappedResource === null); + console.assert(isNaN(this._mappedLineNumber)); + console.assert(isNaN(this._mappedColumnNumber)); + + this._mappedLocationIsResolved = true; + + if (!this._sourceCode) + return; + + var sourceMaps = this._sourceCode.sourceMaps; + if (!sourceMaps.length) + return; + + for (var i = 0; i < sourceMaps.length; ++i) { + var sourceMap = sourceMaps[i]; + var entry = sourceMap.findEntry(this._lineNumber, this._columnNumber); + if (!entry || entry.length === 2) + continue; + console.assert(entry.length === 5); + var url = entry[2]; + var sourceMapResource = sourceMap.resourceForURL(url); + if (!sourceMapResource) + return; + this._mappedResource = sourceMapResource; + this._mappedLineNumber = entry[3]; + this._mappedColumnNumber = entry[4]; + return; + } + } + + // Private + + _locationString(sourceCode, lineNumber, columnNumber, columnStyle, nameStyle, prefix) + { + console.assert(sourceCode); + if (!sourceCode) + return ""; + + columnStyle = columnStyle || WebInspector.SourceCodeLocation.ColumnStyle.OnlyIfLarge; + nameStyle = nameStyle || WebInspector.SourceCodeLocation.NameStyle.Short; + prefix = prefix || ""; + + let lineString = lineNumber + 1; // The user visible line number is 1-based. + if (columnStyle === WebInspector.SourceCodeLocation.ColumnStyle.Shown && columnNumber > 0) + lineString += ":" + (columnNumber + 1); // The user visible column number is 1-based. + else if (columnStyle === WebInspector.SourceCodeLocation.ColumnStyle.OnlyIfLarge && columnNumber > WebInspector.SourceCodeLocation.LargeColumnNumber) + lineString += ":" + (columnNumber + 1); // The user visible column number is 1-based. + else if (columnStyle === WebInspector.SourceCodeLocation.ColumnStyle.Hidden) + lineString = ""; + + switch (nameStyle) { + case WebInspector.SourceCodeLocation.NameStyle.None: + return prefix + lineString; + + case WebInspector.SourceCodeLocation.NameStyle.Short: + case WebInspector.SourceCodeLocation.NameStyle.Full: + var displayURL = sourceCode.displayURL; + var name = nameStyle === WebInspector.SourceCodeLocation.NameStyle.Full && displayURL ? displayURL : sourceCode.displayName; + if (columnStyle === WebInspector.SourceCodeLocation.ColumnStyle.Hidden) + return prefix + name; + var lineSuffix = displayURL ? ":" + lineString : WebInspector.UIString(" (line %s)").format(lineString); + return prefix + name + lineSuffix; + + default: + console.error("Unknown nameStyle: " + nameStyle); + return prefix + lineString; + } + } + + _resetMappedLocation() + { + this._mappedLocationIsResolved = false; + this._mappedResource = null; + this._mappedLineNumber = NaN; + this._mappedColumnNumber = NaN; + } + + _setMappedLocation(mappedResource, mappedLineNumber, mappedColumnNumber) + { + // Called by SourceMapResource when it creates a SourceCodeLocation and already knows the resolved location. + this._mappedLocationIsResolved = true; + this._mappedResource = mappedResource; + this._mappedLineNumber = mappedLineNumber; + this._mappedColumnNumber = mappedColumnNumber; + } + + _resolveFormattedLocation() + { + if (this._sourceCode && this._sourceCode.formatterSourceMap) { + var formattedLocation = this._sourceCode.formatterSourceMap.originalToFormatted(this._lineNumber, this._columnNumber); + this._formattedLineNumber = formattedLocation.lineNumber; + this._formattedColumnNumber = formattedLocation.columnNumber; + } else { + this._formattedLineNumber = this._lineNumber; + this._formattedColumnNumber = this._columnNumber; + } + } + + _makeChangeAndDispatchChangeEventIfNeeded(changeFunction) + { + var oldSourceCode = this._sourceCode; + var oldLineNumber = this._lineNumber; + var oldColumnNumber = this._columnNumber; + + var oldFormattedLineNumber = this._formattedLineNumber; + var oldFormattedColumnNumber = this._formattedColumnNumber; + + var oldDisplaySourceCode = this.displaySourceCode; + var oldDisplayLineNumber = this.displayLineNumber; + var oldDisplayColumnNumber = this.displayColumnNumber; + + this._resetMappedLocation(); + + if (changeFunction) + changeFunction.call(this); + + this.resolveMappedLocation(); + this._resolveFormattedLocation(); + + // If the display source code is non-null then the addresses are not NaN and can be compared. + var displayLocationChanged = false; + var newDisplaySourceCode = this.displaySourceCode; + if (oldDisplaySourceCode !== newDisplaySourceCode) + displayLocationChanged = true; + else if (newDisplaySourceCode && (oldDisplayLineNumber !== this.displayLineNumber || oldDisplayColumnNumber !== this.displayColumnNumber)) + displayLocationChanged = true; + + var anyLocationChanged = false; + if (displayLocationChanged) + anyLocationChanged = true; + else if (oldSourceCode !== this._sourceCode) + anyLocationChanged = true; + else if (this._sourceCode && (oldLineNumber !== this._lineNumber || oldColumnNumber !== this._columnNumber)) + anyLocationChanged = true; + else if (this._sourceCode && (oldFormattedLineNumber !== this._formattedLineNumber || oldFormattedColumnNumber !== this._formattedColumnNumber)) + anyLocationChanged = true; + + if (displayLocationChanged || anyLocationChanged) { + var oldData = { + oldSourceCode, + oldLineNumber, + oldColumnNumber, + oldFormattedLineNumber, + oldFormattedColumnNumber, + oldDisplaySourceCode, + oldDisplayLineNumber, + oldDisplayColumnNumber + }; + if (displayLocationChanged) + this.dispatchEventToListeners(WebInspector.SourceCodeLocation.Event.DisplayLocationChanged, oldData); + if (anyLocationChanged) + this.dispatchEventToListeners(WebInspector.SourceCodeLocation.Event.LocationChanged, oldData); + } + } + + _sourceCodeSourceMapAdded() + { + this._makeChangeAndDispatchChangeEventIfNeeded(null); + } + + _sourceCodeFormatterDidChange() + { + this._makeChangeAndDispatchChangeEventIfNeeded(null); + } +}; + +WebInspector.SourceCodeLocation.DisplayLocationClassName = "display-location"; + +WebInspector.SourceCodeLocation.LargeColumnNumber = 80; + +WebInspector.SourceCodeLocation.NameStyle = { + None: "none", // File name not included. + Short: "short", // Only the file name. + Full: "full" // Full URL is used. +}; + +WebInspector.SourceCodeLocation.ColumnStyle = { + Hidden: "hidden", // line and column numbers are not included. + OnlyIfLarge: "only-if-large", // column numbers greater than 80 are shown. + Shown: "shown" // non-zero column numbers are shown. +}; + +WebInspector.SourceCodeLocation.Event = { + LocationChanged: "source-code-location-location-changed", + DisplayLocationChanged: "source-code-location-display-location-changed" +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/SourceCodePosition.js b/Source/WebInspectorUI/UserInterface/Models/SourceCodePosition.js new file mode 100644 index 000000000..a89554ac2 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/SourceCodePosition.js @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2013 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. + */ + +WebInspector.SourceCodePosition = class SourceCodePosition extends WebInspector.Object +{ + constructor(lineNumber, columNumber) + { + super(); + + this._lineNumber = lineNumber || 0; + this._columnNumber = columNumber || 0; + } + + // Public + + get lineNumber() { return this._lineNumber; } + get columnNumber() { return this._columnNumber; } +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/SourceCodeRevision.js b/Source/WebInspectorUI/UserInterface/Models/SourceCodeRevision.js new file mode 100644 index 000000000..b955493ad --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/SourceCodeRevision.js @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2013 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. + */ + +WebInspector.SourceCodeRevision = class SourceCodeRevision extends WebInspector.Revision +{ + constructor(sourceCode, content) + { + super(); + + console.assert(sourceCode instanceof WebInspector.SourceCode); + + this._sourceCode = sourceCode; + this._content = content || ""; + } + + // Public + + get sourceCode() + { + return this._sourceCode; + } + + get content() + { + return this._content; + } + + set content(content) + { + content = content || ""; + + if (this._content === content) + return; + + this._content = content; + + this._sourceCode.revisionContentDidChange(this); + } + + apply() + { + this._sourceCode.currentRevision = this; + } + + revert() + { + this._sourceCode.currentRevision = this._sourceCode.originalRevision; + } + + copy() + { + return new WebInspector.SourceCodeRevision(this._sourceCode, this._content); + } +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/SourceCodeSearchMatchObject.js b/Source/WebInspectorUI/UserInterface/Models/SourceCodeSearchMatchObject.js new file mode 100644 index 000000000..7bab2d6ec --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/SourceCodeSearchMatchObject.js @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2013 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. + */ + +WebInspector.SourceCodeSearchMatchObject = class SourceCodeSearchMatchObject extends WebInspector.Object +{ + constructor(sourceCode, lineText, searchTerm, textRange) + { + super(); + + console.assert(sourceCode instanceof WebInspector.SourceCode); + + this._sourceCode = sourceCode; + this._lineText = lineText; + this._searchTerm = searchTerm; + this._sourceCodeTextRange = sourceCode.createSourceCodeTextRange(textRange); + } + + // Public + + get sourceCode() { return this._sourceCode; } + get title() { return this._lineText; } + get searchTerm() { return this._searchTerm; } + get sourceCodeTextRange() { return this._sourceCodeTextRange; } + + get className() + { + return WebInspector.SourceCodeSearchMatchObject.SourceCodeMatchIconStyleClassName; + } + + saveIdentityToCookie(cookie) + { + if (this._sourceCode.url) + cookie[WebInspector.SourceCodeSearchMatchObject.URLCookieKey] = this._sourceCode.url.hash; + + var textRange = this._sourceCodeTextRange.textRange; + cookie[WebInspector.SourceCodeSearchMatchObject.TextRangeKey] = [textRange.startLine, textRange.startColumn, textRange.endLine, textRange.endColumn].join(); + } +}; + +WebInspector.SourceCodeSearchMatchObject.SourceCodeMatchIconStyleClassName = "source-code-match-icon"; + +WebInspector.SourceCodeSearchMatchObject.TypeIdentifier = "source-code-search-match-object"; +WebInspector.SourceCodeSearchMatchObject.URLCookieKey = "source-code-url"; +WebInspector.SourceCodeSearchMatchObject.TextRangeKey = "text-range"; diff --git a/Source/WebInspectorUI/UserInterface/Models/SourceCodeTextRange.js b/Source/WebInspectorUI/UserInterface/Models/SourceCodeTextRange.js new file mode 100644 index 000000000..216743249 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/SourceCodeTextRange.js @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2013 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. + */ + +WebInspector.SourceCodeTextRange = class SourceCodeTextRange extends WebInspector.Object +{ + constructor(sourceCode) /* textRange || startLocation, endLocation */ + { + super(); + + console.assert(sourceCode instanceof WebInspector.SourceCode); + console.assert(arguments.length === 2 || arguments.length === 3); + + this._sourceCode = sourceCode; + + if (arguments.length === 2) { + var textRange = arguments[1]; + console.assert(textRange instanceof WebInspector.TextRange); + this._startLocation = sourceCode.createSourceCodeLocation(textRange.startLine, textRange.startColumn); + this._endLocation = sourceCode.createSourceCodeLocation(textRange.endLine, textRange.endColumn); + } else { + console.assert(arguments[1] instanceof WebInspector.SourceCodeLocation); + console.assert(arguments[2] instanceof WebInspector.SourceCodeLocation); + this._startLocation = arguments[1]; + this._endLocation = arguments[2]; + } + + this._startLocation.addEventListener(WebInspector.SourceCodeLocation.Event.LocationChanged, this._sourceCodeLocationChanged, this); + this._endLocation.addEventListener(WebInspector.SourceCodeLocation.Event.LocationChanged, this._sourceCodeLocationChanged, this); + } + + // Public + + get sourceCode() + { + return this._sourceCode; + } + + // Raw text range in the original source code. + + get textRange() + { + var startLine = this._startLocation.lineNumber; + var startColumn = this._startLocation.columnNumber; + var endLine = this._endLocation.lineNumber; + var endColumn = this._endLocation.columnNumber; + return new WebInspector.TextRange(startLine, startColumn, endLine, endColumn); + } + + // Formatted text range in the original source code if it is pretty printed. + // This is the same as the raw text range if the source code has no formatter. + + get formattedTextRange() + { + var startLine = this._startLocation.formattedLineNumber; + var startColumn = this._startLocation.formattedColumnNumber; + var endLine = this._endLocation.formattedLineNumber; + var endColumn = this._endLocation.formattedColumnNumber; + return new WebInspector.TextRange(startLine, startColumn, endLine, endColumn); + } + + // Display values: + // - Mapped resource and text range locations if the original source code has a + // source map and both start and end locations are in the same mapped resource. + // - Otherwise this is the formatted / raw text range. + + get displaySourceCode() + { + if (!this._startAndEndLocationsInSameMappedResource()) + return this._sourceCode; + + return this._startLocation.displaySourceCode; + } + + get displayTextRange() + { + if (!this._startAndEndLocationsInSameMappedResource()) + return this.formattedTextRange; + + var startLine = this._startLocation.displayLineNumber; + var startColumn = this._startLocation.displayColumnNumber; + var endLine = this._endLocation.displayLineNumber; + var endColumn = this._endLocation.displayColumnNumber; + return new WebInspector.TextRange(startLine, startColumn, endLine, endColumn); + } + + // Private + + _startAndEndLocationsInSameMappedResource() + { + return this._startLocation.hasMappedLocation() && this._endLocation.hasMappedLocation() && this._startLocation.displaySourceCode === this._endLocation.displaySourceCode; + } + + _sourceCodeLocationChanged(event) + { + this.dispatchEventToListeners(WebInspector.SourceCodeLocation.Event.RangeChanged); + } +}; + +WebInspector.SourceCodeTextRange.Event = { + RangeChanged: "source-code-text-range-range-changed" +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/SourceCodeTimeline.js b/Source/WebInspectorUI/UserInterface/Models/SourceCodeTimeline.js new file mode 100644 index 000000000..f1699c4df --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/SourceCodeTimeline.js @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2013 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. + */ + +WebInspector.SourceCodeTimeline = class SourceCodeTimeline extends WebInspector.Timeline +{ + constructor(sourceCode, sourceCodeLocation, recordType, recordEventType) + { + super(); + + console.assert(sourceCode); + console.assert(!sourceCodeLocation || sourceCodeLocation.sourceCode === sourceCode); + console.assert(recordType); + + this._sourceCode = sourceCode; + this._sourceCodeLocation = sourceCodeLocation || null; + this._recordType = recordType; + this._recordEventType = recordEventType || null; + } + + // Public + + get sourceCode() { return this._sourceCode; } + get sourceCodeLocation() { return this._sourceCodeLocation; } + get recordType() { return this._recordType; } + get recordEventType() { return this._recordEventType; } + + saveIdentityToCookie(cookie) + { + cookie[WebInspector.SourceCodeTimeline.SourceCodeURLCookieKey] = this._sourceCode.url ? this._sourceCode.url.hash : null; + cookie[WebInspector.SourceCodeTimeline.SourceCodeLocationLineCookieKey] = this._sourceCodeLocation ? this._sourceCodeLocation.lineNumber : null; + cookie[WebInspector.SourceCodeTimeline.SourceCodeLocationColumnCookieKey] = this._sourceCodeLocation ? this._sourceCodeLocation.columnNumber : null; + cookie[WebInspector.SourceCodeTimeline.RecordTypeCookieKey] = this._recordType || null; + cookie[WebInspector.SourceCodeTimeline.RecordEventTypeCookieKey] = this._recordEventType || null; + } +}; + +WebInspector.SourceCodeTimeline.TypeIdentifier = "source-code-timeline"; +WebInspector.SourceCodeTimeline.SourceCodeURLCookieKey = "source-code-timeline-source-code-url"; +WebInspector.SourceCodeTimeline.SourceCodeLocationLineCookieKey = "source-code-timeline-source-code-location-line"; +WebInspector.SourceCodeTimeline.SourceCodeLocationColumnCookieKey = "source-code-timeline-source-code-location-column"; +WebInspector.SourceCodeTimeline.SourceCodeURLCookieKey = "source-code-timeline-source-code-url"; +WebInspector.SourceCodeTimeline.RecordTypeCookieKey = "source-code-timeline-record-type"; +WebInspector.SourceCodeTimeline.RecordEventTypeCookieKey = "source-code-timeline-record-event-type"; diff --git a/Source/WebInspectorUI/UserInterface/Models/SourceMap.js b/Source/WebInspectorUI/UserInterface/Models/SourceMap.js new file mode 100644 index 000000000..1824421bc --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/SourceMap.js @@ -0,0 +1,286 @@ +/* + * Copyright (C) 2013 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. + */ + +WebInspector.SourceMap = class SourceMap extends WebInspector.Object +{ + constructor(sourceMappingURL, payload, originalSourceCode) + { + super(); + + if (!WebInspector.SourceMap._base64Map) { + var base64Digits = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + WebInspector.SourceMap._base64Map = {}; + for (var i = 0; i < base64Digits.length; ++i) + WebInspector.SourceMap._base64Map[base64Digits.charAt(i)] = i; + } + + this._originalSourceCode = originalSourceCode || null; + this._sourceMapResources = {}; + this._sourceMapResourcesList = []; + + this._sourceMappingURL = sourceMappingURL; + this._reverseMappingsBySourceURL = {}; + this._mappings = []; + this._sources = {}; + this._sourceRoot = null; + this._sourceContentByURL = {}; + this._parseMappingPayload(payload); + } + + // Public + + get originalSourceCode() + { + return this._originalSourceCode; + } + + get sourceMappingBasePathURLComponents() + { + if (this._sourceMappingURLBasePathComponents) + return this._sourceMappingURLBasePathComponents; + + if (this._sourceRoot) { + var baseURLPath = absoluteURL(this._sourceRoot, this._sourceMappingURL); + console.assert(baseURLPath); + if (baseURLPath) { + var urlComponents = parseURL(baseURLPath); + if (!/\/$/.test(urlComponents.path)) + urlComponents.path += "/"; + this._sourceMappingURLBasePathComponents = urlComponents; + return this._sourceMappingURLBasePathComponents; + } + } + + var urlComponents = parseURL(this._sourceMappingURL); + + // Fallback for JavaScript debuggable named scripts that may not have a complete URL. + if (!urlComponents.path) + urlComponents.path = this._sourceMappingURL || ""; + + urlComponents.path = urlComponents.path.substr(0, urlComponents.path.lastIndexOf(urlComponents.lastPathComponent)); + urlComponents.lastPathComponent = null; + this._sourceMappingURLBasePathComponents = urlComponents; + return this._sourceMappingURLBasePathComponents; + } + + get resources() + { + return this._sourceMapResourcesList; + } + + addResource(resource) + { + console.assert(!(resource.url in this._sourceMapResources)); + this._sourceMapResources[resource.url] = resource; + this._sourceMapResourcesList.push(resource); + } + + resourceForURL(url) + { + return this._sourceMapResources[url]; + } + + sources() + { + return Object.keys(this._sources); + } + + sourceContent(sourceURL) + { + return this._sourceContentByURL[sourceURL]; + } + + _parseMappingPayload(mappingPayload) + { + if (mappingPayload.sections) + this._parseSections(mappingPayload.sections); + else + this._parseMap(mappingPayload, 0, 0); + } + + _parseSections(sections) + { + for (var i = 0; i < sections.length; ++i) { + var section = sections[i]; + this._parseMap(section.map, section.offset.line, section.offset.column); + } + } + + findEntry(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; + } + } + var entry = this._mappings[first]; + if (!first && entry && (lineNumber < entry[0] || (lineNumber === entry[0] && columnNumber < entry[1]))) + return null; + return entry; + } + + findEntryReversed(sourceURL, lineNumber) + { + var mappings = this._reverseMappingsBySourceURL[sourceURL]; + for ( ; lineNumber < mappings.length; ++lineNumber) { + var mapping = mappings[lineNumber]; + if (mapping) + return mapping; + } + return this._mappings[0]; + } + + _parseMap(map, lineNumber, columnNumber) + { + var sourceIndex = 0; + var sourceLineNumber = 0; + var sourceColumnNumber = 0; + var nameIndex = 0; + + var sources = []; + var originalToCanonicalURLMap = {}; + for (var i = 0; i < map.sources.length; ++i) { + var originalSourceURL = map.sources[i]; + var href = originalSourceURL; + if (map.sourceRoot && href.charAt(0) !== "/") + href = map.sourceRoot.replace(/\/+$/, "") + "/" + href; + var url = absoluteURL(href, this._sourceMappingURL) || href; + originalToCanonicalURLMap[originalSourceURL] = url; + sources.push(url); + this._sources[url] = true; + + if (map.sourcesContent && map.sourcesContent[i]) + this._sourceContentByURL[url] = map.sourcesContent[i]; + } + + this._sourceRoot = map.sourceRoot || null; + + var stringCharIterator = new WebInspector.SourceMap.StringCharIterator(map.mappings); + var sourceURL = sources[sourceIndex]; + + 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())) { + this._mappings.push([lineNumber, columnNumber]); + continue; + } + + var sourceIndexDelta = this._decodeVLQ(stringCharIterator); + if (sourceIndexDelta) { + sourceIndex += sourceIndexDelta; + sourceURL = sources[sourceIndex]; + } + 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]); + } + + for (var i = 0; i < this._mappings.length; ++i) { + var mapping = this._mappings[i]; + var url = mapping[2]; + if (!url) + continue; + if (!this._reverseMappingsBySourceURL[url]) + this._reverseMappingsBySourceURL[url] = []; + var reverseMappings = this._reverseMappingsBySourceURL[url]; + var sourceLine = mapping[3]; + if (!reverseMappings[sourceLine]) + reverseMappings[sourceLine] = [mapping[0], mapping[1]]; + } + } + + _isSeparator(char) + { + return char === "," || char === ";"; + } + + _decodeVLQ(stringCharIterator) + { + // Read unsigned value. + var result = 0; + var shift = 0; + do { + var digit = WebInspector.SourceMap._base64Map[stringCharIterator.next()]; + result += (digit & WebInspector.SourceMap.VLQ_BASE_MASK) << shift; + shift += WebInspector.SourceMap.VLQ_BASE_SHIFT; + } while (digit & WebInspector.SourceMap.VLQ_CONTINUATION_MASK); + + // Fix the sign. + var negative = result & 1; + result >>= 1; + return negative ? -result : result; + } +}; + +WebInspector.SourceMap.VLQ_BASE_SHIFT = 5; +WebInspector.SourceMap.VLQ_BASE_MASK = (1 << 5) - 1; +WebInspector.SourceMap.VLQ_CONTINUATION_MASK = 1 << 5; + +WebInspector.SourceMap.StringCharIterator = class StringCharIterator +{ + constructor(string) + { + this._string = string; + this._position = 0; + } + + next() + { + return this._string.charAt(this._position++); + } + + peek() + { + return this._string.charAt(this._position); + } + + hasNext() + { + return this._position < this._string.length; + } +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/SourceMapResource.js b/Source/WebInspectorUI/UserInterface/Models/SourceMapResource.js new file mode 100644 index 000000000..e7a17cbdc --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/SourceMapResource.js @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2013 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. + */ + +WebInspector.SourceMapResource = class SourceMapResource extends WebInspector.Resource +{ + constructor(url, sourceMap) + { + super(url, null); + + console.assert(url); + console.assert(sourceMap); + + this._sourceMap = sourceMap; + + var inheritedMIMEType = this._sourceMap.originalSourceCode instanceof WebInspector.Resource ? this._sourceMap.originalSourceCode.syntheticMIMEType : null; + + var fileExtension = WebInspector.fileExtensionForURL(url); + var fileExtensionMIMEType = WebInspector.mimeTypeForFileExtension(fileExtension, true); + + // FIXME: This is a layering violation. It should use a helper function on the + // Resource base-class to set _mimeType and _type. + this._mimeType = fileExtensionMIMEType || inheritedMIMEType || "text/javascript"; + this._type = WebInspector.Resource.typeFromMIMEType(this._mimeType); + + // Mark the resource as loaded so it does not show a spinner in the sidebar. + // We will really load the resource the first time content is requested. + this.markAsFinished(); + } + + // Public + + get sourceMap() + { + return this._sourceMap; + } + + get sourceMapDisplaySubpath() + { + var sourceMappingBasePathURLComponents = this._sourceMap.sourceMappingBasePathURLComponents; + var resourceURLComponents = this.urlComponents; + + // Fallback for JavaScript debuggable named scripts that may not have a complete URL. + if (!resourceURLComponents.path) + resourceURLComponents.path = this.url; + + // Different schemes / hosts. Return the host + path of this resource. + if (resourceURLComponents.scheme !== sourceMappingBasePathURLComponents.scheme || resourceURLComponents.host !== sourceMappingBasePathURLComponents.host) + return resourceURLComponents.host + (resourceURLComponents.port ? (":" + resourceURLComponents.port) : "") + resourceURLComponents.path; + + // Same host, but not a subpath of the base. This implies a ".." in the relative path. + if (!resourceURLComponents.path.startsWith(sourceMappingBasePathURLComponents.path)) + return relativePath(resourceURLComponents.path, sourceMappingBasePathURLComponents.path); + + // Same host. Just a subpath of the base. + return resourceURLComponents.path.substring(sourceMappingBasePathURLComponents.path.length, resourceURLComponents.length); + } + + requestContentFromBackend(callback) + { + // Revert the markAsFinished that was done in the constructor. + this.revertMarkAsFinished(); + + var inlineContent = this._sourceMap.sourceContent(this.url); + if (inlineContent) { + // Force inline content to be asynchronous to match the expected load pattern. + // FIXME: We don't know the MIME-type for inline content. Guess by analyzing the content? + // Returns a promise. + return sourceMapResourceLoaded.call(this, {content: inlineContent, mimeType: this.mimeType, statusCode: 200}); + } + + function sourceMapResourceNotAvailable(error, content, mimeType, statusCode) + { + this.markAsFailed(); + return Promise.resolve({ + error: WebInspector.UIString("An error occurred trying to load the resource."), + content, + mimeType, + statusCode + }); + } + + function sourceMapResourceLoadError(error) + { + // There was an error calling NetworkAgent.loadResource. + console.error(error || "There was an unknown error calling NetworkAgent.loadResource."); + this.markAsFailed(); + return Promise.resolve({error: WebInspector.UIString("An error occurred trying to load the resource.")}); + } + + function sourceMapResourceLoaded(parameters) + { + var {error, content, mimeType, statusCode} = parameters; + + var base64encoded = false; + + if (statusCode >= 400 || error) + return sourceMapResourceNotAvailable(error, content, mimeType, statusCode); + + // FIXME: Add support for picking the best MIME-type. Right now the file extension is the best bet. + // The constructor set MIME-type based on the file extension and we ignore mimeType here. + + this.markAsFinished(); + + return Promise.resolve({ + content, + mimeType, + base64encoded, + statusCode + }); + } + + // COMPATIBILITY (iOS 7): Network.loadResource did not exist. + // Also, JavaScript Debuggable with SourceMaps that do not have inlined content may reach this. + if (!window.NetworkAgent || !NetworkAgent.loadResource) + return sourceMapResourceLoadError.call(this); + + var frameIdentifier = null; + if (this._sourceMap.originalSourceCode instanceof WebInspector.Resource && this._sourceMap.originalSourceCode.parentFrame) + frameIdentifier = this._sourceMap.originalSourceCode.parentFrame.id; + + if (!frameIdentifier) + frameIdentifier = WebInspector.frameResourceManager.mainFrame.id; + + return NetworkAgent.loadResource(frameIdentifier, this.url).then(sourceMapResourceLoaded.bind(this)).catch(sourceMapResourceLoadError.bind(this)); + } + + createSourceCodeLocation(lineNumber, columnNumber) + { + // SourceCodeLocations are always constructed with raw resources and raw locations. Lookup the raw location. + var entry = this._sourceMap.findEntryReversed(this.url, lineNumber); + var rawLineNumber = entry[0]; + var rawColumnNumber = entry[1]; + + // If the raw location is an inline script we need to include that offset. + var originalSourceCode = this._sourceMap.originalSourceCode; + if (originalSourceCode instanceof WebInspector.Script) { + if (rawLineNumber === 0) + rawColumnNumber += originalSourceCode.range.startColumn; + rawLineNumber += originalSourceCode.range.startLine; + } + + // Create the SourceCodeLocation and since we already know the the mapped location set it directly. + var location = originalSourceCode.createSourceCodeLocation(rawLineNumber, rawColumnNumber); + location._setMappedLocation(this, lineNumber, columnNumber); + return location; + } + + createSourceCodeTextRange(textRange) + { + // SourceCodeTextRanges are always constructed with raw resources and raw locations. + // However, we can provide the most accurate mapped locations in construction. + var startSourceCodeLocation = this.createSourceCodeLocation(textRange.startLine, textRange.startColumn); + var endSourceCodeLocation = this.createSourceCodeLocation(textRange.endLine, textRange.endColumn); + return new WebInspector.SourceCodeTextRange(this._sourceMap.originalSourceCode, startSourceCodeLocation, endSourceCodeLocation); + } +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/StackTrace.js b/Source/WebInspectorUI/UserInterface/Models/StackTrace.js new file mode 100644 index 000000000..89acfc872 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/StackTrace.js @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2015 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. + */ + +WebInspector.StackTrace = class StackTrace extends WebInspector.Object +{ + constructor(callFrames, topCallFrameIsBoundary, truncated, parentStackTrace) + { + super(); + + console.assert(callFrames && callFrames.every((callFrame) => callFrame instanceof WebInspector.CallFrame)); + + this._callFrames = callFrames; + this._topCallFrameIsBoundary = topCallFrameIsBoundary || false; + this._truncated = truncated || false; + this._parentStackTrace = parentStackTrace || null; + } + + // Static + + static fromPayload(target, payload) + { + let result = null; + let previousStackTrace = null; + + while (payload) { + let callFrames = payload.callFrames.map((x) => WebInspector.CallFrame.fromPayload(target, x)); + let stackTrace = new WebInspector.StackTrace(callFrames, payload.topCallFrameIsBoundary, payload.truncated); + if (!result) + result = stackTrace; + if (previousStackTrace) + previousStackTrace._parentStackTrace = stackTrace; + + previousStackTrace = stackTrace; + payload = payload.parentStackTrace; + } + + return result; + } + + static fromString(target, stack) + { + let callFrames = WebInspector.StackTrace._parseStackTrace(stack); + return WebInspector.StackTrace.fromPayload(target, {callFrames}); + } + + // May produce false negatives; must not produce any false positives. + // It may return false on a valid stack trace, but it will never return true on an invalid stack trace. + static isLikelyStackTrace(stack) + { + // This function runs for every logged string. It penalizes the performance. + // As most logged strings are not stack traces, exit as early as possible. + const smallestPossibleStackTraceLength = "http://a.bc/:9:1".length; + if (stack.length < smallestPossibleStackTraceLength.length * 2) + return false; + + const approximateStackLengthOf50Items = 5000; + if (stack.length > approximateStackLengthOf50Items) + return false; + + if (/^[^a-z$_]/i.test(stack[0])) + return false; + + const reasonablyLongLineLength = 500; + const reasonablyLongNativeMethodLength = 120; + const stackTraceLine = `(.{1,${reasonablyLongLineLength}}:\\d+:\\d+|eval code|.{1,${reasonablyLongNativeMethodLength}}@\\[native code\\])`; + const stackTrace = new RegExp(`^${stackTraceLine}(\\n${stackTraceLine})*$`, "g"); + + return stackTrace.test(stack); + } + + static _parseStackTrace(stack) + { + var lines = stack.split(/\n/g); + var result = []; + + for (var line of lines) { + var functionName = ""; + var url = ""; + var lineNumber = 0; + var columnNumber = 0; + var atIndex = line.indexOf("@"); + + if (atIndex !== -1) { + functionName = line.slice(0, atIndex); + ({url, lineNumber, columnNumber} = WebInspector.StackTrace._parseLocation(line.slice(atIndex + 1))); + } else if (line.includes("/")) + ({url, lineNumber, columnNumber} = WebInspector.StackTrace._parseLocation(line)); + else + functionName = line; + + result.push({functionName, url, lineNumber, columnNumber}); + } + + return result; + } + + static _parseLocation(locationString) + { + var result = {url: "", lineNumber: 0, columnNumber: 0}; + var locationRegEx = /(.+?)(?::(\d+)(?::(\d+))?)?$/; + var matched = locationString.match(locationRegEx); + + if (!matched) + return result; + + result.url = matched[1]; + + if (matched[2]) + result.lineNumber = parseInt(matched[2]); + + if (matched[3]) + result.columnNumber = parseInt(matched[3]); + + return result; + } + + // Public + + get callFrames() + { + return this._callFrames; + } + + get firstNonNativeCallFrame() + { + for (let frame of this._callFrames) { + if (!frame.nativeCode) + return frame; + } + + return null; + } + + get firstNonNativeNonAnonymousCallFrame() + { + for (let frame of this._callFrames) { + if (frame.nativeCode) + continue; + if (frame.sourceCodeLocation) { + let sourceCode = frame.sourceCodeLocation.sourceCode; + if (sourceCode instanceof WebInspector.Script && sourceCode.anonymous) + continue; + } + return frame; + } + + return null; + } + + get topCallFrameIsBoundary() { return this._topCallFrameIsBoundary; } + get truncated() { return this._truncated; } + get parentStackTrace() { return this._parentStackTrace; } +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/StructureDescription.js b/Source/WebInspectorUI/UserInterface/Models/StructureDescription.js new file mode 100644 index 000000000..097230ff1 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/StructureDescription.js @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2015 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. + */ + +WebInspector.StructureDescription = class StructureDescription extends WebInspector.Object +{ + constructor(fields, optionalFields, constructorName, prototypeStructure, imprecise) + { + super(); + + console.assert(!fields || fields.every((x) => typeof x === "string")); + console.assert(!optionalFields || optionalFields.every((x) => typeof x === "string")); + console.assert(!constructorName || typeof constructorName === "string"); + console.assert(!prototypeStructure || prototypeStructure instanceof WebInspector.StructureDescription); + + this._fields = fields || null; + this._optionalFields = optionalFields || null; + this._constructorName = constructorName || ""; + this._prototypeStructure = prototypeStructure || null; + this._imprecise = imprecise || false; + } + + // Static + + // Runtime.StructureDescription. + static fromPayload(payload) + { + if (payload.prototypeStructure) + payload.prototypeStructure = WebInspector.StructureDescription.fromPayload(payload.prototypeStructure); + + return new WebInspector.StructureDescription(payload.fields, payload.optionalFields, payload.constructorName, payload.prototypeStructure, payload.imprecise); + } + + // Public + + get fields() { return this._fields; } + get optionalFields() { return this._optionalFields; } + get constructorName() { return this._constructorName; } + get prototypeStructure() { return this._prototypeStructure; } + get imprecise() { return this._imprecise; } +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/TextMarker.js b/Source/WebInspectorUI/UserInterface/Models/TextMarker.js new file mode 100644 index 000000000..f7480ef58 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/TextMarker.js @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2013 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. + */ + +WebInspector.TextMarker = class TextMarker extends WebInspector.Object +{ + constructor(codeMirrorTextMarker, type) + { + super(); + + this._codeMirrorTextMarker = codeMirrorTextMarker; + codeMirrorTextMarker.__webInspectorTextMarker = this; + + this._type = type || WebInspector.TextMarker.Type.Plain; + } + + // Static + + static textMarkerForCodeMirrorTextMarker(codeMirrorTextMarker) + { + return codeMirrorTextMarker.__webInspectorTextMarker || new WebInspector.TextMarker(codeMirrorTextMarker); + } + + // Public + + get codeMirrorTextMarker() + { + return this._codeMirrorTextMarker; + } + + get type() + { + return this._type; + } + + get range() + { + var range = this._codeMirrorTextMarker.find(); + if (!range) + return null; + return new WebInspector.TextRange(range.from.line, range.from.ch, range.to.line, range.to.ch); + } + + get rects() + { + var range = this._codeMirrorTextMarker.find(); + if (!range) + return WebInspector.Rect.ZERO_RECT; + return this._codeMirrorTextMarker.doc.cm.rectsForRange({ + start: range.from, + end: range.to + }); + } + + clear() + { + this._codeMirrorTextMarker.clear(); + } +}; + +WebInspector.TextMarker.Type = { + Color: "text-marker-type-color", + Gradient: "text-marker-type-gradient", + Plain: "text-marker-type-plain", + CubicBezier: "text-marker-type-cubic-bezier", + Spring: "text-marker-type-spring", + Variable: "text-marker-type-variable", +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/TextRange.js b/Source/WebInspectorUI/UserInterface/Models/TextRange.js new file mode 100644 index 000000000..64fe72147 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/TextRange.js @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2013 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. + */ + +WebInspector.TextRange = class TextRange extends WebInspector.Object +{ + constructor(startLineOrStartOffset, startColumnOrEndOffset, endLine, endColumn) + { + super(); + + if (arguments.length === 4) { + console.assert(startLineOrStartOffset <= endLine); + console.assert(startLineOrStartOffset !== endLine || startColumnOrEndOffset <= endColumn); + + this._startLine = typeof startLineOrStartOffset === "number" ? startLineOrStartOffset : NaN; + this._startColumn = typeof startColumnOrEndOffset === "number" ? startColumnOrEndOffset : NaN; + this._endLine = typeof endLine === "number" ? endLine : NaN; + this._endColumn = typeof endColumn === "number" ? endColumn : NaN; + + this._startOffset = NaN; + this._endOffset = NaN; + } else if (arguments.length === 2) { + console.assert(startLineOrStartOffset <= startColumnOrEndOffset); + + this._startOffset = typeof startLineOrStartOffset === "number" ? startLineOrStartOffset : NaN; + this._endOffset = typeof startColumnOrEndOffset === "number" ? startColumnOrEndOffset : NaN; + + this._startLine = NaN; + this._startColumn = NaN; + this._endLine = NaN; + this._endColumn = NaN; + } + } + + // Public + + get startLine() { return this._startLine; } + get startColumn() { return this._startColumn; } + get endLine() { return this._endLine; } + get endColumn() { return this._endColumn; } + get startOffset() { return this._startOffset; } + get endOffset() { return this._endOffset; } + + startPosition() + { + return new WebInspector.SourceCodePosition(this._startLine, this._startColumn); + } + + endPosition() + { + return new WebInspector.SourceCodePosition(this._endLine, this._endColumn); + } + + resolveOffsets(text) + { + console.assert(typeof text === "string"); + if (typeof text !== "string") + return; + + console.assert(!isNaN(this._startLine)); + console.assert(!isNaN(this._startColumn)); + console.assert(!isNaN(this._endLine)); + console.assert(!isNaN(this._endColumn)); + if (isNaN(this._startLine) || isNaN(this._startColumn) || isNaN(this._endLine) || isNaN(this._endColumn)) + return; + + var lastNewLineOffset = 0; + for (var i = 0; i < this._startLine; ++i) + lastNewLineOffset = text.indexOf("\n", lastNewLineOffset) + 1; + + this._startOffset = lastNewLineOffset + this._startColumn; + + for (var i = this._startLine; i < this._endLine; ++i) + lastNewLineOffset = text.indexOf("\n", lastNewLineOffset) + 1; + + this._endOffset = lastNewLineOffset + this._endColumn; + } + + contains(line, column) + { + console.assert(!isNaN(this._startLine), "TextRange needs line/column data"); + + if (line < this._startLine || line > this._endLine) + return false; + if (line === this._startLine && column < this._startColumn) + return false; + if (line === this._endLine && column > this._endColumn) + return false; + + return true; + } +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/Timeline.js b/Source/WebInspectorUI/UserInterface/Models/Timeline.js new file mode 100644 index 000000000..857e8a37c --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/Timeline.js @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2013 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. + */ + +WebInspector.Timeline = class Timeline extends WebInspector.Object +{ + constructor(type) + { + super(); + + this._type = type; + + this.reset(true); + } + + // Static + + static create(type) + { + if (type === WebInspector.TimelineRecord.Type.Network) + return new WebInspector.NetworkTimeline(type); + + if (type === WebInspector.TimelineRecord.Type.Memory) + return new WebInspector.MemoryTimeline(type); + + return new WebInspector.Timeline(type); + } + + // Public + + get type() { return this._type; } + get startTime() { return this._startTime; } + get endTime() { return this._endTime; } + get records() { return this._records; } + + reset(suppressEvents) + { + this._records = []; + this._startTime = NaN; + this._endTime = NaN; + + if (!suppressEvents) { + this.dispatchEventToListeners(WebInspector.Timeline.Event.TimesUpdated); + this.dispatchEventToListeners(WebInspector.Timeline.Event.Reset); + } + } + + addRecord(record) + { + if (record.updatesDynamically) + record.addEventListener(WebInspector.TimelineRecord.Event.Updated, this._recordUpdated, this); + + this._records.push(record); + + this._updateTimesIfNeeded(record); + + this.dispatchEventToListeners(WebInspector.Timeline.Event.RecordAdded, {record}); + } + + saveIdentityToCookie(cookie) + { + cookie[WebInspector.Timeline.TimelineTypeCookieKey] = this._type; + } + + refresh() + { + this.dispatchEventToListeners(WebInspector.Timeline.Event.Refreshed); + } + + recordsInTimeRange(startTime, endTime, includeRecordBeforeStart) + { + let lowerIndex = this._records.lowerBound(startTime, (time, record) => time - record.timestamp); + let upperIndex = this._records.upperBound(endTime, (time, record) => time - record.timestamp); + + // Include the record right before the start time. + if (includeRecordBeforeStart && lowerIndex > 0) + lowerIndex--; + + return this._records.slice(lowerIndex, upperIndex); + } + + // Private + + _updateTimesIfNeeded(record) + { + var changed = false; + + if (isNaN(this._startTime) || record.startTime < this._startTime) { + this._startTime = record.startTime; + changed = true; + } + + if (isNaN(this._endTime) || this._endTime < record.endTime) { + this._endTime = record.endTime; + changed = true; + } + + if (changed) + this.dispatchEventToListeners(WebInspector.Timeline.Event.TimesUpdated); + } + + _recordUpdated(event) + { + this._updateTimesIfNeeded(event.target); + } +}; + +WebInspector.Timeline.Event = { + Reset: "timeline-reset", + RecordAdded: "timeline-record-added", + TimesUpdated: "timeline-times-updated", + Refreshed: "timeline-refreshed", +}; + +WebInspector.Timeline.TimelineTypeCookieKey = "timeline-type"; diff --git a/Source/WebInspectorUI/UserInterface/Models/TimelineMarker.js b/Source/WebInspectorUI/UserInterface/Models/TimelineMarker.js new file mode 100644 index 000000000..dee038cb0 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/TimelineMarker.js @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2013 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. + */ + +WebInspector.TimelineMarker = class TimelineMarker extends WebInspector.Object +{ + constructor(time, type, details) + { + super(); + + console.assert(type); + + this._time = time || 0; + this._type = type; + this._details = details || null; + } + + // Public + + get time() + { + return this._time; + } + + set time(x) + { + console.assert(typeof x === "number", "Time should be a number."); + + x = x || 0; + + if (this._time === x) + return; + + this._time = x; + + this.dispatchEventToListeners(WebInspector.TimelineMarker.Event.TimeChanged); + } + + get type() + { + return this._type; + } + + get details() + { + return this._details; + } +}; + +WebInspector.TimelineMarker.Event = { + TimeChanged: "timeline-marker-time-changed" +}; + +WebInspector.TimelineMarker.Type = { + CurrentTime: "current-time", + LoadEvent: "load-event", + DOMContentEvent: "dom-content-event", + TimeStamp: "timestamp" +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/TimelineRange.js b/Source/WebInspectorUI/UserInterface/Models/TimelineRange.js new file mode 100644 index 000000000..60ef60f93 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/TimelineRange.js @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2016 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. + */ + +WebInspector.TimelineRange = class TimelineRange extends WebInspector.Object +{ + constructor(startValue, endValue) + { + super(); + + this._startValue = startValue; + this._endValue = endValue; + } + + get startValue() { return this._startValue; } + set startValue(x) { this._startValue = x; } + + get endValue() { return this._endValue; } + set endValue(x) { this._endValue = x; } +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/TimelineRecord.js b/Source/WebInspectorUI/UserInterface/Models/TimelineRecord.js new file mode 100644 index 000000000..99f443147 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/TimelineRecord.js @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2013 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. + */ + +WebInspector.TimelineRecord = class TimelineRecord extends WebInspector.Object +{ + constructor(type, startTime, endTime, callFrames, sourceCodeLocation) + { + super(); + + console.assert(type); + + if (type in WebInspector.TimelineRecord.Type) + type = WebInspector.TimelineRecord.Type[type]; + + this._type = type; + this._startTime = startTime || NaN; + this._endTime = endTime || NaN; + this._callFrames = callFrames || null; + this._sourceCodeLocation = sourceCodeLocation || null; + this._children = []; + } + + // Public + + get type() + { + return this._type; + } + + get startTime() + { + // Implemented by subclasses if needed. + return this._startTime; + } + + get activeStartTime() + { + // Implemented by subclasses if needed. + return this._startTime; + } + + get endTime() + { + // Implemented by subclasses if needed. + return this._endTime; + } + + get duration() + { + // Use the getters instead of the properties so this works for subclasses that override the getters. + return this.endTime - this.startTime; + } + + get inactiveDuration() + { + // Use the getters instead of the properties so this works for subclasses that override the getters. + return this.activeStartTime - this.startTime; + } + + get activeDuration() + { + // Use the getters instead of the properties so this works for subclasses that override the getters. + return this.endTime - this.activeStartTime; + } + + get updatesDynamically() + { + // Implemented by subclasses if needed. + return false; + } + + get usesActiveStartTime() + { + // Implemented by subclasses if needed. + return false; + } + + get callFrames() + { + return this._callFrames; + } + + get initiatorCallFrame() + { + if (!this._callFrames || !this._callFrames.length) + return null; + + // Return the first non-native code call frame as the initiator. + for (var i = 0; i < this._callFrames.length; ++i) { + if (this._callFrames[i].nativeCode) + continue; + return this._callFrames[i]; + } + + return null; + } + + get sourceCodeLocation() + { + return this._sourceCodeLocation; + } + + get parent() + { + return this._parent; + } + + set parent(x) + { + if (this._parent === x) + return; + + this._parent = x; + } + + get children() + { + return this._children; + } + + saveIdentityToCookie(cookie) + { + cookie[WebInspector.TimelineRecord.SourceCodeURLCookieKey] = this._sourceCodeLocation ? this._sourceCodeLocation.sourceCode.url ? this._sourceCodeLocation.sourceCode.url.hash : null : null; + cookie[WebInspector.TimelineRecord.SourceCodeLocationLineCookieKey] = this._sourceCodeLocation ? this._sourceCodeLocation.lineNumber : null; + cookie[WebInspector.TimelineRecord.SourceCodeLocationColumnCookieKey] = this._sourceCodeLocation ? this._sourceCodeLocation.columnNumber : null; + cookie[WebInspector.TimelineRecord.TypeCookieKey] = this._type || null; + } +}; + +WebInspector.TimelineRecord.Event = { + Updated: "timeline-record-updated" +}; + +WebInspector.TimelineRecord.Type = { + Network: "timeline-record-type-network", + Layout: "timeline-record-type-layout", + Script: "timeline-record-type-script", + RenderingFrame: "timeline-record-type-rendering-frame", + Memory: "timeline-record-type-memory", + HeapAllocations: "timeline-record-type-heap-allocations", +}; + +WebInspector.TimelineRecord.TypeIdentifier = "timeline-record"; +WebInspector.TimelineRecord.SourceCodeURLCookieKey = "timeline-record-source-code-url"; +WebInspector.TimelineRecord.SourceCodeLocationLineCookieKey = "timeline-record-source-code-location-line"; +WebInspector.TimelineRecord.SourceCodeLocationColumnCookieKey = "timeline-record-source-code-location-column"; +WebInspector.TimelineRecord.TypeCookieKey = "timeline-record-type"; diff --git a/Source/WebInspectorUI/UserInterface/Models/TimelineRecording.js b/Source/WebInspectorUI/UserInterface/Models/TimelineRecording.js new file mode 100644 index 000000000..f4cc10295 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/TimelineRecording.js @@ -0,0 +1,375 @@ +/* + * Copyright (C) 2013 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. + */ + +WebInspector.TimelineRecording = class TimelineRecording extends WebInspector.Object +{ + constructor(identifier, displayName, instruments) + { + super(); + + this._identifier = identifier; + this._timelines = new Map; + this._displayName = displayName; + this._capturing = false; + this._readonly = false; + this._instruments = instruments || []; + this._topDownCallingContextTree = new WebInspector.CallingContextTree(WebInspector.CallingContextTree.Type.TopDown); + this._bottomUpCallingContextTree = new WebInspector.CallingContextTree(WebInspector.CallingContextTree.Type.BottomUp); + this._topFunctionsTopDownCallingContextTree = new WebInspector.CallingContextTree(WebInspector.CallingContextTree.Type.TopFunctionsTopDown); + this._topFunctionsBottomUpCallingContextTree = new WebInspector.CallingContextTree(WebInspector.CallingContextTree.Type.TopFunctionsBottomUp); + + for (let type of WebInspector.TimelineManager.availableTimelineTypes()) { + let timeline = WebInspector.Timeline.create(type); + this._timelines.set(type, timeline); + timeline.addEventListener(WebInspector.Timeline.Event.TimesUpdated, this._timelineTimesUpdated, this); + } + + // For legacy backends, we compute the elapsed time of records relative to this timestamp. + this._legacyFirstRecordedTimestamp = NaN; + + this.reset(true); + } + + // Static + + static sourceCodeTimelinesSupported() + { + return WebInspector.debuggableType === WebInspector.DebuggableType.Web; + } + + // Public + + get displayName() { return this._displayName; } + get identifier() { return this._identifier; } + get timelines() { return this._timelines; } + get instruments() { return this._instruments; } + get readonly() { return this._readonly; } + get startTime() { return this._startTime; } + get endTime() { return this._endTime; } + + get topDownCallingContextTree() { return this._topDownCallingContextTree; } + get bottomUpCallingContextTree() { return this._bottomUpCallingContextTree; } + get topFunctionsTopDownCallingContextTree() { return this._topFunctionsTopDownCallingContextTree; } + get topFunctionsBottomUpCallingContextTree() { return this._topFunctionsBottomUpCallingContextTree; } + + start(initiatedByBackend) + { + console.assert(!this._capturing, "Attempted to start an already started session."); + console.assert(!this._readonly, "Attempted to start a readonly session."); + + this._capturing = true; + + for (let instrument of this._instruments) + instrument.startInstrumentation(initiatedByBackend); + } + + stop(initiatedByBackend) + { + console.assert(this._capturing, "Attempted to stop an already stopped session."); + console.assert(!this._readonly, "Attempted to stop a readonly session."); + + this._capturing = false; + + for (let instrument of this._instruments) + instrument.stopInstrumentation(initiatedByBackend); + } + + saveIdentityToCookie() + { + // Do nothing. Timeline recordings are not persisted when the inspector is + // re-opened, so do not attempt to restore by identifier or display name. + } + + isEmpty() + { + for (var timeline of this._timelines.values()) { + if (timeline.records.length) + return false; + } + + return true; + } + + unloaded() + { + console.assert(!this.isEmpty(), "Shouldn't unload an empty recording; it should be reused instead."); + + this._readonly = true; + + this.dispatchEventToListeners(WebInspector.TimelineRecording.Event.Unloaded); + } + + reset(suppressEvents) + { + console.assert(!this._readonly, "Can't reset a read-only recording."); + + this._sourceCodeTimelinesMap = new Map; + this._eventMarkers = []; + this._startTime = NaN; + this._endTime = NaN; + this._discontinuities = []; + + this._topDownCallingContextTree.reset(); + this._bottomUpCallingContextTree.reset(); + this._topFunctionsTopDownCallingContextTree.reset(); + this._topFunctionsBottomUpCallingContextTree.reset(); + + for (var timeline of this._timelines.values()) + timeline.reset(suppressEvents); + + WebInspector.RenderingFrameTimelineRecord.resetFrameIndex(); + + if (!suppressEvents) { + this.dispatchEventToListeners(WebInspector.TimelineRecording.Event.Reset); + this.dispatchEventToListeners(WebInspector.TimelineRecording.Event.TimesUpdated); + } + } + + sourceCodeTimelinesForSourceCode(sourceCode) + { + var timelines = this._sourceCodeTimelinesMap.get(sourceCode); + if (!timelines) + return []; + return [...timelines.values()]; + } + + timelineForInstrument(instrument) + { + return this._timelines.get(instrument.timelineRecordType); + } + + instrumentForTimeline(timeline) + { + return this._instruments.find((instrument) => instrument.timelineRecordType === timeline.type); + } + + timelineForRecordType(recordType) + { + return this._timelines.get(recordType); + } + + addInstrument(instrument) + { + console.assert(instrument instanceof WebInspector.Instrument, instrument); + console.assert(!this._instruments.includes(instrument), this._instruments, instrument); + + this._instruments.push(instrument); + + this.dispatchEventToListeners(WebInspector.TimelineRecording.Event.InstrumentAdded, {instrument}); + } + + removeInstrument(instrument) + { + console.assert(instrument instanceof WebInspector.Instrument, instrument); + console.assert(this._instruments.includes(instrument), this._instruments, instrument); + + this._instruments.remove(instrument); + + this.dispatchEventToListeners(WebInspector.TimelineRecording.Event.InstrumentRemoved, {instrument}); + } + + addEventMarker(marker) + { + if (!this._capturing) + return; + + this._eventMarkers.push(marker); + + this.dispatchEventToListeners(WebInspector.TimelineRecording.Event.MarkerAdded, {marker}); + } + + addRecord(record) + { + var timeline = this._timelines.get(record.type); + console.assert(timeline, record, this._timelines); + if (!timeline) + return; + + // Add the record to the global timeline by type. + timeline.addRecord(record); + + // Some records don't have source code timelines. + if (record.type === WebInspector.TimelineRecord.Type.Network + || record.type === WebInspector.TimelineRecord.Type.RenderingFrame + || record.type === WebInspector.TimelineRecord.Type.Memory + || record.type === WebInspector.TimelineRecord.Type.HeapAllocations) + return; + + if (!WebInspector.TimelineRecording.sourceCodeTimelinesSupported()) + return; + + // Add the record to the source code timelines. + var activeMainResource = WebInspector.frameResourceManager.mainFrame.provisionalMainResource || WebInspector.frameResourceManager.mainFrame.mainResource; + var sourceCode = record.sourceCodeLocation ? record.sourceCodeLocation.sourceCode : activeMainResource; + + var sourceCodeTimelines = this._sourceCodeTimelinesMap.get(sourceCode); + if (!sourceCodeTimelines) { + sourceCodeTimelines = new Map; + this._sourceCodeTimelinesMap.set(sourceCode, sourceCodeTimelines); + } + + var newTimeline = false; + var key = this._keyForRecord(record); + var sourceCodeTimeline = sourceCodeTimelines.get(key); + if (!sourceCodeTimeline) { + sourceCodeTimeline = new WebInspector.SourceCodeTimeline(sourceCode, record.sourceCodeLocation, record.type, record.eventType); + sourceCodeTimelines.set(key, sourceCodeTimeline); + newTimeline = true; + } + + sourceCodeTimeline.addRecord(record); + + if (newTimeline) + this.dispatchEventToListeners(WebInspector.TimelineRecording.Event.SourceCodeTimelineAdded, {sourceCodeTimeline}); + } + + addMemoryPressureEvent(memoryPressureEvent) + { + let memoryTimeline = this._timelines.get(WebInspector.TimelineRecord.Type.Memory); + console.assert(memoryTimeline, this._timelines); + if (!memoryTimeline) + return; + + memoryTimeline.addMemoryPressureEvent(memoryPressureEvent); + } + + addDiscontinuity(startTime, endTime) + { + this._discontinuities.push({startTime, endTime}); + } + + discontinuitiesInTimeRange(startTime, endTime) + { + return this._discontinuities.filter((item) => item.startTime < endTime && item.endTime > startTime); + } + + addScriptInstrumentForProgrammaticCapture() + { + for (let instrument of this._instruments) { + if (instrument instanceof WebInspector.ScriptInstrument) + return; + } + + this.addInstrument(new WebInspector.ScriptInstrument); + + let instrumentTypes = this._instruments.map((instrument) => instrument.timelineRecordType); + WebInspector.timelineManager.enabledTimelineTypes = instrumentTypes; + } + + computeElapsedTime(timestamp) + { + if (!timestamp || isNaN(timestamp)) + return NaN; + + // COMPATIBILITY (iOS 8): old backends send timestamps (seconds or milliseconds since the epoch), + // rather than seconds elapsed since timeline capturing started. We approximate the latter by + // subtracting the start timestamp, as old versions did not use monotonic times. + if (WebInspector.TimelineRecording.isLegacy === undefined) + WebInspector.TimelineRecording.isLegacy = timestamp > WebInspector.TimelineRecording.TimestampThresholdForLegacyRecordConversion; + + if (!WebInspector.TimelineRecording.isLegacy) + return timestamp; + + // If the record's start time is large, but not really large, then it is seconds since epoch + // not millseconds since epoch, so convert it to milliseconds. + if (timestamp < WebInspector.TimelineRecording.TimestampThresholdForLegacyAssumedMilliseconds) + timestamp *= 1000; + + if (isNaN(this._legacyFirstRecordedTimestamp)) + this._legacyFirstRecordedTimestamp = timestamp; + + // Return seconds since the first recorded value. + return (timestamp - this._legacyFirstRecordedTimestamp) / 1000.0; + } + + setLegacyBaseTimestamp(timestamp) + { + console.assert(isNaN(this._legacyFirstRecordedTimestamp)); + + if (timestamp < WebInspector.TimelineRecording.TimestampThresholdForLegacyAssumedMilliseconds) + timestamp *= 1000; + + this._legacyFirstRecordedTimestamp = timestamp; + } + + initializeTimeBoundsIfNecessary(timestamp) + { + if (isNaN(this._startTime)) { + console.assert(isNaN(this._endTime)); + + this._startTime = timestamp; + this._endTime = timestamp; + + this.dispatchEventToListeners(WebInspector.TimelineRecording.Event.TimesUpdated); + } + } + + // Private + + _keyForRecord(record) + { + var key = record.type; + if (record instanceof WebInspector.ScriptTimelineRecord || record instanceof WebInspector.LayoutTimelineRecord) + key += ":" + record.eventType; + if (record instanceof WebInspector.ScriptTimelineRecord && record.eventType === WebInspector.ScriptTimelineRecord.EventType.EventDispatched) + key += ":" + record.details; + if (record.sourceCodeLocation) + key += ":" + record.sourceCodeLocation.lineNumber + ":" + record.sourceCodeLocation.columnNumber; + return key; + } + + _timelineTimesUpdated(event) + { + var timeline = event.target; + var changed = false; + + if (isNaN(this._startTime) || timeline.startTime < this._startTime) { + this._startTime = timeline.startTime; + changed = true; + } + + if (isNaN(this._endTime) || this._endTime < timeline.endTime) { + this._endTime = timeline.endTime; + changed = true; + } + + if (changed) + this.dispatchEventToListeners(WebInspector.TimelineRecording.Event.TimesUpdated); + } +}; + +WebInspector.TimelineRecording.Event = { + Reset: "timeline-recording-reset", + Unloaded: "timeline-recording-unloaded", + SourceCodeTimelineAdded: "timeline-recording-source-code-timeline-added", + InstrumentAdded: "timeline-recording-instrument-added", + InstrumentRemoved: "timeline-recording-instrument-removed", + TimesUpdated: "timeline-recording-times-updated", + MarkerAdded: "timeline-recording-marker-added", +}; + +WebInspector.TimelineRecording.isLegacy = undefined; +WebInspector.TimelineRecording.TimestampThresholdForLegacyRecordConversion = 10000000; // Some value not near zero. +WebInspector.TimelineRecording.TimestampThresholdForLegacyAssumedMilliseconds = 1420099200000; // Date.parse("Jan 1, 2015"). Milliseconds since epoch. diff --git a/Source/WebInspectorUI/UserInterface/Models/TypeDescription.js b/Source/WebInspectorUI/UserInterface/Models/TypeDescription.js new file mode 100644 index 000000000..60a08def6 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/TypeDescription.js @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2015 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. + */ + +WebInspector.TypeDescription = class TypeDescription extends WebInspector.Object +{ + constructor(leastCommonAncestor, typeSet, structures, valid, truncated) + { + super(); + + console.assert(!leastCommonAncestor || typeof leastCommonAncestor === "string"); + console.assert(!typeSet || typeSet instanceof WebInspector.TypeSet); + console.assert(!structures || structures.every((x) => x instanceof WebInspector.StructureDescription)); + + this._leastCommonAncestor = leastCommonAncestor || null; + this._typeSet = typeSet || null; + this._structures = structures || null; + this._valid = valid || false; + this._truncated = truncated || false; + } + + // Static + + // Runtime.TypeDescription. + static fromPayload(payload) + { + var typeSet = undefined; + if (payload.typeSet) + typeSet = WebInspector.TypeSet.fromPayload(payload.typeSet); + + var structures = undefined; + if (payload.structures) + structures = payload.structures.map(WebInspector.StructureDescription.fromPayload); + + return new WebInspector.TypeDescription(payload.leastCommonAncestor, typeSet, structures, payload.isValid, payload.isTruncated); + } + + // Public + + get leastCommonAncestor() { return this._leastCommonAncestor; } + get typeSet() { return this._typeSet; } + get structures() { return this._structures; } + get valid() { return this._valid; } + get truncated() { return this._truncated; } +}; diff --git a/Source/WebInspectorUI/UserInterface/Models/TypeSet.js b/Source/WebInspectorUI/UserInterface/Models/TypeSet.js new file mode 100644 index 000000000..ad5b430c9 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/TypeSet.js @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2014, 2015 Apple Inc. All rights reserved. + * Copyright (C) Saam Barati. + * + * 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. + */ + +WebInspector.TypeSet = class TypeSet extends WebInspector.Object +{ + constructor(typeSet) + { + super(); + + console.assert(typeSet); + + var bitString = 0x0; + if (typeSet.isFunction) + bitString |= WebInspector.TypeSet.TypeBit.Function; + if (typeSet.isUndefined) + bitString |= WebInspector.TypeSet.TypeBit.Undefined; + if (typeSet.isNull) + bitString |= WebInspector.TypeSet.TypeBit.Null; + if (typeSet.isBoolean) + bitString |= WebInspector.TypeSet.TypeBit.Boolean; + if (typeSet.isInteger) + bitString |= WebInspector.TypeSet.TypeBit.Integer; + if (typeSet.isNumber) + bitString |= WebInspector.TypeSet.TypeBit.Number; + if (typeSet.isString) + bitString |= WebInspector.TypeSet.TypeBit.String; + if (typeSet.isObject) + bitString |= WebInspector.TypeSet.TypeBit.Object; + if (typeSet.isSymbol) + bitString |= WebInspector.TypeSet.TypeBit.Symbol; + console.assert(bitString); + + this._typeSet = typeSet; + this._bitString = bitString; + this._primitiveTypeNames = null; + } + + // Static + + static fromPayload(payload) + { + return new WebInspector.TypeSet(payload); + } + + // Public + + isContainedIn(test) + { + // This function checks if types in bitString are contained in the types described by the 'test' bitstring. (i.e we haven't seen more types than 'test'). + // We have seen fewer or equal number of types as 'test' if ANDing bitString with test doesn't zero out any of our bits. + + // For example: + + // 0b0110 (bitString) + // 0b1111 (test) + // ------ (AND) + // 0b0110 == bitString + + // 0b0110 (bitString) + // 0b0010 (test) + // ------ (AND) + // 0b0010 != bitString + + return this._bitString && (this._bitString & test) === this._bitString; + } + + get primitiveTypeNames() + { + if (this._primitiveTypeNames) + return this._primitiveTypeNames; + + this._primitiveTypeNames = []; + var typeSet = this._typeSet; + if (typeSet.isUndefined) + this._primitiveTypeNames.push("Undefined"); + if (typeSet.isNull) + this._primitiveTypeNames.push("Null"); + if (typeSet.isBoolean) + this._primitiveTypeNames.push("Boolean"); + if (typeSet.isString) + this._primitiveTypeNames.push("String"); + if (typeSet.isSymbol) + this._primitiveTypeNames.push("Symbol"); + + // It's implied that type Integer is contained in type Number. Don't put + // both 'Integer' and 'Number' into the set because this could imply that + // Number means to Double instead of Double|Integer. + if (typeSet.isNumber) + this._primitiveTypeNames.push("Number"); + else if (typeSet.isInteger) + this._primitiveTypeNames.push("Integer"); + + return this._primitiveTypeNames; + } +}; + +WebInspector.TypeSet.TypeBit = { + "Function" : 0x1, + "Undefined" : 0x2, + "Null" : 0x4, + "Boolean" : 0x8, + "Integer" : 0x10, + "Number" : 0x20, + "String" : 0x40, + "Object" : 0x80, + "Symbol" : 0x100 +}; + +WebInspector.TypeSet.NullOrUndefinedTypeBits = WebInspector.TypeSet.TypeBit.Null | WebInspector.TypeSet.TypeBit.Undefined; diff --git a/Source/WebInspectorUI/UserInterface/Models/WrappedPromise.js b/Source/WebInspectorUI/UserInterface/Models/WrappedPromise.js new file mode 100644 index 000000000..a42253d26 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Models/WrappedPromise.js @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2015, 2016 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. + */ + +WebInspector.WrappedPromise = class WrappedPromise +{ + constructor(work) + { + this._settled = false; + this._promise = new Promise((resolve, reject) => { + this._resolveCallback = resolve; + this._rejectCallback = reject; + + // Allow work to resolve or reject the promise by shimming our + // internal callbacks. This ensures that this._settled gets set properly. + if (work && typeof work === "function") + return work(this.resolve.bind(this), this.reject.bind(this)); + }); + } + + // Public + + get settled() + { + return this._settled; + } + + get promise() + { + return this._promise; + } + + resolve(value) + { + if (this._settled) + throw new Error("Promise is already settled, cannot call resolve()."); + + this._settled = true; + this._resolveCallback(value); + } + + reject(value) + { + if (this._settled) + throw new Error("Promise is already settled, cannot call reject()."); + + this._settled = true; + this._rejectCallback(value); + } +}; |