author | Wes Kocher <wkocher@mozilla.com> |
Thu, 09 Jan 2014 16:50:03 -0800 | |
changeset 162816 | 37516445a0b58a662a5fcd8734db0b1c8b633be9 |
parent 162768 | f12b645b0890f151472951a7736e988676f5abb2 (current diff) |
parent 162815 | 204056ecab571a5554db9757fb45f7a9b8dea193 (diff) |
child 162817 | 30f3710477c2f3cf6d939146a6950801341d6d09 |
child 162832 | 2ae64c9c496f72df0bc08715fcf09e76104ce7be |
child 162878 | dfb64dc87f11894142c9d1b5e811c6408198c58a |
child 162904 | fb5e82e717088d07490213376a2eaa3b47e9d153 |
push id | 25971 |
push user | kwierso@gmail.com |
push date | Fri, 10 Jan 2014 00:50:13 +0000 |
treeherder | autoland@37516445a0b5 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
milestone | 29.0a1 |
first release with | nightly linux32
37516445a0b5
/
29.0a1
/
20140110030650
/
files
nightly linux64
37516445a0b5
/
29.0a1
/
20140110030650
/
files
nightly mac
37516445a0b5
/
29.0a1
/
20140110030650
/
files
nightly win32
37516445a0b5
/
29.0a1
/
20140110030650
/
files
nightly win64
37516445a0b5
/
29.0a1
/
20140110030650
/
files
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
releases | nightly linux32
29.0a1
/
20140110030650
/
pushlog to previous
nightly linux64
29.0a1
/
20140110030650
/
pushlog to previous
nightly mac
29.0a1
/
20140110030650
/
pushlog to previous
nightly win32
29.0a1
/
20140110030650
/
pushlog to previous
nightly win64
29.0a1
/
20140110030650
/
pushlog to previous
|
CLOBBER | file | annotate | diff | comparison | revisions | |
browser/devtools/inspector/highlighter.js | file | annotate | diff | comparison | revisions | |
browser/devtools/inspector/selection.js | file | annotate | diff | comparison | revisions | |
browser/devtools/inspector/test/browser_inspector_bug_835722_infobar_reappears.js | file | annotate | diff | comparison | revisions | |
browser/devtools/inspector/test/browser_inspector_highlighter_autohide.js | file | annotate | diff | comparison | revisions | |
browser/metro/base/content/contenthandlers/FindHandler.js | file | annotate | diff | comparison | revisions | |
mobile/android/base/moz.build | file | annotate | diff | comparison | revisions |
--- a/CLOBBER +++ b/CLOBBER @@ -17,9 +17,9 @@ # # Modifying this file will now automatically clobber the buildbot machines \o/ # # Are you updating CLOBBER because you think it's needed for your WebIDL # changes to stick? As of bug 928195, this shouldn't be necessary! Please # don't change CLOBBER for WebIDL changes any more. -Bug 934756 causes the Fennec build to fail unless we clobber. +Bug 944533 requires clobber to force a Proguard refresh
--- a/browser/base/content/browser-thumbnails.js +++ b/browser/base/content/browser-thumbnails.js @@ -1,14 +1,16 @@ #ifdef 0 /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ #endif +Cu.import("resource://gre/modules/NewTabUtils.jsm"); + /** * Keeps thumbnails of open web pages up-to-date. */ let gBrowserThumbnails = { /** * Pref that controls whether we can store SSL content on disk */ PREF_DISK_CACHE_SSL: "browser.cache.disk_cache_ssl", @@ -115,16 +117,21 @@ let gBrowserThumbnails = { this._clearTimeout(aBrowser); this._capture(aBrowser); }.bind(this), this._captureDelayMS); this._timeouts.set(aBrowser, timeout); }, _shouldCapture: function Thumbnails_shouldCapture(aBrowser) { + // Capture only if it's a top site in about:newtab. + if (!NewTabUtils.links.getLinks().some( + (link) => link && link.url == aBrowser.currentURI.spec)) + return false; + // Capture only if it's the currently selected tab. if (aBrowser != gBrowser.selectedBrowser) return false; // Don't capture in per-window private browsing mode. if (PrivateBrowsingUtils.isWindowPrivate(window)) return false;
--- a/browser/base/content/highlighter.css +++ b/browser/base/content/highlighter.css @@ -1,20 +1,16 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - + .highlighter-container { pointer-events: none; } -.highlighter-controls { - position: relative; -} - .highlighter-outline-container { overflow: hidden; position: relative; } .highlighter-outline { position: absolute; } @@ -29,75 +25,65 @@ transition-property: opacity, top, left, width, height; transition-duration: 0.1s; transition-timing-function: linear; } /* * Node Infobar */ +.highlighter-nodeinfobar-container { + position: relative; +} -.highlighter-nodeinfobar-container { +.highlighter-nodeinfobar-positioner { position: absolute; max-width: 95%; } -.highlighter-nodeinfobar-container[hidden] { +.highlighter-nodeinfobar-positioner[hidden] { opacity: 0; pointer-events: none; display: -moz-box; } -.highlighter-nodeinfobar-container:not([disable-transitions]), -.highlighter-nodeinfobar-container[disable-transitions][force-transitions] { +.highlighter-nodeinfobar-positioner:not([disable-transitions]), +.highlighter-nodeinfobar-positioner[disable-transitions][force-transitions] { transition-property: transform, opacity, top, left; transition-duration: 0.1s; transition-timing-function: linear; } .highlighter-nodeinfobar-text { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; direction: ltr; } -.highlighter-nodeinfobar-button > .toolbarbutton-text { - display: none; -} - -.highlighter-nodeinfobar-container:not([locked]):not(:hover) > .highlighter-nodeinfobar > .highlighter-nodeinfobar-button { - visibility: hidden; -} - -.highlighter-nodeinfobar-container[locked] > .highlighter-nodeinfobar, -.highlighter-nodeinfobar-container:not([locked]):hover > .highlighter-nodeinfobar { - pointer-events: auto; -} - html|*.highlighter-nodeinfobar-id, html|*.highlighter-nodeinfobar-classes, html|*.highlighter-nodeinfobar-pseudo-classes, html|*.highlighter-nodeinfobar-tagname { -moz-user-select: text; -moz-user-focus: normal; cursor: text; } .highlighter-nodeinfobar-arrow { display: none; } -.highlighter-nodeinfobar-container[position="top"]:not([hide-arrow]) > .highlighter-nodeinfobar-arrow-bottom { +.highlighter-nodeinfobar-positioner[position="top"]:not([hide-arrow]) > .highlighter-nodeinfobar-arrow-bottom { display: block; } -.highlighter-nodeinfobar-container[position="bottom"]:not([hide-arrow]) > .highlighter-nodeinfobar-arrow-top { +.highlighter-nodeinfobar-positioner[position="bottom"]:not([hide-arrow]) > .highlighter-nodeinfobar-arrow-top { display: block; } -.highlighter-nodeinfobar-container[disabled] { +.highlighter-nodeinfobar-positioner[disabled] { visibility: hidden; } html|*.highlighter-nodeinfobar-tagname { text-transform: lowercase; }
--- a/browser/components/customizableui/src/CustomizeMode.jsm +++ b/browser/components/customizableui/src/CustomizeMode.jsm @@ -1101,16 +1101,20 @@ CustomizeMode.prototype = { if (aTargetArea.id == kPaletteId) { // Did we drag from outside the palette? if (aOriginArea.id !== kPaletteId) { if (!CustomizableUI.isWidgetRemovable(aDraggedItemId)) { return; } CustomizableUI.removeWidgetFromArea(aDraggedItemId); + // Special widgets are removed outright, we can return here: + if (CustomizableUI.isSpecialWidget(aDraggedItemId)) { + return; + } } draggedItem = draggedItem.parentNode; // If the target node is the palette itself, just append if (aTargetNode == this.visiblePalette) { this.visiblePalette.appendChild(draggedItem); } else { // The items in the palette are wrapped, so we need the target node's parent here:
--- a/browser/components/customizableui/test/browser.ini +++ b/browser/components/customizableui/test/browser.ini @@ -54,9 +54,10 @@ skip-if = os == "linux" [browser_941083_invalidate_wrapper_cache_createWidget.js] [browser_942581_unregisterArea_keeps_placements.js] [browser_943683_migration_test.js] [browser_944887_destroyWidget_should_destroy_in_palette.js] [browser_945739_showInPrivateBrowsing_customize_mode.js] [browser_947987_removable_default.js] [browser_948985_non_removable_defaultArea.js] [browser_952963_areaType_getter_no_area.js] +[browser_956602_remove_special_widget.js] [browser_panel_toggle.js]
new file mode 100644 --- /dev/null +++ b/browser/components/customizableui/test/browser_956602_remove_special_widget.js @@ -0,0 +1,31 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + + +// Adding a separator and then dragging it out of the navbar shouldn't throw +add_task(function() { + try { + let navbar = document.getElementById("nav-bar"); + let separatorSelector = "toolbarseparator[id^=customizableui-special-separator]"; + ok(!navbar.querySelector(separatorSelector), "Shouldn't be a separator in the navbar"); + CustomizableUI.addWidgetToArea('separator', 'nav-bar'); + yield startCustomizing(); + let separator = navbar.querySelector(separatorSelector); + ok(separator, "There should be a separator in the navbar now."); + let palette = document.getElementById("customization-palette"); + simulateItemDrag(separator, palette); + ok(!palette.querySelector(separatorSelector), "No separator in the palette."); + } catch (ex) { + Cu.reportError(ex); + ok(false, "Shouldn't throw an exception moving an item to the navbar."); + } finally { + yield endCustomizing(); + } +}); + +add_task(function asyncCleanup() { + resetCustomization(); +});
--- a/browser/components/nsBrowserContentHandler.js +++ b/browser/components/nsBrowserContentHandler.js @@ -578,35 +578,16 @@ nsBrowserContentHandler.prototype = { willRestoreSession = ss.isAutomaticRestoreEnabled(); overridePage = Services.urlFormatter.formatURLPref("startup.homepage_override_url"); if (prefb.prefHasUserValue("app.update.postupdate")) overridePage = getPostUpdateOverridePage(overridePage); overridePage = overridePage.replace("%OLD_VERSION%", old_mstone); break; - - // Temporary case for Australis whatsnew - case OVERRIDE_NEW_BUILD_ID: - let locale = "en-US"; - try { - locale = Services.prefs.getCharPref("general.useragent.locale"); - } catch (e) {} - - let showedAustralisWhatsNew = false; - try { - showedAustralisWhatsNew = Services.prefs.getBoolPref("browser.showedAustralisWhatsNew"); - } catch(e) {} - - // Show the Australis whatsnew page for en-US if we haven't yet shown it - if (!showedAustralisWhatsNew && locale == "en-US") { - Services.prefs.setBoolPref("browser.showedAustralisWhatsNew", true); - overridePage = "https://www.mozilla.org/en-US/firefox/29.0a1/whatsnew/"; - } - break; } } } catch (ex) {} // formatURLPref might return "about:blank" if getting the pref fails if (overridePage == "about:blank") overridePage = "";
--- a/browser/components/search/content/search.xml +++ b/browser/components/search/content/search.xml @@ -82,26 +82,32 @@ var os = Components.classes["@mozilla.org/observer-service;1"] .getService(Components.interfaces.nsIObserverService); os.addObserver(this, "browser-search-engine-modified", false); this._addedObserver = true; this.searchService.init((function search_init_cb(aStatus) { + // Bail out if the binding's been destroyed + if (this._destroyed) + return; + if (Components.isSuccessCode(aStatus)) { // Refresh the display (updating icon, etc) this.updateDisplay(); } else { Components.utils.reportError("Cannot initialize search service, bailing out: " + aStatus); } }).bind(this)); ]]></constructor> <destructor><![CDATA[ + this._destroyed = true; + if (this._addedObserver) { var os = Components.classes["@mozilla.org/observer-service;1"] .getService(Components.interfaces.nsIObserverService); os.removeObserver(this, "browser-search-engine-modified"); this._addedObserver = false; } // Make sure to break the cycle from _textbox to us. Otherwise we leak
--- a/browser/devtools/app-manager/content/projects.js +++ b/browser/devtools/app-manager/content/projects.js @@ -127,17 +127,17 @@ let UI = { let icon; if (manifest.icons) { let size = Object.keys(manifest.icons).sort(function(a, b) b - a)[0]; if (size) { icon = manifest.icons[size]; } } if (!icon) - return null; + return "chrome://browser/skin/devtools/app-manager/default-app-icon.png"; if (project.type == "hosted") { let manifestURL = Services.io.newURI(project.location, null, null); let origin = Services.io.newURI(manifestURL.prePath, null, null); return Services.io.newURI(icon, null, origin).spec; } else if (project.type == "packaged") { let projectFolder = FileUtils.File(project.location); let folderURI = Services.io.newFileURI(projectFolder).spec; return folderURI + icon.replace(/^\/|\\/, "");
--- a/browser/devtools/debugger/debugger-toolbar.js +++ b/browser/devtools/debugger/debugger-toolbar.js @@ -26,21 +26,22 @@ function ToolbarView() { ToolbarView.prototype = { /** * Initialization function, called when the debugger is started. */ initialize: function() { dumpn("Initializing the ToolbarView"); this._instrumentsPaneToggleButton = document.getElementById("instruments-pane-toggle"); - this._resumeOrderPanel = document.getElementById("resumption-order-panel"); this._resumeButton = document.getElementById("resume"); this._stepOverButton = document.getElementById("step-over"); this._stepInButton = document.getElementById("step-in"); this._stepOutButton = document.getElementById("step-out"); + this._resumeOrderTooltip = new Tooltip(document); + this._resumeOrderTooltip.defaultPosition = TOOLBAR_ORDER_POPUP_POSITION; let resumeKey = ShortcutUtils.prettifyShortcut(document.getElementById("resumeKey")); let stepOverKey = ShortcutUtils.prettifyShortcut(document.getElementById("stepOverKey")); let stepInKey = ShortcutUtils.prettifyShortcut(document.getElementById("stepInKey")); let stepOutKey = ShortcutUtils.prettifyShortcut(document.getElementById("stepOutKey")); this._resumeTooltip = L10N.getFormatStr("resumeButtonTooltip", resumeKey); this._pauseTooltip = L10N.getFormatStr("pauseButtonTooltip", resumeKey); this._stepOverTooltip = L10N.getFormatStr("stepOverTooltip", stepOverKey); @@ -75,20 +76,18 @@ ToolbarView.prototype = { * Display a warning when trying to resume a debuggee while another is paused. * Debuggees must be unpaused in a Last-In-First-Out order. * * @param string aPausedUrl * The URL of the last paused debuggee. */ showResumeWarning: function(aPausedUrl) { let label = L10N.getFormatStr("resumptionOrderPanelTitle", aPausedUrl); - let descriptionNode = document.getElementById("resumption-panel-desc"); - descriptionNode.setAttribute("value", label); - - this._resumeOrderPanel.openPopup(this._resumeButton); + this._resumeOrderTooltip.setTextContent([label]); + this._resumeOrderTooltip.show(this._resumeButton); }, /** * Sets the resume button state based on the debugger active thread. * * @param string aState * Either "paused" or "attached". */ @@ -158,21 +157,21 @@ ToolbarView.prototype = { if (DebuggerController.activeThread.paused) { DebuggerController.StackFrames.currentFrameDepth = -1; let warn = DebuggerController._ensureResumptionOrder; DebuggerController.activeThread.stepOut(warn); } }, _instrumentsPaneToggleButton: null, - _resumeOrderPanel: null, _resumeButton: null, _stepOverButton: null, _stepInButton: null, _stepOutButton: null, + _resumeOrderTooltip: null, _resumeTooltip: "", _pauseTooltip: "", _stepOverTooltip: "", _stepInTooltip: "", _stepOutTooltip: "" }; /**
--- a/browser/devtools/debugger/debugger-view.js +++ b/browser/devtools/debugger/debugger-view.js @@ -23,16 +23,17 @@ const GLOBAL_SEARCH_ACTION_MAX_DELAY = 1 const FUNCTION_SEARCH_ACTION_MAX_DELAY = 400; // ms const SEARCH_GLOBAL_FLAG = "!"; const SEARCH_FUNCTION_FLAG = "@"; const SEARCH_TOKEN_FLAG = "#"; const SEARCH_LINE_FLAG = ":"; const SEARCH_VARIABLE_FLAG = "*"; const EDITOR_VARIABLE_HOVER_DELAY = 350; // ms const EDITOR_VARIABLE_POPUP_POSITION = "topcenter bottomleft"; +const TOOLBAR_ORDER_POPUP_POSITION = "topcenter bottomleft"; /** * Object defining the debugger view components. */ let DebuggerView = { /** * Initializes the debugger view. *
--- a/browser/devtools/debugger/debugger.xul +++ b/browser/devtools/debugger/debugger.xul @@ -508,20 +508,9 @@ consumeoutsideclicks="false"> <vbox> <label id="conditional-breakpoint-panel-description" value="&debuggerUI.condBreakPanelTitle;"/> <textbox id="conditional-breakpoint-panel-textbox"/> </vbox> </panel> - <panel id="resumption-order-panel" - type="arrow" - position="before_start" - noautofocus="true" - consumeoutsideclicks="false"> - <hbox align="start"> - <image class="alert-icon"/> - <label id="resumption-panel-desc" class="description"/> - </hbox> - </panel> - </window>
--- a/browser/devtools/fontinspector/font-inspector.js +++ b/browser/devtools/fontinspector/font-inspector.js @@ -15,22 +15,18 @@ function FontInspector(inspector, window this.chromeDoc = window.document; this.init(); } FontInspector.prototype = { init: function FI_init() { this.update = this.update.bind(this); this.onNewNode = this.onNewNode.bind(this); - this.onHighlighterLocked = this.onHighlighterLocked.bind(this); this.inspector.selection.on("new-node", this.onNewNode); this.inspector.sidebar.on("fontinspector-selected", this.onNewNode); - if (this.inspector.highlighter) { - this.inspector.highlighter.on("locked", this.onHighlighterLocked); - } this.update(); }, /** * Is the fontinspector visible in the sidebar? */ isActive: function FI_isActive() { return this.inspector.sidebar && @@ -39,46 +35,34 @@ FontInspector.prototype = { /** * Remove listeners. */ destroy: function FI_destroy() { this.chromeDoc = null; this.inspector.sidebar.off("layoutview-selected", this.onNewNode); this.inspector.selection.off("new-node", this.onNewNode); - if (this.inspector.highlighter) { - this.inspector.highlighter.off("locked", this.onHighlighterLocked); - } }, /** * Selection 'new-node' event handler. */ onNewNode: function FI_onNewNode() { if (this.isActive() && this.inspector.selection.isLocal() && this.inspector.selection.isConnected() && - this.inspector.selection.isElementNode() && - this.inspector.selection.reason != "highlighter") { + this.inspector.selection.isElementNode()) { this.undim(); this.update(); } else { this.dim(); } }, /** - * Highlighter 'locked' event handler - */ - onHighlighterLocked: function FI_onHighlighterLocked() { - this.undim(); - this.update(); - }, - - /** * Hide the font list. No node are selected. */ dim: function FI_dim() { this.chromeDoc.body.classList.add("dim"); this.chromeDoc.querySelector("#all-fonts").innerHTML = ""; }, /** @@ -205,17 +189,17 @@ FontInspector.prototype = { * Select the <body> to show all the fonts included in the document. */ showAll: function FI_showAll() { if (!this.isActive() || !this.inspector.selection.isConnected() || !this.inspector.selection.isElementNode()) { return; } - let node = this.inspector.selection.node; + let node = this.inspector.selection.nodeFront; let contentDocument = node.ownerDocument; let root = contentDocument.documentElement; if (contentDocument.body) { root = contentDocument.body; } this.inspector.selection.setNode(root, "fontinspector"); }, }
new file mode 100644 --- /dev/null +++ b/browser/devtools/framework/selection.js @@ -0,0 +1,296 @@ +/* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const {Cu, Ci} = require("chrome"); +let EventEmitter = require("devtools/shared/event-emitter"); + +/** + * API + * + * new Selection(walker=null, node=null, track={attributes,detached}); + * destroy() + * node (readonly) + * setNode(node, origin="unknown") + * + * Helpers: + * + * window + * document + * isRoot() + * isNode() + * isHTMLNode() + * + * Check the nature of the node: + * + * isElementNode() + * isAttributeNode() + * isTextNode() + * isCDATANode() + * isEntityRefNode() + * isEntityNode() + * isProcessingInstructionNode() + * isCommentNode() + * isDocumentNode() + * isDocumentTypeNode() + * isDocumentFragmentNode() + * isNotationNode() + * + * Events: + * "new-node" when the inner node changed + * "before-new-node" when the inner node is set to change + * "attribute-changed" when an attribute is changed (only if tracked) + * "detached" when the node (or one of its parents) is removed from the document (only if tracked) + * "reparented" when the node (or one of its parents) is moved under a different node (only if tracked) + */ + +/** + * A Selection object. Hold a reference to a node. + * Includes some helpers, fire some helpful events. + * + * @param node Inner node. + * Can be null. Can be (un)set in the future via the "node" property; + * @param trackAttribute Tell if events should be fired when the attributes of + * the node change. + * + */ +function Selection(walker, node=null, track={attributes:true,detached:true}) { + EventEmitter.decorate(this); + + this._onMutations = this._onMutations.bind(this); + this.track = track; + this.setWalker(walker); + this.setNode(node); +} + +exports.Selection = Selection; + +Selection.prototype = { + _walker: null, + _node: null, + + _onMutations: function(mutations) { + let attributeChange = false; + let pseudoChange = false; + let detached = false; + let parentNode = null; + + for (let m of mutations) { + if (!attributeChange && m.type == "attributes") { + attributeChange = true; + } + if (m.type == "childList") { + if (!detached && !this.isConnected()) { + if (this.isNode()) { + parentNode = m.target; + } + detached = true; + } + } + if (m.type == "pseudoClassLock") { + pseudoChange = true; + } + } + + // Fire our events depending on what changed in the mutations array + if (attributeChange) { + this.emit("attribute-changed"); + } + if (pseudoChange) { + this.emit("pseudoclass"); + } + if (detached) { + let rawNode = null; + if (parentNode && parentNode.isLocal_toBeDeprecated()) { + rawNode = parentNode.rawNode(); + } + + this.emit("detached", rawNode, null); + this.emit("detached-front", parentNode); + } + }, + + destroy: function() { + this.setNode(null); + this.setWalker(null); + }, + + setWalker: function(walker) { + if (this._walker) { + this._walker.off("mutations", this._onMutations); + } + this._walker = walker; + if (this._walker) { + this._walker.on("mutations", this._onMutations); + } + }, + + // Not remote-safe + setNode: function(value, reason="unknown") { + if (value) { + value = this._walker.frontForRawNode(value); + } + this.setNodeFront(value, reason); + }, + + // Not remote-safe + get node() { + return this._node; + }, + + // Not remote-safe + get window() { + if (this.isNode()) { + return this.node.ownerDocument.defaultView; + } + return null; + }, + + // Not remote-safe + get document() { + if (this.isNode()) { + return this.node.ownerDocument; + } + return null; + }, + + setNodeFront: function(value, reason="unknown") { + this.reason = reason; + if (value !== this._nodeFront) { + let rawValue = null; + if (value && value.isLocal_toBeDeprecated()) { + rawValue = value.rawNode(); + } + this.emit("before-new-node", rawValue, reason); + this.emit("before-new-node-front", value, reason); + let previousNode = this._node; + let previousFront = this._nodeFront; + this._node = rawValue; + this._nodeFront = value; + this.emit("new-node", previousNode, this.reason); + this.emit("new-node-front", value, this.reason); + } + }, + + get documentFront() { + return this._walker.document(this._nodeFront); + }, + + get nodeFront() { + return this._nodeFront; + }, + + isRoot: function() { + return this.isNode() && + this.isConnected() && + this._nodeFront.isDocumentElement; + }, + + isNode: function() { + if (!this._nodeFront) { + return false; + } + + // As long as tools are still accessing node.rawNode(), + // this needs to stay here. + if (this._node && Cu.isDeadWrapper(this._node)) { + return false; + } + + return true; + }, + + isLocal: function() { + return !!this._node; + }, + + isConnected: function() { + let node = this._nodeFront; + if (!node || !node.actorID) { + return false; + } + + // As long as there are still tools going around + // accessing node.rawNode, this needs to stay. + let rawNode = null; + if (node.isLocal_toBeDeprecated()) { + rawNode = node.rawNode(); + } + if (rawNode) { + try { + let doc = this.document; + return (doc && doc.defaultView && doc.documentElement.contains(rawNode)); + } catch (e) { + // "can't access dead object" error + return false; + } + } + + while(node) { + if (node === this._walker.rootNode) { + return true; + } + node = node.parentNode(); + }; + return false; + }, + + isHTMLNode: function() { + let xhtml_ns = "http://www.w3.org/1999/xhtml"; + return this.isNode() && this.node.namespaceURI == xhtml_ns; + }, + + // Node type + + isElementNode: function() { + return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.ELEMENT_NODE; + }, + + isAttributeNode: function() { + return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.ATTRIBUTE_NODE; + }, + + isTextNode: function() { + return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.TEXT_NODE; + }, + + isCDATANode: function() { + return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.CDATA_SECTION_NODE; + }, + + isEntityRefNode: function() { + return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.ENTITY_REFERENCE_NODE; + }, + + isEntityNode: function() { + return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.ENTITY_NODE; + }, + + isProcessingInstructionNode: function() { + return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.PROCESSING_INSTRUCTION_NODE; + }, + + isCommentNode: function() { + return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.PROCESSING_INSTRUCTION_NODE; + }, + + isDocumentNode: function() { + return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.DOCUMENT_NODE; + }, + + isDocumentTypeNode: function() { + return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.DOCUMENT_TYPE_NODE; + }, + + isDocumentFragmentNode: function() { + return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.DOCUMENT_FRAGMENT_NODE; + }, + + isNotationNode: function() { + return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.NOTATION_NODE; + }, +};
--- a/browser/devtools/framework/test/browser_keybindings.js +++ b/browser/devtools/framework/test/browser_keybindings.js @@ -12,16 +12,17 @@ function test() let node; let inspector; let keysetMap = { }; gBrowser.selectedTab = gBrowser.addTab(); gBrowser.selectedBrowser.addEventListener("load", function onload() { gBrowser.selectedBrowser.removeEventListener("load", onload, true); doc = content.document; + node = doc.querySelector("h1"); waitForFocus(setupKeyBindingsTest, content); }, true); content.location = "data:text/html,<html><head><title>Test for the " + "highlighter keybindings</title></head><body>" + "<h1>Keybindings!</h1></body></html>"; function buildDevtoolsKeysetMap(keyset) { @@ -58,34 +59,47 @@ function test() gDevTools.once("toolbox-ready", (e, toolbox) => { inspectorShouldBeOpenAndHighlighting(toolbox.getCurrentPanel(), toolbox) }); keysetMap.inspector.synthesizeKey(); } + function moveMouseOver(aElement, aInspector, cb) + { + EventUtils.synthesizeMouse(aElement, 2, 2, {type: "mousemove"}, + aElement.ownerDocument.defaultView); + aInspector.toolbox.once("picker-node-hovered", () => { + executeSoon(cb); + }); + } + + function isHighlighting() + { + let outline = gBrowser.selectedBrowser.parentNode + .querySelector(".highlighter-container .highlighter-outline"); + return outline && !outline.hasAttribute("hidden"); + } + function inspectorShouldBeOpenAndHighlighting(aInspector, aToolbox) { is (aToolbox.currentToolId, "inspector", "Correct tool has been loaded"); - is (aInspector.highlighter.locked, true, "Highlighter should be locked"); - aInspector.highlighter.once("unlocked", () => { - is (aInspector.highlighter.locked, false, "Highlighter should be unlocked"); - keysetMap.inspector.synthesizeKey(); - is (aInspector.highlighter.locked, true, "Highlighter should be locked"); + aToolbox.once("picker-started", () => { + ok(true, "picker-started event received, highlighter started"); keysetMap.inspector.synthesizeKey(); - is (aInspector.highlighter.locked, false, "Highlighter should be unlocked"); - keysetMap.inspector.synthesizeKey(); - is (aInspector.highlighter.locked, true, "Highlighter should be locked"); - aToolbox.once("webconsole-ready", (e, panel) => { - webconsoleShouldBeSelected(aToolbox, panel); + aToolbox.once("picker-stopped", () => { + ok(true, "picker-stopped event received, highlighter stopped"); + aToolbox.once("webconsole-ready", (e, panel) => { + webconsoleShouldBeSelected(aToolbox, panel); + }); + keysetMap.webconsole.synthesizeKey(); }); - keysetMap.webconsole.synthesizeKey(); }); } function webconsoleShouldBeSelected(aToolbox, panel) { is (aToolbox.currentToolId, "webconsole"); aToolbox.once("jsdebugger-ready", (e, panel) => {
--- a/browser/devtools/framework/toolbox.js +++ b/browser/devtools/framework/toolbox.js @@ -41,16 +41,19 @@ loader.lazyGetter(this, "toolboxStrings" }); loader.lazyGetter(this, "Requisition", () => { let {require} = Cu.import("resource://gre/modules/devtools/Require.jsm", {}); Cu.import("resource://gre/modules/devtools/gcli.jsm", {}); return require("gcli/cli").Requisition; }); +loader.lazyGetter(this, "Selection", () => require("devtools/framework/selection").Selection); +loader.lazyGetter(this, "InspectorFront", () => require("devtools/server/actors/inspector").InspectorFront); + /** * A "Toolbox" is the component that holds all the tools for one specific * target. Visually, it's a document that includes the tools tabs and all * the iframes where the tool panels will be living in. * * @param {object} target * The object the toolbox is debugging. * @param {string} selectedTool @@ -179,16 +182,52 @@ Toolbox.prototype = { /** * Get current zoom level of toolbox */ get zoomValue() { return parseFloat(Services.prefs.getCharPref(ZOOM_PREF)); }, /** + * Get the toolbox highlighter front. Note that it may not always have been + * initialized first. Use `initInspector()` if needed. + */ + get highlighter() { + if (this.isRemoteHighlightable) { + return this._highlighter; + } else { + return null; + } + }, + + /** + * Get the toolbox's inspector front. Note that it may not always have been + * initialized first. Use `initInspector()` if needed. + */ + get inspector() { + return this._inspector; + }, + + /** + * Get the toolbox's walker front. Note that it may not always have been + * initialized first. Use `initInspector()` if needed. + */ + get walker() { + return this._walker; + }, + + /** + * Get the toolbox's node selection. Note that it may not always have been + * initialized first. Use `initInspector()` if needed. + */ + get selection() { + return this._selection; + }, + + /** * Get the toggled state of the split console */ get splitConsole() { return this._splitConsole; }, /** * Open the toolbox @@ -209,21 +248,24 @@ Toolbox.prototype = { this._buildOptions(); this._buildTabs(); this._buildButtons(); this._addKeysToWindow(); this._addToolSwitchingKeys(); this._addZoomKeys(); this._loadInitialZoom(); - this._telemetry.toolOpened("toolbox"); + // Load the toolbox-level actor fronts and utilities now + this._target.makeRemote().then(() => { + this._telemetry.toolOpened("toolbox"); - this.selectTool(this._defaultToolId).then(panel => { - this.emit("ready"); - deferred.resolve(); + this.selectTool(this._defaultToolId).then(panel => { + this.emit("ready"); + deferred.resolve(); + }); }); }; iframe.setAttribute("src", this._URL); let domHelper = new DOMHelpers(iframe.contentWindow); domHelper.onceDOMReady(domReady); @@ -410,17 +452,16 @@ Toolbox.prototype = { key.setAttribute("oncommand", "void(0)"); // needed. See bug 371900 key.addEventListener("command", () => { HUDService.toggleBrowserConsole(); }, true); doc.getElementById("toolbox-keyset").appendChild(key); } }, - /** * Handle any custom key events. Returns true if there was a custom key binding run * @param {string} toolId * Which tool to run the command on (skip if not current) */ fireCustomKey: function(toolId) { let toolDefinition = gDevTools.getToolDefinition(toolId); @@ -479,33 +520,52 @@ Toolbox.prototype = { */ _buildTabs: function() { for (let definition of gDevTools.getToolDefinitionArray()) { this._buildTabForTool(definition); } }, /** - * Add buttons to the UI as specified in the devtools.window.toolbarSpec pref + * Add buttons to the UI as specified in the devtools.toolbox.toolbarSpec pref */ _buildButtons: function() { + this._buildPickerButton(); + if (!this.target.isLocalTab) { return; } let spec = CommandUtils.getCommandbarSpec("devtools.toolbox.toolbarSpec"); let env = CommandUtils.createEnvironment(this.target.tab.ownerDocument, this.target.window.document); let req = new Requisition(env); let buttons = CommandUtils.createButtons(spec, this._target, this.doc, req); let container = this.doc.getElementById("toolbox-buttons"); buttons.forEach(container.appendChild.bind(container)); }, /** + * Adding the element picker button is done here unlike the other buttons + * since we want it to work for remote targets too + */ + _buildPickerButton: function() { + this._pickerButton = this.doc.createElement("toolbarbutton"); + this._pickerButton.id = "command-button-pick"; + this._pickerButton.className = "command-button"; + this._pickerButton.setAttribute("tooltiptext", toolboxStrings("pickButton.tooltip")); + + let container = this.doc.querySelector("#toolbox-buttons"); + container.appendChild(this._pickerButton); + + this.togglePicker = this.togglePicker.bind(this); + this._pickerButton.addEventListener("command", this.togglePicker, false); + }, + + /** * Build a tab for one tool definition and add to the toolbox * * @param {string} toolDefinition * Tool definition of the tool to build a tab for. */ _buildTabForTool: function(toolDefinition) { if (!toolDefinition.isTargetSupported(this._target)) { return; @@ -595,16 +655,22 @@ Toolbox.prototype = { /** * Ensure the tool with the given id is loaded. * * @param {string} id * The id of the tool to load. */ loadTool: function(id) { + if (id === "inspector" && !this._inspector) { + return this.initInspector().then(() => { + return this.loadTool(id); + }); + } + let deferred = promise.defer(); let iframe = this.doc.getElementById("toolbox-panel-iframe-" + id); if (iframe) { let panel = this._toolPanels.get(id); if (panel) { deferred.resolve(panel); } else { @@ -953,16 +1019,164 @@ Toolbox.prototype = { let key = doc.getElementById("key_" + toolId); if (key) { key.parentNode.removeChild(key); } } }, /** + * Initialize the inspector/walker/selection/highlighter fronts. + * Returns a promise that resolves when the fronts are initialized + */ + initInspector: function() { + let deferred = promise.defer(); + + if (!this._inspector) { + this._inspector = InspectorFront(this._target.client, this._target.form); + this._inspector.getWalker().then(walker => { + this._walker = walker; + this._selection = new Selection(this._walker); + if (this.isRemoteHighlightable) { + this._inspector.getHighlighter().then(highlighter => { + this._highlighter = highlighter; + deferred.resolve(); + }); + } else { + deferred.resolve(); + } + }); + } else { + deferred.resolve(); + } + + return deferred.promise; + }, + + /** + * Destroy the inspector/walker/selection fronts + * Returns a promise that resolves when the fronts are destroyed + */ + destroyInspector: function() { + let deferred = promise.defer(); + + if (this._inspector) { + this._selection.destroy(); + this._selection = null; + this._walker.release().then( + () => { + this._inspector.destroy(); + this._highlighter.destroy(); + }, + (e) => { + console.error("Walker.release() failed: " + e); + this._inspector.destroy(); + return this._highlighter.destroy(); + } + ).then(() => { + this._inspector = null; + this._highlighter = null; + this._walker = null; + deferred.resolve(); + }); + } else { + deferred.resolve(); + } + + return deferred.promise; + }, + + /** + * Start/stop the element picker on the debuggee target. + */ + togglePicker: function() { + if (this._isPicking) { + return this.stopPicker(); + } else { + return this.startPicker(); + } + }, + + get isRemoteHighlightable() { + return this._target.client.traits.highlightable; + }, + + /** + * Start the element picker on the debuggee target. + * This will request the inspector actor to start listening for mouse/touch + * events on the target to highlight the hovered/picked element. + * Depending on the server-side capabilities, this may fire events when nodes + * are hovered. + * @return A promise that resolves when the picker has started + */ + startPicker: function() { + let deferred = promise.defer(); + + let done = () => { + this.emit("picker-started"); + deferred.resolve(); + }; + + this.initInspector().then(() => { + this._isPicking = true; + this._pickerButton.setAttribute("checked", "true"); + + if (this.isRemoteHighlightable) { + this.highlighter.pick().then(done); + + this._onPickerNodeHovered = res => { + this.emit("picker-node-hovered", res.node); + }; + this.walker.on("picker-node-hovered", this._onPickerNodeHovered); + + this._onPickerNodePicked = res => { + this.selection.setNodeFront(res.node, "picker-node-picked"); + this.stopPicker(); + }; + this.walker.on("picker-node-picked", this._onPickerNodePicked); + } else { + this.walker.pick().then(node => { + this.selection.setNodeFront(node, "picker-node-picked"); + this.stopPicker(); + }); + done(); + } + }); + + return deferred.promise; + }, + + /** + * Stop the element picker + * @return A promise that resolves when the picker has stopped + */ + stopPicker: function() { + let deferred = promise.defer(); + + let done = () => { + this.emit("picker-stopped"); + deferred.resolve(); + }; + + this.initInspector().then(() => { + this._isPicking = false; + this._pickerButton.removeAttribute("checked"); + if (this.isRemoteHighlightable) { + this.highlighter.cancelPick().then(done); + this.walker.off("picker-node-hovered", this._onPickerNodeHovered); + this.walker.off("picker-node-picked", this._onPickerNodePicked); + } else { + this.walker.cancelPick().then(done); + } + }); + + return deferred.promise; + }, + + /** * Get the toolbox's notification box * * @return The notification box element. */ getNotificationBox: function() { return this.doc.getElementById("toolbox-notificationbox"); }, @@ -999,16 +1213,22 @@ Toolbox.prototype = { try { outstanding.push(panel.destroy()); } catch (e) { // We don't want to stop here if any panel fail to close. console.error(e); } } + // Destroying the walker and inspector fronts + outstanding.push(this.destroyInspector()); + + // Removing buttons + this._pickerButton.removeEventListener("command", this.togglePicker, false); + this._pickerButton = null; let container = this.doc.getElementById("toolbox-buttons"); while (container.firstChild) { container.removeChild(container.firstChild); } outstanding.push(this.destroyHost()); this._telemetry.destroy();
deleted file mode 100644 --- a/browser/devtools/inspector/highlighter.js +++ /dev/null @@ -1,877 +0,0 @@ -/* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -const {Cu, Cc, Ci} = require("chrome"); - -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm"); -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); - -let EventEmitter = require("devtools/shared/event-emitter"); - -const PSEUDO_CLASSES = [":hover", ":active", ":focus"]; - // add ":visited" and ":link" after bug 713106 is fixed - -exports._forceBasic = {value: false}; - -exports.Highlighter = function Highlighter(aTarget, aInspector, aToolbox) { - if (aTarget.isLocalTab && !exports._forceBasic.value) { - return new LocalHighlighter(aTarget, aInspector, aToolbox); - } else { - return new BasicHighlighter(aTarget, aInspector, aToolbox); - } -} - -exports.LocalHighlighter = LocalHighlighter; -exports.BasicHighlighter = BasicHighlighter; - -/** - * A highlighter mechanism. - * - * The highlighter is built dynamically into the browser element. - * The caller is in charge of destroying the highlighter (ie, the highlighter - * won't be destroyed if a new tab is selected for example). - * - * API: - * - * // Constructor and destructor. - * Highlighter(aTab, aInspector) - * void destroy(); - * - * // Show and hide the highlighter - * void show(); - * void hide(); - * boolean isHidden(); - * - * // Redraw the highlighter if the visible portion of the node has changed. - * void invalidateSize(aScroll); - * - * Events: - * - * "closed" - Highlighter is closing - * "highlighting" - Highlighter is highlighting - * "locked" - The selected node has been locked - * "unlocked" - The selected ndoe has been unlocked - * - * Structure: - * <stack class="highlighter-container"> - * <box class="highlighter-outline-container"> - * <box class="highlighter-outline" locked="true/false"/> - * </box> - * <box class="highlighter-controls"> - * <box class="highlighter-nodeinfobar-container" position="top/bottom" locked="true/false"> - * <box class="highlighter-nodeinfobar-arrow highlighter-nodeinfobar-arrow-top"/> - * <hbox class="highlighter-nodeinfobar"> - * <toolbarbutton class="highlighter-nodeinfobar-inspectbutton highlighter-nodeinfobar-button"/> - * <hbox class="highlighter-nodeinfobar-text">tagname#id.class1.class2</hbox> - * <toolbarbutton class="highlighter-nodeinfobar-menu highlighter-nodeinfobar-button">…</toolbarbutton> - * </hbox> - * <box class="highlighter-nodeinfobar-arrow highlighter-nodeinfobar-arrow-bottom"/> - * </box> - * </box> - * </stack> - * - */ - - -/** - * Constructor. - * - * @param aTarget The inspection target. - * @param aInspector Inspector panel. - * @param aToolbox The toolbox holding the inspector. - */ -function LocalHighlighter(aTarget, aInspector, aToolbox) -{ - this.target = aTarget; - this.tab = aTarget.tab; - this.toolbox = aToolbox; - this.browser = this.tab.linkedBrowser; - this.chromeDoc = this.tab.ownerDocument; - this.chromeWin = this.chromeDoc.defaultView; - this.inspector = aInspector - this.layoutHelpers = new LayoutHelpers(this.browser.contentWindow); - - EventEmitter.decorate(this); - - this._init(); -} - -LocalHighlighter.prototype = { - get selection() { - return this.inspector.selection; - }, - - _init: function LocalHighlighter__init() - { - this.toggleLockState = this.toggleLockState.bind(this); - this.unlockAndFocus = this.unlockAndFocus.bind(this); - this.updateInfobar = this.updateInfobar.bind(this); - this.highlight = this.highlight.bind(this); - - let stack = this.browser.parentNode; - this.win = this.browser.contentWindow; - this._highlighting = false; - - this.highlighterContainer = this.chromeDoc.createElement("stack"); - this.highlighterContainer.className = "highlighter-container"; - - this.outline = this.chromeDoc.createElement("box"); - this.outline.className = "highlighter-outline"; - - let outlineContainer = this.chromeDoc.createElement("box"); - outlineContainer.appendChild(this.outline); - outlineContainer.className = "highlighter-outline-container"; - - // The controlsBox will host the different interactive - // elements of the highlighter (buttons, toolbars, ...). - let controlsBox = this.chromeDoc.createElement("box"); - controlsBox.className = "highlighter-controls"; - this.highlighterContainer.appendChild(outlineContainer); - this.highlighterContainer.appendChild(controlsBox); - - // Insert the highlighter right after the browser - stack.insertBefore(this.highlighterContainer, stack.childNodes[1]); - - this.buildInfobar(controlsBox); - - this.transitionDisabler = null; - this.pageEventsMuter = null; - - this.selection.on("new-node", this.highlight); - this.selection.on("new-node", this.updateInfobar); - this.selection.on("pseudoclass", this.updateInfobar); - this.selection.on("attribute-changed", this.updateInfobar); - - this.onToolSelected = function(event, id) { - if (id != "inspector") { - this.chromeWin.clearTimeout(this.pageEventsMuter); - this.detachMouseListeners(); - this.disabled = true; - this.hide(); - } else { - if (!this.locked) { - this.attachMouseListeners(); - } - this.disabled = false; - this.show(); - } - }.bind(this); - this.toolbox.on("select", this.onToolSelected); - - this.hidden = true; - this.highlight(); - }, - - /** - * Destroy the nodes. Remove listeners. - */ - destroy: function LocalHighlighter_destroy() - { - this.inspectButton.removeEventListener("command", this.unlockAndFocus); - this.inspectButton = null; - - this.toolbox.off("select", this.onToolSelected); - this.toolbox = null; - - this.selection.off("new-node", this.highlight); - this.selection.off("new-node", this.updateInfobar); - this.selection.off("pseudoclass", this.updateInfobar); - this.selection.off("attribute-changed", this.updateInfobar); - - this.detachMouseListeners(); - this.detachPageListeners(); - - this.chromeWin.clearTimeout(this.transitionDisabler); - this.chromeWin.clearTimeout(this.pageEventsMuter); - this.boundCloseEventHandler = null; - this._contentRect = null; - this._highlightRect = null; - this._highlighting = false; - this.outline = null; - this.nodeInfo = null; - this.highlighterContainer.parentNode.removeChild(this.highlighterContainer); - this.highlighterContainer = null; - this.win = null - this.browser = null; - this.chromeDoc = null; - this.chromeWin = null; - this.tabbrowser = null; - - this.emit("closed"); - }, - - /** - * Show the outline, and select a node. - */ - highlight: function LocalHighlighter_highlight() - { - if (this.selection.reason != "highlighter") { - this.lock(); - } - - let canHighlightNode = this.selection.isNode() && - this.selection.isConnected() && - this.selection.isElementNode(); - - if (canHighlightNode) { - if (this.selection.reason != "navigateaway") { - this.disabled = false; - } - this.show(); - this.updateInfobar(); - this.invalidateSize(); - if (!this._highlighting && - this.selection.reason != "highlighter") { - this.layoutHelpers.scrollIntoViewIfNeeded(this.selection.node); - } - } else { - this.disabled = true; - this.hide(); - } - }, - - /** - * Update the highlighter size and position. - */ - invalidateSize: function LocalHighlighter_invalidateSize() - { - let canHiglightNode = this.selection.isNode() && - this.selection.isConnected() && - this.selection.isElementNode(); - - if (!canHiglightNode) - return; - - // The highlighter runs locally while the selection runs remotely, - // so we can't quite trust the selection's isConnected to protect us - // here, do the check manually. - if (!this.selection.node || - !this.selection.node.ownerDocument || - !this.selection.node.ownerDocument.defaultView) { - return; - } - - let clientRect = this.selection.node.getBoundingClientRect(); - let rect = this.layoutHelpers.getDirtyRect(this.selection.node); - this.highlightRectangle(rect); - - this.moveInfobar(); - - if (this._highlighting) { - this.showOutline(); - this.emit("highlighting"); - } - }, - - /** - * Show the highlighter if it has been hidden. - */ - show: function() { - if (!this.hidden || this.disabled) return; - this.showOutline(); - this.showInfobar(); - this.computeZoomFactor(); - this.attachPageListeners(); - this.invalidateSize(); - this.hidden = false; - }, - - /** - * Hide the highlighter, the outline and the infobar. - */ - hide: function() { - if (this.hidden) return; - this.hideOutline(); - this.hideInfobar(); - this.detachPageListeners(); - this.hidden = true; - }, - - /** - * Is the highlighter visible? - * - * @return boolean - */ - isHidden: function() { - return this.hidden; - }, - - /** - * Lock a node. Stops the inspection. - */ - lock: function() { - if (this.locked === true) return; - this.outline.setAttribute("locked", "true"); - this.nodeInfo.container.setAttribute("locked", "true"); - this.detachMouseListeners(); - this.locked = true; - this.emit("locked"); - }, - - /** - * Start inspecting. - * Unlock the current node (if any), and select any node being hovered. - */ - unlock: function() { - if (this.locked === false) return; - this.outline.removeAttribute("locked"); - this.nodeInfo.container.removeAttribute("locked"); - this.attachMouseListeners(); - this.locked = false; - if (this.selection.isElementNode() && - this.selection.isConnected()) { - this.showOutline(); - } - this.emit("unlocked"); - }, - - /** - * Toggle between locked and unlocked - */ - toggleLockState: function() { - if (this.locked) { - this.startNode = this.selection.node; - this.unlockAndFocus(); - } else { - this.selection.setNode(this.startNode); - this.lock(); - } - }, - - /** - * Focus the browser before unlocking. - */ - unlockAndFocus: function LocalHighlighter_unlockAndFocus() { - if (this.locked === false) return; - this.chromeWin.focus(); - this.unlock(); - }, - - /** - * Hide the infobar - */ - hideInfobar: function LocalHighlighter_hideInfobar() { - this.nodeInfo.container.setAttribute("force-transitions", "true"); - this.nodeInfo.container.setAttribute("hidden", "true"); - }, - - /** - * Show the infobar - */ - showInfobar: function LocalHighlighter_showInfobar() { - this.nodeInfo.container.removeAttribute("hidden"); - this.moveInfobar(); - this.nodeInfo.container.removeAttribute("force-transitions"); - }, - - /** - * Hide the outline - */ - hideOutline: function LocalHighlighter_hideOutline() { - this.outline.setAttribute("hidden", "true"); - }, - - /** - * Show the outline - */ - showOutline: function LocalHighlighter_showOutline() { - if (this._highlighting) - this.outline.removeAttribute("hidden"); - }, - - /** - * Build the node Infobar. - * - * <box class="highlighter-nodeinfobar-container"> - * <box class="highlighter-nodeinfobar-arrow-top"/> - * <hbox class="highlighter-nodeinfobar"> - * <toolbarbutton class="highlighter-nodeinfobar-button highlighter-nodeinfobar-inspectbutton"/> - * <hbox class="highlighter-nodeinfobar-text"> - * <xhtml:span class="highlighter-nodeinfobar-tagname"/> - * <xhtml:span class="highlighter-nodeinfobar-id"/> - * <xhtml:span class="highlighter-nodeinfobar-classes"/> - * <xhtml:span class="highlighter-nodeinfobar-pseudo-classes"/> - * </hbox> - * <toolbarbutton class="highlighter-nodeinfobar-button highlighter-nodeinfobar-menu"/> - * </hbox> - * <box class="highlighter-nodeinfobar-arrow-bottom"/> - * </box> - * - * @param nsIDOMElement aParent - * The container of the infobar. - */ - buildInfobar: function LocalHighlighter_buildInfobar(aParent) - { - let container = this.chromeDoc.createElement("box"); - container.className = "highlighter-nodeinfobar-container"; - container.setAttribute("position", "top"); - container.setAttribute("disabled", "true"); - - let nodeInfobar = this.chromeDoc.createElement("hbox"); - nodeInfobar.className = "highlighter-nodeinfobar"; - - let arrowBoxTop = this.chromeDoc.createElement("box"); - arrowBoxTop.className = "highlighter-nodeinfobar-arrow highlighter-nodeinfobar-arrow-top"; - - let arrowBoxBottom = this.chromeDoc.createElement("box"); - arrowBoxBottom.className = "highlighter-nodeinfobar-arrow highlighter-nodeinfobar-arrow-bottom"; - - let tagNameLabel = this.chromeDoc.createElementNS("http://www.w3.org/1999/xhtml", "span"); - tagNameLabel.className = "highlighter-nodeinfobar-tagname"; - - let idLabel = this.chromeDoc.createElementNS("http://www.w3.org/1999/xhtml", "span"); - idLabel.className = "highlighter-nodeinfobar-id"; - - let classesBox = this.chromeDoc.createElementNS("http://www.w3.org/1999/xhtml", "span"); - classesBox.className = "highlighter-nodeinfobar-classes"; - - let pseudoClassesBox = this.chromeDoc.createElementNS("http://www.w3.org/1999/xhtml", "span"); - pseudoClassesBox.className = "highlighter-nodeinfobar-pseudo-classes"; - - // Add some content to force a better boundingClientRect down below. - pseudoClassesBox.textContent = " "; - - // Create buttons - - this.inspectButton = this.chromeDoc.createElement("toolbarbutton"); - this.inspectButton.className = "highlighter-nodeinfobar-button highlighter-nodeinfobar-inspectbutton" - let toolbarInspectButton = this.inspector.panelDoc.getElementById("inspector-inspect-toolbutton"); - this.inspectButton.setAttribute("tooltiptext", toolbarInspectButton.getAttribute("tooltiptext")); - this.inspectButton.addEventListener("command", this.toggleLockState); - - let nodemenu = this.chromeDoc.createElement("toolbarbutton"); - nodemenu.setAttribute("type", "menu"); - nodemenu.className = "highlighter-nodeinfobar-button highlighter-nodeinfobar-menu" - nodemenu.setAttribute("tooltiptext", - this.strings.GetStringFromName("nodeMenu.tooltiptext")); - - nodemenu.onclick = function() { - this.inspector.showNodeMenu(nodemenu, "after_start"); - }.bind(this); - - // <hbox class="highlighter-nodeinfobar-text"/> - let texthbox = this.chromeDoc.createElement("hbox"); - texthbox.className = "highlighter-nodeinfobar-text"; - texthbox.setAttribute("align", "center"); - texthbox.setAttribute("flex", "1"); - - texthbox.addEventListener("mousedown", function(aEvent) { - // On click, show the node: - if (this.selection.isElementNode()) { - this.layoutHelpers.scrollIntoViewIfNeeded(this.selection.node); - } - }.bind(this), true); - - texthbox.appendChild(tagNameLabel); - texthbox.appendChild(idLabel); - texthbox.appendChild(classesBox); - texthbox.appendChild(pseudoClassesBox); - - nodeInfobar.appendChild(this.inspectButton); - nodeInfobar.appendChild(texthbox); - nodeInfobar.appendChild(nodemenu); - - container.appendChild(arrowBoxTop); - container.appendChild(nodeInfobar); - container.appendChild(arrowBoxBottom); - - aParent.appendChild(container); - - let barHeight = container.getBoundingClientRect().height; - - this.nodeInfo = { - tagNameLabel: tagNameLabel, - idLabel: idLabel, - classesBox: classesBox, - pseudoClassesBox: pseudoClassesBox, - container: container, - barHeight: barHeight, - }; - }, - - /** - * Highlight a rectangular region. - * - * @param object aRect - * The rectangle region to highlight. - * @returns boolean - * True if the rectangle was highlighted, false otherwise. - */ - highlightRectangle: function LocalHighlighter_highlightRectangle(aRect) - { - if (!aRect) { - this.unhighlight(); - return; - } - - let oldRect = this._contentRect; - - if (oldRect && aRect.top == oldRect.top && aRect.left == oldRect.left && - aRect.width == oldRect.width && aRect.height == oldRect.height) { - return; // same rectangle - } - - let aRectScaled = this.layoutHelpers.getZoomedRect(this.win, aRect); - - if (aRectScaled.left >= 0 && aRectScaled.top >= 0 && - aRectScaled.width > 0 && aRectScaled.height > 0) { - - this.showOutline(); - - // The bottom div and the right div are flexibles (flex=1). - // We don't need to resize them. - let top = "top:" + aRectScaled.top + "px;"; - let left = "left:" + aRectScaled.left + "px;"; - let width = "width:" + aRectScaled.width + "px;"; - let height = "height:" + aRectScaled.height + "px;"; - this.outline.setAttribute("style", top + left + width + height); - - this._highlighting = true; - } else { - this.unhighlight(); - } - - this._contentRect = aRect; // save orig (non-scaled) rect - this._highlightRect = aRectScaled; // and save the scaled rect. - - return; - }, - - /** - * Clear the highlighter surface. - */ - unhighlight: function LocalHighlighter_unhighlight() - { - this._highlighting = false; - this.hideOutline(); - }, - - /** - * Update node information (tagName#id.class) - */ - updateInfobar: function LocalHighlighter_updateInfobar() - { - if (!this.selection.isElementNode()) { - this.nodeInfo.tagNameLabel.textContent = ""; - this.nodeInfo.idLabel.textContent = ""; - this.nodeInfo.classesBox.textContent = ""; - this.nodeInfo.pseudoClassesBox.textContent = ""; - return; - } - - let node = this.selection.node; - - // Tag name - this.nodeInfo.tagNameLabel.textContent = node.tagName; - - // ID - this.nodeInfo.idLabel.textContent = node.id ? "#" + node.id : ""; - - // Classes - let classes = this.nodeInfo.classesBox; - - classes.textContent = node.classList.length ? - "." + Array.join(node.classList, ".") : ""; - - // Pseudo-classes - let pseudos = PSEUDO_CLASSES.filter(function(pseudo) { - return DOMUtils.hasPseudoClassLock(node, pseudo); - }, this); - - let pseudoBox = this.nodeInfo.pseudoClassesBox; - pseudoBox.textContent = pseudos.join(""); - }, - - /** - * Move the Infobar to the right place in the highlighter. - */ - moveInfobar: function LocalHighlighter_moveInfobar() - { - if (this._highlightRect) { - let winHeight = this.win.innerHeight * this.zoom; - let winWidth = this.win.innerWidth * this.zoom; - - let rect = {top: this._highlightRect.top, - left: this._highlightRect.left, - width: this._highlightRect.width, - height: this._highlightRect.height}; - - rect.top = Math.max(rect.top, 0); - rect.left = Math.max(rect.left, 0); - rect.width = Math.max(rect.width, 0); - rect.height = Math.max(rect.height, 0); - - rect.top = Math.min(rect.top, winHeight); - rect.left = Math.min(rect.left, winWidth); - - this.nodeInfo.container.removeAttribute("disabled"); - // Can the bar be above the node? - if (rect.top < this.nodeInfo.barHeight) { - // No. Can we move the toolbar under the node? - if (rect.top + rect.height + - this.nodeInfo.barHeight > winHeight) { - // No. Let's move it inside. - this.nodeInfo.container.style.top = rect.top + "px"; - this.nodeInfo.container.setAttribute("position", "overlap"); - } else { - // Yes. Let's move it under the node. - this.nodeInfo.container.style.top = rect.top + rect.height + "px"; - this.nodeInfo.container.setAttribute("position", "bottom"); - } - } else { - // Yes. Let's move it on top of the node. - this.nodeInfo.container.style.top = - rect.top - this.nodeInfo.barHeight + "px"; - this.nodeInfo.container.setAttribute("position", "top"); - } - - let barWidth = this.nodeInfo.container.getBoundingClientRect().width; - let left = rect.left + rect.width / 2 - barWidth / 2; - - // Make sure the whole infobar is visible - if (left < 0) { - left = 0; - this.nodeInfo.container.setAttribute("hide-arrow", "true"); - } else { - if (left + barWidth > winWidth) { - left = winWidth - barWidth; - this.nodeInfo.container.setAttribute("hide-arrow", "true"); - } else { - this.nodeInfo.container.removeAttribute("hide-arrow"); - } - } - this.nodeInfo.container.style.left = left + "px"; - } else { - this.nodeInfo.container.style.left = "0"; - this.nodeInfo.container.style.top = "0"; - this.nodeInfo.container.setAttribute("position", "top"); - this.nodeInfo.container.setAttribute("hide-arrow", "true"); - } - }, - - /** - * Store page zoom factor. - */ - computeZoomFactor: function LocalHighlighter_computeZoomFactor() { - this.zoom = - this.win.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowUtils) - .fullZoom; - }, - - ///////////////////////////////////////////////////////////////////////// - //// Event Handling - - attachMouseListeners: function LocalHighlighter_attachMouseListeners() - { - this.browser.addEventListener("mousemove", this, true); - this.browser.addEventListener("click", this, true); - this.browser.addEventListener("dblclick", this, true); - this.browser.addEventListener("mousedown", this, true); - this.browser.addEventListener("mouseup", this, true); - }, - - detachMouseListeners: function LocalHighlighter_detachMouseListeners() - { - this.browser.removeEventListener("mousemove", this, true); - this.browser.removeEventListener("click", this, true); - this.browser.removeEventListener("dblclick", this, true); - this.browser.removeEventListener("mousedown", this, true); - this.browser.removeEventListener("mouseup", this, true); - }, - - attachPageListeners: function LocalHighlighter_attachPageListeners() - { - this.browser.addEventListener("resize", this, true); - this.browser.addEventListener("scroll", this, true); - this.browser.addEventListener("MozAfterPaint", this, true); - }, - - detachPageListeners: function LocalHighlighter_detachPageListeners() - { - this.browser.removeEventListener("resize", this, true); - this.browser.removeEventListener("scroll", this, true); - this.browser.removeEventListener("MozAfterPaint", this, true); - }, - - /** - * Generic event handler. - * - * @param nsIDOMEvent aEvent - * The DOM event object. - */ - handleEvent: function LocalHighlighter_handleEvent(aEvent) - { - switch (aEvent.type) { - case "click": - this.handleClick(aEvent); - break; - case "mousemove": - this.brieflyIgnorePageEvents(); - this.handleMouseMove(aEvent); - break; - case "resize": - this.computeZoomFactor(); - break; - case "MozAfterPaint": - case "scroll": - this.brieflyDisableTransitions(); - this.invalidateSize(); - break; - case "dblclick": - case "mousedown": - case "mouseup": - aEvent.stopPropagation(); - aEvent.preventDefault(); - break; - } - }, - - /** - * Disable the CSS transitions for a short time to avoid laggy animations - * during scrolling or resizing. - */ - brieflyDisableTransitions: function LocalHighlighter_brieflyDisableTransitions() - { - if (this.transitionDisabler) { - this.chromeWin.clearTimeout(this.transitionDisabler); - } else { - this.outline.setAttribute("disable-transitions", "true"); - this.nodeInfo.container.setAttribute("disable-transitions", "true"); - } - this.transitionDisabler = - this.chromeWin.setTimeout(function() { - this.outline.removeAttribute("disable-transitions"); - this.nodeInfo.container.removeAttribute("disable-transitions"); - this.transitionDisabler = null; - }.bind(this), 500); - }, - - /** - * Don't listen to page events while inspecting with the mouse. - */ - brieflyIgnorePageEvents: function LocalHighlighter_brieflyIgnorePageEvents() - { - // The goal is to keep smooth animations while inspecting. - // CSS Transitions might be interrupted because of a MozAfterPaint - // event that would triger an invalidateSize() call. - // So we don't listen to events that would trigger an invalidateSize() - // call. - // - // Side effect, zoom levels are not updated during this short period. - // It's very unlikely this would happen, but just in case, we call - // computeZoomFactor() when reattaching the events. - if (this.pageEventsMuter) { - this.chromeWin.clearTimeout(this.pageEventsMuter); - } else { - this.detachPageListeners(); - } - this.pageEventsMuter = - this.chromeWin.setTimeout(function() { - this.attachPageListeners(); - // Just in case the zoom level changed while ignoring the paint events - this.computeZoomFactor(); - this.pageEventsMuter = null; - }.bind(this), 500); - }, - - /** - * Handle clicks. - * - * @param nsIDOMEvent aEvent - * The DOM event. - */ - handleClick: function LocalHighlighter_handleClick(aEvent) - { - // Stop inspection when the user clicks on a node. - if (aEvent.button == 0) { - this.lock(); - let node = this.selection.node; - this.selection.setNode(node, "highlighter-lock"); - aEvent.preventDefault(); - aEvent.stopPropagation(); - } - }, - - /** - * Handle mousemoves in panel. - * - * @param nsiDOMEvent aEvent - * The MouseEvent triggering the method. - */ - handleMouseMove: function LocalHighlighter_handleMouseMove(aEvent) - { - let doc = aEvent.target.ownerDocument; - - // This should never happen, but just in case, we don't let the - // highlighter highlight browser nodes. - if (doc && doc != this.chromeDoc) { - let element = this.layoutHelpers.getElementFromPoint(aEvent.target.ownerDocument, - aEvent.clientX, aEvent.clientY); - if (element && element != this.selection.node) { - this.selection.setNode(element, "highlighter"); - } - } - }, -}; - -// BasicHighlighter. Doesn't implement any fancy features. Just change -// the outline of the selected node. Works with remote target. - -function BasicHighlighter(aTarget, aInspector) -{ - this.walker = aInspector.walker; - this.selection = aInspector.selection; - this.highlight = this.highlight.bind(this); - this.selection.on("new-node-front", this.highlight); - EventEmitter.decorate(this); - this.locked = true; -} - -BasicHighlighter.prototype = { - destroy: function() { - this.walker.highlight(null); - this.selection.off("new-node-front", this.highlight); - this.walker = null; - this.selection = null; - }, - toggleLockState: function() { - this.locked = !this.locked; - if (this.locked) { - this.walker.cancelPick(); - } else { - this.emit("unlocked"); - this.walker.pick().then( - (node) => this._onPick(node), - () => this._onPick(null) - ); - } - }, - highlight: function() { - this.walker.highlight(this.selection.nodeFront); - }, - _onPick: function(node) { - if (node) { - this.selection.setNodeFront(node); - } - this.locked = true; - this.emit("locked"); - }, - hide: function() {}, - show: function() {}, -} - -/////////////////////////////////////////////////////////////////////////// - -XPCOMUtils.defineLazyGetter(this, "DOMUtils", function () { - return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils) -}); - -XPCOMUtils.defineLazyGetter(LocalHighlighter.prototype, "strings", function () { - return Services.strings.createBundle( - "chrome://browser/locale/devtools/inspector.properties"); -});
--- a/browser/devtools/inspector/inspector-panel.js +++ b/browser/devtools/inspector/inspector-panel.js @@ -8,30 +8,26 @@ const {Cc, Ci, Cu, Cr} = require("chrome Cu.import("resource://gre/modules/Services.jsm"); let promise = require("sdk/core/promise"); let EventEmitter = require("devtools/shared/event-emitter"); let {CssLogic} = require("devtools/styleinspector/css-logic"); loader.lazyGetter(this, "MarkupView", () => require("devtools/markupview/markup-view").MarkupView); -loader.lazyGetter(this, "Selection", () => require("devtools/inspector/selection").Selection); loader.lazyGetter(this, "HTMLBreadcrumbs", () => require("devtools/inspector/breadcrumbs").HTMLBreadcrumbs); -loader.lazyGetter(this, "Highlighter", () => require("devtools/inspector/highlighter").Highlighter); loader.lazyGetter(this, "ToolSidebar", () => require("devtools/framework/sidebar").ToolSidebar); loader.lazyGetter(this, "SelectorSearch", () => require("devtools/inspector/selector-search").SelectorSearch); -loader.lazyGetter(this, "InspectorFront", () => require("devtools/server/actors/inspector").InspectorFront); const LAYOUT_CHANGE_TIMER = 250; /** * Represents an open instance of the Inspector for a tab. - * The inspector controls the highlighter, the breadcrumbs, - * the markup view, and the sidebar (computed view, rule view - * and layout view). + * The inspector controls the breadcrumbs, the markup view, and the sidebar + * (computed view, rule view, font view and layout view). * * Events: * - ready * Fired when the inspector panel is opened for the first time and ready to * use * - new-root * Fired after a new root (navigation to a new page) event was fired by * the walker, and taken into account by the inspector (after the markup @@ -72,51 +68,57 @@ function InspectorPanel(iframeWindow, to exports.InspectorPanel = InspectorPanel; InspectorPanel.prototype = { /** * open is effectively an asynchronous constructor */ open: function InspectorPanel_open() { return this.target.makeRemote().then(() => { - return this._getWalker(); + return this._getPageStyle(); }).then(() => { return this._getDefaultNodeForSelection(); }).then(defaultSelection => { return this._deferredOpen(defaultSelection); }).then(null, console.error); }, + get toolbox() { + return this._toolbox; + }, + get inspector() { - if (!this._target.form) { - throw new Error("Target.inspector requires an initialized remote actor."); - } - if (!this._inspector) { - this._inspector = InspectorFront(this._target.client, this._target.form); - } - return this._inspector; + return this._toolbox.inspector; + }, + + get walker() { + return this._toolbox.walker; + }, + + get selection() { + return this._toolbox.selection; + }, + + get isOuterHTMLEditable() { + return this._target.client.traits.editOuterHTML; }, _deferredOpen: function(defaultSelection) { let deferred = promise.defer(); - this.outerHTMLEditable = this._target.client.traits.editOuterHTML; - this.onNewRoot = this.onNewRoot.bind(this); this.walker.on("new-root", this.onNewRoot); this.nodemenu = this.panelDoc.getElementById("inspector-node-popup"); this.lastNodemenuItem = this.nodemenu.lastChild; this._setupNodeMenu = this._setupNodeMenu.bind(this); this._resetNodeMenu = this._resetNodeMenu.bind(this); this.nodemenu.addEventListener("popupshowing", this._setupNodeMenu, true); this.nodemenu.addEventListener("popuphiding", this._resetNodeMenu, true); - // Create an empty selection - this._selection = new Selection(this.walker); this.onNewSelection = this.onNewSelection.bind(this); this.selection.on("new-node-front", this.onNewSelection); this.onBeforeNewSelection = this.onBeforeNewSelection.bind(this); this.selection.on("before-new-node-front", this.onBeforeNewSelection); this.onDetached = this.onDetached.bind(this); this.selection.on("detached-front", this.onDetached); this.breadcrumbs = new HTMLBreadcrumbs(this); @@ -149,37 +151,24 @@ InspectorPanel.prototype = { }.bind(this); this.target.on("thread-paused", this.updateDebuggerPausedWarning); this.target.on("thread-resumed", this.updateDebuggerPausedWarning); this._toolbox.on("select", this.updateDebuggerPausedWarning); this.updateDebuggerPausedWarning(); } - this.highlighter = new Highlighter(this.target, this, this._toolbox); - let button = this.panelDoc.getElementById("inspector-inspect-toolbutton"); - this.onLockStateChanged = function() { - if (this.highlighter.locked) { - button.removeAttribute("checked"); - this._toolbox.raise(); - } else { - button.setAttribute("checked", "true"); - } - }.bind(this); - this.highlighter.on("locked", this.onLockStateChanged); - this.highlighter.on("unlocked", this.onLockStateChanged); - this._initMarkup(); this.isReady = false; this.once("markuploaded", function() { this.isReady = true; // All the components are initialized. Let's select a node. - this._selection.setNodeFront(defaultSelection); + this.selection.setNodeFront(defaultSelection, "inspector-open"); this.markup.expandNode(this.selection.nodeFront); this.emit("ready"); deferred.resolve(this); }.bind(this)); this.setupSearchBox(); @@ -190,21 +179,18 @@ InspectorPanel.prototype = { _onBeforeNavigate: function() { this._defaultNode = null; this.selection.setNodeFront(null); this._destroyMarkup(); this.isDirty = false; }, - _getWalker: function() { - return this.inspector.getWalker().then(walker => { - this.walker = walker; - return this.inspector.getPageStyle(); - }).then(pageStyle => { + _getPageStyle: function() { + return this._toolbox.inspector.getPageStyle().then(pageStyle => { this.pageStyle = pageStyle; }); }, /** * Return a promise that will resolve to the default node for selection. */ _getDefaultNodeForSelection: function() { @@ -234,23 +220,16 @@ InspectorPanel.prototype = { promise.reject(null); } this._defaultNode = node; return node; }); }, /** - * Selection object (read only) - */ - get selection() { - return this._selection; - }, - - /** * Target getter. */ get target() { return this._target; }, /** * Target setter. @@ -309,17 +288,16 @@ InspectorPanel.prototype = { let defaultTab = Services.prefs.getCharPref("devtools.inspector.activeSidebar"); this._setDefaultSidebar = function(event, toolId) { Services.prefs.setCharPref("devtools.inspector.activeSidebar", toolId); }.bind(this); this.sidebar.on("select", this._setDefaultSidebar); - this.toggleHighlighter = this.toggleHighlighter.bind(this); this.sidebar.addTab("ruleview", "chrome://browser/content/devtools/cssruleview.xhtml", "ruleview" == defaultTab); this.sidebar.addTab("computedview", "chrome://browser/content/devtools/computedview.xhtml", "computedview" == defaultTab); @@ -330,40 +308,35 @@ InspectorPanel.prototype = { "fontinspector" == defaultTab); } this.sidebar.addTab("layoutview", "chrome://browser/content/devtools/layoutview/view.xhtml", "layoutview" == defaultTab); let ruleViewTab = this.sidebar.getTab("ruleview"); - ruleViewTab.addEventListener("mouseover", this.toggleHighlighter, false); - ruleViewTab.addEventListener("mouseout", this.toggleHighlighter, false); this.sidebar.show(); }, /** * Reset the inspector on new root mutation. */ onNewRoot: function InspectorPanel_onNewRoot() { this._defaultNode = null; this.selection.setNodeFront(null); this._destroyMarkup(); this.isDirty = false; this._getDefaultNodeForSelection().then(defaultNode => { - if (this._destroyPromise) { - return; - } this.selection.setNodeFront(defaultNode, "navigateaway"); this._initMarkup(); this.once("markuploaded", () => { - if (this._destroyPromise) { + if (!this.markup) { return; } this.markup.expandNode(this.selection.nodeFront); this.setupSearchBox(); this.emit("new-root"); }); }); }, @@ -394,16 +367,20 @@ InspectorPanel.prototype = { return null; } }, /** * When a new node is selected. */ onNewSelection: function InspectorPanel_onNewSelection(event, value, reason) { + if (reason === "selection-destroy") { + return; + } + this.cancelLayoutChange(); // Wait for all the known tools to finish updating and then let the // client know. let selection = this.selection.nodeFront; // On any new selection made by the user, store the unique css selector // of the selected node so it can be restored after reload of the same page @@ -495,89 +472,66 @@ InspectorPanel.prototype = { this.breadcrumbs.cutAfter(this.breadcrumbs.indexOf(parentNode)); this.selection.setNodeFront(parentNode ? parentNode : this._defaultNode, "detached"); }, /** * Destroy the inspector. */ destroy: function InspectorPanel__destroy() { - if (this._destroyPromise) { - return this._destroyPromise; + if (this._panelDestroyer) { + return this._panelDestroyer.promise; } - - if (this.highlighter) { - this.highlighter.off("locked", this.onLockStateChanged); - this.highlighter.off("unlocked", this.onLockStateChanged); - this.highlighter.destroy(); - } - - delete this.onLockStateChanged; + this._panelDestroyer = promise.defer(); if (this.walker) { this.walker.off("new-root", this.onNewRoot); - this._destroyPromise = this.walker.release() - .then(() => this._inspector.destroy(), - (e) => { - console.error("Walker.release() failed: " + e); - return this._inspector.destroy(); - }) - .then(() => { - this._inspector = null; - }, console.error); - - delete this.walker; - delete this.pageStyle; - } else { - this._destroyPromise = promise.resolve(null); + this.pageStyle = null; } this.cancelUpdate(); this.cancelLayoutChange(); if (this.browser) { this.browser.removeEventListener("resize", this.scheduleLayoutChange, true); this.browser = null; } this.target.off("will-navigate", this._onBeforeNavigate); this.target.off("thread-paused", this.updateDebuggerPausedWarning); this.target.off("thread-resumed", this.updateDebuggerPausedWarning); this._toolbox.off("select", this.updateDebuggerPausedWarning); - this._toolbox = null; - this.sidebar.off("select", this._setDefaultSidebar); this.sidebar.destroy(); this.sidebar = null; this.nodemenu.removeEventListener("popupshowing", this._setupNodeMenu, true); this.nodemenu.removeEventListener("popuphiding", this._resetNodeMenu, true); this.breadcrumbs.destroy(); this.searchSuggestions.destroy(); - delete this.searchBox; + this.searchBox = null; this.selection.off("new-node-front", this.onNewSelection); this.selection.off("before-new-node", this.onBeforeNewSelection); this.selection.off("before-new-node-front", this.onBeforeNewSelection); this.selection.off("detached-front", this.onDetached); this._destroyMarkup(); - this._selection.destroy(); - this._selection = null; this.panelWin.inspector = null; this.target = null; this.panelDoc = null; this.panelWin = null; this.breadcrumbs = null; this.searchSuggestions = null; this.lastNodemenuItem = null; this.nodemenu = null; - this.highlighter = null; + this._toolbox = null; - return this._destroyPromise; + this._panelDestroyer.resolve(null); + return this._panelDestroyer.promise; }, /** * Show the node menu. */ showNodeMenu: function InspectorPanel_showNodeMenu(aButton, aPosition, aExtraItems) { if (aExtraItems) { for (let item of aExtraItems) { @@ -628,17 +582,17 @@ InspectorPanel.prototype = { copyOuterHTML.removeAttribute("disabled"); } else { unique.setAttribute("disabled", "true"); copyInnerHTML.setAttribute("disabled", "true"); copyOuterHTML.setAttribute("disabled", "true"); } let editHTML = this.panelDoc.getElementById("node-menu-edithtml"); - if (this.outerHTMLEditable && selectionIsElement) { + if (this.isOuterHTMLEditable && selectionIsElement) { editHTML.removeAttribute("disabled"); } else { editHTML.setAttribute("disabled", "true"); } }, _resetNodeMenu: function InspectorPanel_resetNodeMenu() { // Remove any extra items @@ -681,74 +635,58 @@ InspectorPanel.prototype = { this.markup = new MarkupView(this, this._markupFrame, controllerWindow); this.emit("markuploaded"); }, _destroyMarkup: function InspectorPanel__destroyMarkup() { if (this._boundMarkupFrameLoad) { this._markupFrame.removeEventListener("load", this._boundMarkupFrameLoad, true); - delete this._boundMarkupFrameLoad; + this._boundMarkupFrameLoad = null; } if (this.markup) { this.markup.destroy(); - delete this.markup; + this.markup = null; } if (this._markupFrame) { this._markupFrame.parentNode.removeChild(this._markupFrame); - delete this._markupFrame; + this._markupFrame = null; } this._markupBox = null; }, /** * Toggle a pseudo class. */ togglePseudoClass: function InspectorPanel_togglePseudoClass(aPseudo) { if (this.selection.isElementNode()) { let node = this.selection.nodeFront; if (node.hasPseudoClassLock(aPseudo)) { - return this.walker.removePseudoClassLock(node, aPseudo, { parents: true }); + return this.walker.removePseudoClassLock(node, aPseudo, {parents: true}); } let hierarchical = aPseudo == ":hover" || aPseudo == ":active"; - return this.walker.addPseudoClassLock(node, aPseudo, { parents: hierarchical }); + return this.walker.addPseudoClassLock(node, aPseudo, {parents: hierarchical}); } }, /** * Clear any pseudo-class locks applied to the current hierarchy. */ clearPseudoClasses: function InspectorPanel_clearPseudoClasses() { if (!this.walker) { return; } return this.walker.clearPseudoClassLocks().then(null, console.error); }, /** - * Toggle the highlighter when ruleview is hovered. - */ - toggleHighlighter: function InspectorPanel_toggleHighlighter(event) - { - if (!this.highlighter) { - return; - } - if (event.type == "mouseover") { - this.highlighter.hide(); - } - else if (event.type == "mouseout") { - this.highlighter.show(); - } - }, - - /** * Edit the outerHTML of the selected Node. */ editHTML: function InspectorPanel_editHTML() { if (!this.selection.isNode()) { return; } if (this.markup) {
--- a/browser/devtools/inspector/inspector.xul +++ b/browser/devtools/inspector/inspector.xul @@ -70,20 +70,16 @@ </menupopup> </popupset> <box flex="1" class="devtools-responsive-container theme-body"> <vbox flex="1"> <toolbar id="inspector-toolbar" class="devtools-toolbar" nowindowdrag="true"> - <toolbarbutton id="inspector-inspect-toolbutton" - tooltiptext="&inspector.selectButton.tooltip;" - class="devtools-toolbarbutton" - oncommand="inspector.highlighter.toggleLockState()"/> <arrowscrollbox id="inspector-breadcrumbs" class="breadcrumbs-widget-container" flex="1" orient="horizontal" clicktoscroll="true"/> <textbox id="inspector-searchbox" type="search" timeout="50" class="devtools-searchinput"
deleted file mode 100644 --- a/browser/devtools/inspector/selection.js +++ /dev/null @@ -1,296 +0,0 @@ -/* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -"use strict"; - -const {Cu, Ci} = require("chrome"); -let EventEmitter = require("devtools/shared/event-emitter"); - -/** - * API - * - * new Selection(walker=null, node=null, track={attributes,detached}); - * destroy() - * node (readonly) - * setNode(node, origin="unknown") - * - * Helpers: - * - * window - * document - * isRoot() - * isNode() - * isHTMLNode() - * - * Check the nature of the node: - * - * isElementNode() - * isAttributeNode() - * isTextNode() - * isCDATANode() - * isEntityRefNode() - * isEntityNode() - * isProcessingInstructionNode() - * isCommentNode() - * isDocumentNode() - * isDocumentTypeNode() - * isDocumentFragmentNode() - * isNotationNode() - * - * Events: - * "new-node" when the inner node changed - * "before-new-node" when the inner node is set to change - * "attribute-changed" when an attribute is changed (only if tracked) - * "detached" when the node (or one of its parents) is removed from the document (only if tracked) - * "reparented" when the node (or one of its parents) is moved under a different node (only if tracked) - */ - -/** - * A Selection object. Hold a reference to a node. - * Includes some helpers, fire some helpful events. - * - * @param node Inner node. - * Can be null. Can be (un)set in the future via the "node" property; - * @param trackAttribute Tell if events should be fired when the attributes of - * the node change. - * - */ -function Selection(walker, node=null, track={attributes:true,detached:true}) { - EventEmitter.decorate(this); - - this._onMutations = this._onMutations.bind(this); - this.track = track; - this.setWalker(walker); - this.setNode(node); -} - -exports.Selection = Selection; - -Selection.prototype = { - _walker: null, - _node: null, - - _onMutations: function(mutations) { - let attributeChange = false; - let pseudoChange = false; - let detached = false; - let parentNode = null; - - for (let m of mutations) { - if (!attributeChange && m.type == "attributes") { - attributeChange = true; - } - if (m.type == "childList") { - if (!detached && !this.isConnected()) { - if (this.isNode()) { - parentNode = m.target; - } - detached = true; - } - } - if (m.type == "pseudoClassLock") { - pseudoChange = true; - } - } - - // Fire our events depending on what changed in the mutations array - if (attributeChange) { - this.emit("attribute-changed"); - } - if (pseudoChange) { - this.emit("pseudoclass"); - } - if (detached) { - let rawNode = null; - if (parentNode && parentNode.isLocal_toBeDeprecated()) { - rawNode = parentNode.rawNode(); - } - - this.emit("detached", rawNode, null); - this.emit("detached-front", parentNode); - } - }, - - destroy: function() { - this.setNode(null); - this.setWalker(null); - }, - - setWalker: function(walker) { - if (this._walker) { - this._walker.off("mutations", this._onMutations); - } - this._walker = walker; - if (this._walker) { - this._walker.on("mutations", this._onMutations); - } - }, - - // Not remote-safe - setNode: function(value, reason="unknown") { - if (value) { - value = this._walker.frontForRawNode(value); - } - this.setNodeFront(value, reason); - }, - - // Not remote-safe - get node() { - return this._node; - }, - - // Not remote-safe - get window() { - if (this.isNode()) { - return this.node.ownerDocument.defaultView; - } - return null; - }, - - // Not remote-safe - get document() { - if (this.isNode()) { - return this.node.ownerDocument; - } - return null; - }, - - setNodeFront: function(value, reason="unknown") { - this.reason = reason; - if (value !== this._nodeFront) { - let rawValue = null; - if (value && value.isLocal_toBeDeprecated()) { - rawValue = value.rawNode(); - } - this.emit("before-new-node", rawValue, reason); - this.emit("before-new-node-front", value, reason); - let previousNode = this._node; - let previousFront = this._nodeFront; - this._node = rawValue; - this._nodeFront = value; - this.emit("new-node", previousNode, this.reason); - this.emit("new-node-front", value, this.reason); - } - }, - - get documentFront() { - return this._walker.document(this._nodeFront); - }, - - get nodeFront() { - return this._nodeFront; - }, - - isRoot: function() { - return this.isNode() && - this.isConnected() && - this._nodeFront.isDocumentElement; - }, - - isNode: function() { - if (!this._nodeFront) { - return false; - } - - // As long as tools are still accessing node.rawNode(), - // this needs to stay here. - if (this._node && Cu.isDeadWrapper(this._node)) { - return false; - } - - return true; - }, - - isLocal: function() { - return !!this._node; - }, - - isConnected: function() { - let node = this._nodeFront; - if (!node || !node.actorID) { - return false; - } - - // As long as there are still tools going around - // accessing node.rawNode, this needs to stay. - let rawNode = null; - if (node.isLocal_toBeDeprecated()) { - rawNode = node.rawNode(); - } - if (rawNode) { - try { - let doc = this.document; - return (doc && doc.defaultView && doc.documentElement.contains(rawNode)); - } catch (e) { - // "can't access dead object" error - return false; - } - } - - while(node) { - if (node === this._walker.rootNode) { - return true; - } - node = node.parentNode(); - }; - return false; - }, - - isHTMLNode: function() { - let xhtml_ns = "http://www.w3.org/1999/xhtml"; - return this.isNode() && this.node.namespaceURI == xhtml_ns; - }, - - // Node type - - isElementNode: function() { - return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.ELEMENT_NODE; - }, - - isAttributeNode: function() { - return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.ATTRIBUTE_NODE; - }, - - isTextNode: function() { - return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.TEXT_NODE; - }, - - isCDATANode: function() { - return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.CDATA_SECTION_NODE; - }, - - isEntityRefNode: function() { - return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.ENTITY_REFERENCE_NODE; - }, - - isEntityNode: function() { - return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.ENTITY_NODE; - }, - - isProcessingInstructionNode: function() { - return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.PROCESSING_INSTRUCTION_NODE; - }, - - isCommentNode: function() { - return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.PROCESSING_INSTRUCTION_NODE; - }, - - isDocumentNode: function() { - return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.DOCUMENT_NODE; - }, - - isDocumentTypeNode: function() { - return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.DOCUMENT_TYPE_NODE; - }, - - isDocumentFragmentNode: function() { - return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.DOCUMENT_FRAGMENT_NODE; - }, - - isNotationNode: function() { - return this.isNode() && this.nodeFront.nodeType == Ci.nsIDOMNode.NOTATION_NODE; - }, -};
--- a/browser/devtools/inspector/test/browser.ini +++ b/browser/devtools/inspector/test/browser.ini @@ -19,24 +19,22 @@ support-files = [browser_inspector_bug_672902_keyboard_shortcuts.js] [browser_inspector_bug_674871.js] [browser_inspector_bug_699308_iframe_navigation.js] [browser_inspector_bug_817558_delete_node.js] [browser_inspector_bug_831693_combinator_suggestions.js] [browser_inspector_bug_831693_input_suggestion.js] # [browser_inspector_bug_831693_searchbox_panel_navigation.js] # Disabled for too many intermittent failures (bug 851349) -[browser_inspector_bug_835722_infobar_reappears.js] [browser_inspector_bug_840156_destroy_after_navigation.js] [browser_inspector_changes.js] [browser_inspector_cmd_inspect.js] [browser_inspector_dead_node_exception.js] [browser_inspector_destroyselection.js] [browser_inspector_highlighter.js] -[browser_inspector_highlighter_autohide.js] [browser_inspector_iframeTest.js] [browser_inspector_infobar.js] [browser_inspector_initialization.js] [browser_inspector_invalidate.js] [browser_inspector_menu.js] [browser_inspector_navigation.js] [browser_inspector_pseudoClass_menu.js] [browser_inspector_pseudoclass_lock.js]
--- a/browser/devtools/inspector/test/browser_inspector_basic_highlighter.js +++ b/browser/devtools/inspector/test/browser_inspector_basic_highlighter.js @@ -1,93 +1,84 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - function test() { - let inspector, doc; + let inspector, doc, toolbox; let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}); let {require} = devtools; let promise = require("sdk/core/promise"); - let { Task } = Cu.import("resource://gre/modules/Task.jsm", {}); + let {Task} = Cu.import("resource://gre/modules/Task.jsm", {}); waitForExplicitFinish(); gBrowser.selectedTab = gBrowser.addTab(); gBrowser.selectedBrowser.addEventListener("load", function onload() { gBrowser.selectedBrowser.removeEventListener("load", onload, true); doc = content.document; waitForFocus(setupTest, content); }, true); - content.location = "data:text/html,<h1>foo<h1><h2>bar</h2>"; + content.location = "data:text/html,<h1>foo</h1><h2>bar</h2>"; function setupTest() { - let h = require("devtools/inspector/highlighter"); - h._forceBasic.value = true; - openInspector(runTests); + openInspector((aInspector, aToolbox) => { + toolbox = aToolbox; + inspector = aInspector; + inspector.selection.setNode(doc.querySelector("h2"), null); + inspector.once("inspector-updated", runTests); + }); } function runTests(aInspector) { - inspector = aInspector; - + getHighlighterOutline().setAttribute("disable-transitions", "true"); Task.spawn(function() { - yield selectH1(); - yield verifyH1Selected(); - yield deselect(); - yield verifyNoNodeSelected(); - - yield selectH1(); - yield verifyH1Selected(); - yield destroyInspector(); - yield verifyNoNodeSelected(); + yield hoverH1InMarkupView(); + yield assertH1Highlighted(); + yield mouseLeaveMarkupView(); + yield assertNoNodeHighlighted(); finishUp(); }).then(null, Cu.reportError); } - function selectH1() { + function hoverH1InMarkupView() { let deferred = promise.defer(); - let h1 = doc.querySelector("h1"); - inspector.selection.once("new-node-front", () => { - executeSoon(deferred.resolve); - }); - inspector.selection.setNode(h1); + + let container = getContainerForRawNode(inspector.markup, doc.querySelector("h1")); + EventUtils.synthesizeMouse(container.tagLine, 2, 2, {type: "mousemove"}, + inspector.markup.doc.defaultView); + inspector.markup.once("node-highlight", deferred.resolve); + return deferred.promise; } - function verifyH1Selected() { - let h1 = doc.querySelector("h1"); - let nodes = doc.querySelectorAll(":-moz-devtools-highlighted"); - is(nodes.length, 1, "only one node selected"); - is(nodes[0], h1, "h1 selected"); + function assertH1Highlighted() { + ok(isHighlighting(), "The highlighter is shown on a markup container hover"); + is(getHighlitNode(), doc.querySelector("h1"), "The highlighter highlights the right node"); return promise.resolve(); } - function deselect() { + function mouseLeaveMarkupView() { let deferred = promise.defer(); - inspector.selection.once("new-node-front", () => { - executeSoon(deferred.resolve); - }); - inspector.selection.setNode(null); + + // Find another element to mouseover over in order to leave the markup-view + let btn = toolbox.doc.querySelector(".toolbox-dock-button"); + + EventUtils.synthesizeMouse(btn, 2, 2, {type: "mousemove"}, + toolbox.doc.defaultView); + executeSoon(deferred.resolve); + return deferred.promise; } - function destroyInspector() { - return inspector.destroy(); - } - - function verifyNoNodeSelected() { - is(doc.querySelectorAll(":-moz-devtools-highlighted").length, 0, "no node selected"); + function assertNoNodeHighlighted() { + ok(!isHighlighting(), "After the mouse left the markup view, the highlighter is hidden"); return promise.resolve(); } function finishUp() { - let h = require("devtools/inspector/highlighter"); - h._forceBasic.value = false; - inspector = doc = null; + inspector = doc = toolbox = null; gBrowser.removeCurrentTab(); finish(); } } - -
--- a/browser/devtools/inspector/test/browser_inspector_breadcrumbs.js +++ b/browser/devtools/inspector/test/browser_inspector_breadcrumbs.js @@ -52,17 +52,20 @@ function test() function nodeSelected() { performTest(); cursor++; if (cursor >= nodes.length) { inspector.off("breadcrumbs-updated", nodeSelected); - finishUp(); + // breadcrumbs-updated is an event that is fired before the rest of the + // inspector is updated, so there'll be hanging connections if we finish + // up before waiting for everything to end. + inspector.once("inspector-updated", finishUp); } else { let node = nodes[cursor].node; inspector.selection.setNode(node); } } function performTest() {
--- a/browser/devtools/inspector/test/browser_inspector_bug_665880.js +++ b/browser/devtools/inspector/test/browser_inspector_bug_665880.js @@ -1,52 +1,45 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ - -function test() -{ +function test() { waitForExplicitFinish(); ignoreAllUncaughtExceptions(); let doc; let objectNode; gBrowser.selectedTab = gBrowser.addTab(); gBrowser.selectedBrowser.addEventListener("load", function() { gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true); doc = content.document; waitForFocus(setupObjectInspectionTest, content); }, true); content.location = "data:text/html,<object style='padding: 100px'><p>foobar</p></object>"; - function setupObjectInspectionTest() - { + function setupObjectInspectionTest() { objectNode = doc.querySelector("object"); ok(objectNode, "we have the object node"); openInspector(runObjectInspectionTest); } - function runObjectInspectionTest(inspector) - { - inspector.highlighter.once("locked", performTestComparison); - inspector.highlighter.unlock(); + function runObjectInspectionTest(inspector) { + inspector.once("inspector-updated", performTestComparison); inspector.selection.setNode(objectNode, ""); } - function performTestComparison() - { + function performTestComparison() { is(getActiveInspector().selection.node, objectNode, "selection matches node"); let target = TargetFactory.forTab(gBrowser.selectedTab); executeSoon(function() { gDevTools.closeToolbox(target); finishUp(); }); } - function finishUp() { doc = objectNode = null; gBrowser.removeCurrentTab(); finish(); } }
--- a/browser/devtools/inspector/test/browser_inspector_bug_674871.js +++ b/browser/devtools/inspector/test/browser_inspector_bug_674871.js @@ -2,16 +2,17 @@ http://creativecommons.org/publicdomain/zero/1.0/ */ function test() { waitForExplicitFinish(); let doc; let iframeNode, iframeBodyNode; + let inspector; let iframeSrc = "<style>" + "body {" + "margin:0;" + "height:100%;" + "background-color:red" + "}" + "</style>" + @@ -40,62 +41,68 @@ function test() content.location = "data:text/html," + docSrc; function setupTest() { iframeNode = doc.querySelector("iframe"); iframeBodyNode = iframeNode.contentDocument.querySelector("body"); ok(iframeNode, "we have the iframe node"); ok(iframeBodyNode, "we have the body node"); - openInspector(runTests); - } - - function runTests(inspector) - { - inspector.highlighter.unlock(); - executeSoon(function() { - inspector.highlighter.once("highlighting", isTheIframeSelected); - moveMouseOver(iframeNode, 1, 1); + openInspector(aInspector => { + inspector = aInspector; + // Make sure the highlighter is shown so we can disable transitions + inspector.toolbox.highlighter.showBoxModel(getNodeFront(doc.body)).then(() => { + getHighlighterOutline().setAttribute("disable-transitions", "true"); + runTests(); + }); }); } - function isTheIframeSelected() + function runTests() { - let inspector = getActiveInspector(); + inspector.toolbox.startPicker().then(() => { + moveMouseOver(iframeNode, 1, 1, isTheIframeHighlighted); + }); + } - is(inspector.selection.node, iframeNode, "selection matches node"); + function isTheIframeHighlighted() + { + let outlineRect = getHighlighterOutlineRect(); + let iframeRect = iframeNode.getBoundingClientRect(); + for (let dim of ["width", "height", "top", "left"]) { + is(Math.floor(outlineRect[dim]), Math.floor(iframeRect[dim]), "Outline dimension is correct"); + } + iframeNode.style.marginBottom = doc.defaultView.innerHeight + "px"; doc.defaultView.scrollBy(0, 40); - executeSoon(function() { - inspector.selection.once("new-node", isTheIframeContentSelected); - moveMouseOver(iframeNode, 40, 40); + moveMouseOver(iframeNode, 40, 40, isTheIframeContentHighlighted); + } + + function isTheIframeContentHighlighted() + { + is(getHighlitNode(), iframeBodyNode, "highlighter shows the right node"); + + // 184 == 200 + 11(border) + 13(padding) - 40(scroll) + let outlineRect = getHighlighterOutlineRect(); + is(outlineRect.height, 184, "highlighter height"); + + inspector.toolbox.stopPicker().then(() => { + let target = TargetFactory.forTab(gBrowser.selectedTab); + gDevTools.closeToolbox(target); + finishUp(); }); } - function isTheIframeContentSelected() + function finishUp() { - let inspector = getActiveInspector(); - is(inspector.selection.node, iframeBodyNode, "selection matches node"); - // 184 == 200 + 11(border) + 13(padding) - 40(scroll) - is(inspector.highlighter._highlightRect.height, 184, - "highlighter height"); - - let target = TargetFactory.forTab(gBrowser.selectedTab); - gDevTools.closeToolbox(target); - finishUp(); - } - - function finishUp() { - doc = iframeNode = iframeBodyNode = null; + doc = inspector = iframeNode = iframeBodyNode = null; gBrowser.removeCurrentTab(); finish(); } - - function moveMouseOver(aElement, x, y) + function moveMouseOver(aElement, x, y, cb) { EventUtils.synthesizeMouse(aElement, x, y, {type: "mousemove"}, aElement.ownerDocument.defaultView); + inspector.toolbox.once("picker-node-hovered", cb); } - } -
--- a/browser/devtools/inspector/test/browser_inspector_bug_699308_iframe_navigation.js +++ b/browser/devtools/inspector/test/browser_inspector_bug_699308_iframe_navigation.js @@ -3,58 +3,76 @@ function test() { let iframe; let iframeLoads = 0; let checksAfterLoads = false; let inspector; function startTest() { - openInspector(runInspectorTests); + openInspector(aInspector => { + inspector = aInspector; + runInspectorTests(); + }); } - function runInspectorTests(aInspector) { - inspector = aInspector; + function showHighlighter(cb) { + inspector.toolbox.startPicker().then(() => { + EventUtils.synthesizeMouse(content.document.body, 1, 1, + {type: "mousemove"}, content); + inspector.toolbox.once("picker-node-hovered", () => { + executeSoon(() => { + getHighlighterOutline().setAttribute("disable-transitions", "true"); + cb(); + }); + }); + }); + } + function runInspectorTests() { iframe = content.document.querySelector("iframe"); ok(iframe, "found the iframe element"); - ok(inspector.highlighter._highlighting, "Inspector is highlighting"); + showHighlighter(() => { + ok(isHighlighting(), "Inspector is highlighting"); - iframe.addEventListener("load", onIframeLoad, false); + iframe.addEventListener("load", onIframeLoad, false); - executeSoon(function() { - iframe.contentWindow.location = "javascript:location.reload()"; + executeSoon(function() { + iframe.contentWindow.location = "javascript:location.reload()"; + }); }); } function onIframeLoad() { if (++iframeLoads != 2) { executeSoon(function() { iframe.contentWindow.location = "javascript:location.reload()"; }); return; } iframe.removeEventListener("load", onIframeLoad, false); - ok(inspector.highlighter._highlighting, "Inspector is highlighting after iframe nav"); + ok(isHighlighting(), "Inspector is highlighting after iframe nav"); checksAfterLoads = true; finishTest(); } function finishTest() { is(iframeLoads, 2, "iframe loads"); ok(checksAfterLoads, "the Inspector tests got the chance to run after iframe reloads"); - iframe = null; - gBrowser.removeCurrentTab(); - executeSoon(finish); + inspector.toolbox.stopPicker().then(() => { + iframe = null; + gBrowser.removeCurrentTab(); + executeSoon(finish); + }); } waitForExplicitFinish(); gBrowser.selectedTab = gBrowser.addTab(); gBrowser.selectedBrowser.addEventListener("load", function onBrowserLoad() { gBrowser.selectedBrowser.removeEventListener("load", onBrowserLoad, true); waitForFocus(startTest, content);
deleted file mode 100644 --- a/browser/devtools/inspector/test/browser_inspector_bug_835722_infobar_reappears.js +++ /dev/null @@ -1,103 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ - -function test() { - let inspector, utils; - - function startLocationTests() { - openInspector(runInspectorTests); - } - - function runInspectorTests(aInspector) { - inspector = aInspector; - utils = inspector.panelWin - .QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowUtils); - ok(utils, "utils is defined"); - executeSoon(function() { - inspector.selection.once("new-node", onNewSelection); - info("selecting the DOCTYPE node"); - inspector.selection.setNode(content.document.doctype, "test"); - }); - } - - function sendMouseEvent(node, type, x, y) { - let rect = node.getBoundingClientRect(); - let left = rect.left + x; - let top = rect.top + y; - utils.sendMouseEventToWindow(type, left, top, 0, 1, 0, false, 0, 0); - } - - function onNewSelection() { - is(inspector.highlighter.isHidden(), true, - "The infobar should be hidden now on selecting a non element node."); - inspector.sidebar.select("ruleview"); - let ruleView = inspector.sidebar.getTab("ruleview"); - ruleView.addEventListener("mouseover", function onMouseOver() { - ruleView.removeEventListener("mouseover", onMouseOver, false); - is(inspector.highlighter.isHidden(), true, - "The infobar was hidden so mouseover on the rules view did nothing"); - executeSoon(mouseOutAndContinue); - }, false); - sendMouseEvent(ruleView, "mouseover", 10, 10); - } - - function mouseOutAndContinue() { - let ruleView = inspector.sidebar.getTab("ruleview"); - info("adding mouseout listener"); - ruleView.addEventListener("mouseout", function onMouseOut() { - info("mouseout happened"); - ruleView.removeEventListener("mouseout", onMouseOut, false); - is(inspector.highlighter.isHidden(), true, - "The infobar should not be visible after we mouseout of rules view"); - switchToWebConsole(); - }, false); - info("Synthesizing mouseout on " + ruleView); - sendMouseEvent(inspector._markupBox, "mousemove", 50, 50); - info("mouseout synthesized"); - } - - function switchToWebConsole() { - inspector.selection.once("new-node", function() { - is(inspector.highlighter.isHidden(), false, - "The infobar should be visible after we select a div."); - gDevTools.showToolbox(inspector.target, "webconsole").then(function() { - is(inspector.highlighter.isHidden(), true, - "The infobar should not be visible after we switched to webconsole"); - reloadAndWait(); - }); - }); - inspector.selection.setNode(content.document.querySelector("div"), "test"); - } - - function reloadAndWait() { - gBrowser.selectedBrowser.addEventListener("load", function onBrowserLoad() { - gBrowser.selectedBrowser.removeEventListener("load", onBrowserLoad, true); - waitForFocus(testAfterReload, content); - }, true); - content.location.reload(); - } - - function testAfterReload() { - is(inspector.highlighter.isHidden(), true, - "The infobar should not be visible after we reload with webconsole shown"); - testEnd(); - } - - function testEnd() { - gBrowser.removeCurrentTab(); - utils = null; - executeSoon(finish); - } - - waitForExplicitFinish(); - - gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.selectedBrowser.addEventListener("load", function onBrowserLoad() { - gBrowser.selectedBrowser.removeEventListener("load", onBrowserLoad, true); - waitForFocus(startLocationTests, content); - }, true); - - content.location = "data:text/html,<!DOCTYPE html><div>Infobar should not " + - "reappear</div><p>init</p>"; -}
--- a/browser/devtools/inspector/test/browser_inspector_bug_840156_destroy_after_navigation.js +++ b/browser/devtools/inspector/test/browser_inspector_bug_840156_destroy_after_navigation.js @@ -28,16 +28,23 @@ function test() { // open devtools panel deferred.promise .then(function () gDevTools.showToolbox(target, null, Toolbox.HostType.BOTTOM)) .then(function (aToolbox) { toolbox = aToolbox; }) // select the inspector .then(function () toolbox.selectTool("inspector")) + // wait until inspector ready + .then(function () { + let deferred = promise.defer(); + toolbox.getPanel("inspector").once("inspector-updated", deferred.resolve); + return deferred.promise; + }) + // navigate to URL_2 .then(function () { let deferred = promise.defer(); target.once("navigate", function () deferred.resolve()); browser.loadURI(URL_2); return deferred.promise; })
--- a/browser/devtools/inspector/test/browser_inspector_bug_848731_reset_selection_on_delete.js +++ b/browser/devtools/inspector/test/browser_inspector_bug_848731_reset_selection_on_delete.js @@ -134,13 +134,10 @@ function test() { // Right node selected? is(inspector.selection.nodeFront, getNodeFront(node), "The right node is selected"); // breadcrumbs updated? let breadcrumbs = inspector.panelDoc.getElementById("inspector-breadcrumbs"); is(breadcrumbs.querySelector("button[checked=true]").textContent, crumbLabel, "The right breadcrumb is selected"); - - // Highlighter is shown? - ok(!inspector.highlighter.isHidden(), "The highlighter is shown"); } }
--- a/browser/devtools/inspector/test/browser_inspector_bug_922125_destroy_on_navigate.js +++ b/browser/devtools/inspector/test/browser_inspector_bug_922125_destroy_on_navigate.js @@ -29,17 +29,16 @@ function test() { deferred.promise.then(() => { return gDevTools.showToolbox(target, null, Toolbox.HostType.BOTTOM); }).then(aToolbox => { toolbox = aToolbox; }).then(() => { // select the inspector return toolbox.selectTool("inspector").then(i => { inspector = i; - // Verify we are on page one let testNode = content.document.querySelector("#one"); ok(testNode, "We have the test node on page 1"); assertMarkupViewIsLoaded(); }); }).then(() => { // navigate to URL_2
--- a/browser/devtools/inspector/test/browser_inspector_changes.js +++ b/browser/devtools/inspector/test/browser_inspector_changes.js @@ -27,44 +27,47 @@ function test() { } } return null; } function runInspectorTests(aInspector) { inspector = aInspector; - inspector.sidebar.once("computedview-ready", function() { + inspector.sidebar.once("computedview-ready", () => { info("Computed View ready"); inspector.sidebar.select("computedview"); testDiv = doc.getElementById("testdiv"); testDiv.style.fontSize = "10px"; // Start up the style inspector panel... - inspector.once("computed-view-refreshed", computedStylePanelTests); - + inspector.once("computed-view-refreshed", () => { + executeSoon(computedStylePanelTests); + }); inspector.selection.setNode(testDiv); }); } function computedStylePanelTests() { let computedview = inspector.sidebar.getWindowForTab("computedview").computedview; ok(computedview, "Style Panel has a cssHtmlTree"); let fontSize = getComputedPropertyValue("font-size"); is(fontSize, "10px", "Style inspector should be showing the correct font size."); testDiv.style.cssText = "font-size: 15px; color: red;"; // Wait until layout-change fires from mutation to skip earlier refresh event inspector.once("layout-change", () => { - inspector.once("computed-view-refreshed", computedStylePanelAfterChange); + inspector.once("computed-view-refreshed", () => { + executeSoon(computedStylePanelAfterChange); + }); }); } function computedStylePanelAfterChange() { let fontSize = getComputedPropertyValue("font-size"); is(fontSize, "15px", "Style inspector should be showing the new font size."); @@ -74,22 +77,21 @@ function test() { computedStylePanelNotActive(); } function computedStylePanelNotActive() { // Tests changes made while the style panel is not active. inspector.sidebar.select("ruleview"); - testDiv.style.fontSize = "20px"; - testDiv.style.color = "blue"; - testDiv.style.textAlign = "center"; + testDiv.style.cssText = "font-size: 20px; color: blue; text-align: center"; - inspector.once("computed-view-refreshed", computedStylePanelAfterSwitch); - inspector.sidebar.select("computedview"); + inspector.once("computed-view-refreshed", () => { + executeSoon(computedStylePanelAfterSwitch); + }); } function computedStylePanelAfterSwitch() { let fontSize = getComputedPropertyValue("font-size"); is(fontSize, "20px", "Style inspector should be showing the new font size."); let color = getComputedPropertyValue("color"); @@ -105,23 +107,21 @@ function test() { { inspector.sidebar.select("ruleview"); let ruleview = inspector.sidebar.getWindowForTab("ruleview").ruleview; ok(ruleview, "Style Panel has a ruleview"); let propView = getInspectorRuleProp("text-align"); is(propView.value, "center", "Style inspector should be showing the new text align."); - testDiv.style.textAlign = "right"; - testDiv.style.color = "lightgoldenrodyellow"; - testDiv.style.fontSize = "3em"; - testDiv.style.textTransform = "uppercase"; + testDiv.style.cssText = "font-size: 3em; color: lightgoldenrodyellow; text-align: right; text-transform: uppercase"; - - inspector.once("rule-view-refreshed", rulePanelAfterChange); + inspector.once("rule-view-refreshed", () => { + executeSoon(rulePanelAfterChange); + }); } function rulePanelAfterChange() { let propView = getInspectorRuleProp("text-align"); is(propView.value, "right", "Style inspector should be showing the new text align."); let propView = getInspectorRuleProp("color");
--- a/browser/devtools/inspector/test/browser_inspector_destroyselection.js +++ b/browser/devtools/inspector/test/browser_inspector_destroyselection.js @@ -1,15 +1,14 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ function test() { waitForExplicitFinish(); - //ignoreAllUncaughtExceptions(); let node, iframe, inspector; gBrowser.selectedTab = gBrowser.addTab(); gBrowser.selectedBrowser.addEventListener("load", function onload() { gBrowser.selectedBrowser.removeEventListener("load", onload, true); waitForFocus(setupTest, content); }, true); @@ -23,27 +22,31 @@ function test() openInspector(runTests); } function runTests(aInspector) { inspector = aInspector; inspector.selection.setNode(node); - iframe.parentNode.removeChild(iframe); - iframe = null; + inspector.once("inspector-updated", () => { + iframe.parentNode.removeChild(iframe); + iframe = null; - let tmp = {}; - Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm", tmp); - let lh = new tmp.LayoutHelpers(window.content); - ok(!lh.isNodeConnected(node), "Node considered as disconnected."); - ok(!inspector.selection.isConnected(), "Selection considered as disconnected"); + let tmp = {}; + Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm", tmp); + let lh = new tmp.LayoutHelpers(window.content); + ok(!lh.isNodeConnected(node), "Node considered as disconnected."); + ok(!inspector.selection.isConnected(), "Selection considered as disconnected"); - finishUp(); + inspector.once("inspector-updated", () => { + finishUp(); + }); + }); } function finishUp() { - node = null; + node = inspector = null; gBrowser.removeCurrentTab(); finish(); } }
--- a/browser/devtools/inspector/test/browser_inspector_highlighter.js +++ b/browser/devtools/inspector/test/browser_inspector_highlighter.js @@ -1,22 +1,21 @@ /* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ let doc; let h1; -let div; +let inspector; -function createDocument() -{ +function createDocument() { let div = doc.createElement("div"); - let h1 = doc.createElement("h1"); + h1 = doc.createElement("h1"); let p1 = doc.createElement("p"); let p2 = doc.createElement("p"); let div2 = doc.createElement("div"); let p3 = doc.createElement("p"); doc.title = "Inspector Highlighter Meatballs"; h1.textContent = "Inspector Tree Selection Test"; p1.textContent = "This is some example text"; p2.textContent = "Lorem ipsum dolor sit amet, consectetur adipisicing " + @@ -43,135 +42,90 @@ function createDocument() div.appendChild(p1); div.appendChild(p2); div2.appendChild(p3); div3.appendChild(p4); doc.body.appendChild(div); doc.body.appendChild(div2); doc.body.appendChild(div3); - openInspector(setupHighlighterTests); -} - -function setupHighlighterTests() -{ - h1 = doc.querySelector("h1"); - ok(h1, "we have the header"); - - let i = getActiveInspector(); - i.selection.setNode(div); - i.highlighter.unlockAndFocus(); - i.highlighter.outline.setAttribute("disable-transitions", "true"); - - executeSoon(function() { - i.selection.once("new-node", performToggleComparisons); - EventUtils.synthesizeMouse(h1, 2, 2, {type: "mousemove"}, content); + openInspector(aInspector => { + inspector = aInspector; + inspector.selection.setNode(div, null); + inspector.once("inspector-updated", () => { + getHighlighterOutline().setAttribute("disable-transitions", "true"); + inspector.toolbox.startPicker().then(testMouseOverH1Highlights); + }); }); } -function performToggleComparisons(evt) -{ - let i = getActiveInspector(); +function testMouseOverH1Highlights() { + inspector.toolbox.once("picker-node-hovered", () => { + ok(isHighlighting(), "Highlighter is shown"); + is(getHighlitNode(), h1, "Highlighter's outline correspond to the selected node"); + testOutlineDimensions(); + }); - i.highlighter.toggleLockState(); - ok(i.highlighter.locked, "highlighter locks"); - is(i.selection.node, div); - i.highlighter.toggleLockState(); - ok(!i.highlighter.locked, "highlighter unlocks"); - - i.highlighter.toggleLockState(); - ok(i.highlighter.locked, "highlighter locks if selection is unchanged"); - i.highlighter.toggleLockState(); - - executeSoon(function() { - i.selection.once("new-node", performTestComparisons); - EventUtils.synthesizeMouse(h1, 2, 2, {type: "mousemove"}, content); - }); + EventUtils.synthesizeMouse(h1, 2, 2, {type: "mousemove"}, content); } -function performTestComparisons(evt) -{ - let i = getActiveInspector(); - i.highlighter.lock(); - ok(isHighlighting(), "highlighter is highlighting"); - is(getHighlitNode(), h1, "highlighter matches selection") - is(i.selection.node, h1, "selection matches node"); - is(i.selection.node, getHighlitNode(), "selection matches highlighter"); - - - div = doc.querySelector("div#checkOutThisWickedSpread"); +function testOutlineDimensions() { + let h1Dims = h1.getBoundingClientRect(); + let h1Width = h1Dims.width; + let h1Height = h1Dims.height; - executeSoon(function() { - i.selection.once("new-node", finishTestComparisons); - i.selection.setNode(div); - }); -} - -function finishTestComparisons() -{ - let i = getActiveInspector(); - - // get dimensions of div element - let divDims = div.getBoundingClientRect(); - let divWidth = divDims.width; - let divHeight = divDims.height; - - // get dimensions of the outline - let outlineDims = i.highlighter.outline.getBoundingClientRect(); + let outlineDims = getHighlighterOutlineRect(); let outlineWidth = outlineDims.width; let outlineHeight = outlineDims.height; // Disabled due to bug 716245 - //is(outlineWidth, divWidth, "outline width matches dimensions of element (no zoom)"); - //is(outlineHeight, divHeight, "outline height matches dimensions of element (no zoom)"); + is(outlineWidth, h1Width, "outline width matches dimensions of element (no zoom)"); + is(outlineHeight, h1Height, "outline height matches dimensions of element (no zoom)"); // zoom the page by a factor of 2 let contentViewer = gBrowser.selectedBrowser.docShell.contentViewer .QueryInterface(Ci.nsIMarkupDocumentViewer); contentViewer.fullZoom = 2; // We wait at least 500ms to make sure the highlighter is not "mutting" the // resize event window.setTimeout(function() { - // check what zoom factor we're at, should be 2 - let zoom = i.highlighter.zoom; - is(zoom, 2, "zoom is 2?"); + // simulate the zoomed dimensions of the div element + let h1Dims = h1.getBoundingClientRect(); + // There seems to be some very minor differences in the floats, so let's + // floor the values + let h1Width = Math.floor(h1Dims.width * contentViewer.fullZoom); + let h1Height = Math.floor(h1Dims.height * contentViewer.fullZoom); - // simulate the zoomed dimensions of the div element - let divDims = div.getBoundingClientRect(); - let divWidth = divDims.width * zoom; - let divHeight = divDims.height * zoom; - - // now zoomed, get new dimensions the outline - let outlineDims = i.highlighter.outline.getBoundingClientRect(); - let outlineWidth = outlineDims.width; - let outlineHeight = outlineDims.height; + let outlineDims = getHighlighterOutlineRect(); + let outlineWidth = Math.floor(outlineDims.width); + let outlineHeight = Math.floor(outlineDims.height); // Disabled due to bug 716245 - //is(outlineWidth, divWidth, "outline width matches dimensions of element (no zoom)"); - //is(outlineHeight, divHeight, "outline height matches dimensions of element (no zoom)"); + is(outlineWidth, h1Width, "outline width matches dimensions of element (zoomed)"); + is(outlineHeight, h1Height, "outline height matches dimensions of element (zoomed)"); - doc = h1 = div = null; executeSoon(finishUp); }, 500); } function finishUp() { - let target = TargetFactory.forTab(gBrowser.selectedTab); - gDevTools.closeToolbox(target); - gBrowser.removeCurrentTab(); - finish(); + inspector.toolbox.stopPicker().then(() => { + doc = h1 = inspector = null; + let target = TargetFactory.forTab(gBrowser.selectedTab); + gDevTools.closeToolbox(target); + gBrowser.removeCurrentTab(); + finish(); + }); } -function test() -{ +function test() { waitForExplicitFinish(); gBrowser.selectedTab = gBrowser.addTab(); gBrowser.selectedBrowser.addEventListener("load", function() { gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true); doc = content.document; waitForFocus(createDocument, content); }, true); content.location = "data:text/html,basic tests for inspector"; } -
deleted file mode 100644 --- a/browser/devtools/inspector/test/browser_inspector_highlighter_autohide.js +++ /dev/null @@ -1,46 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ - - -function test() -{ - let toolbox; - let inspector; - - waitForExplicitFinish(); - - gBrowser.selectedTab = gBrowser.addTab(); - gBrowser.selectedBrowser.addEventListener("load", function onload() { - gBrowser.selectedBrowser.removeEventListener("load", onload, true); - waitForFocus(startInspector, content); - }, true); - content.location = "data:text/html,mop" - - function startInspector() { - info("Tab loaded"); - openInspector(function(aInspector) { - inspector = aInspector; - ok(!inspector.highlighter.hidden, "Highlighter is visible"); - toolbox = inspector._toolbox; - toolbox.once("webconsole-selected", onWebConsoleSelected); - toolbox.selectTool("webconsole"); - }); - } - - function onWebConsoleSelected() { - executeSoon(function() { - ok(inspector.highlighter.hidden, "Highlighter is hidden"); - toolbox.once("inspector-selected", onInspectorSelected); - toolbox.selectTool("inspector"); - }); - } - - function onInspectorSelected() { - executeSoon(function() { - ok(!inspector.highlighter.hidden, "Highlighter is visible once inspector reopen"); - gBrowser.removeCurrentTab(); - finish(); - }); - } -} -
--- a/browser/devtools/inspector/test/browser_inspector_iframeTest.js +++ b/browser/devtools/inspector/test/browser_inspector_iframeTest.js @@ -4,19 +4,19 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ let doc; let div1; let div2; let iframe1; let iframe2; +let inspector; -function createDocument() -{ +function createDocument() { doc.title = "Inspector iframe Tests"; iframe1 = doc.createElement('iframe'); iframe1.addEventListener("load", function () { iframe1.removeEventListener("load", arguments.callee, false); div1 = iframe1.contentDocument.createElement('div'); @@ -27,95 +27,90 @@ function createDocument() iframe2.addEventListener('load', function () { iframe2.removeEventListener("load", arguments.callee, false); div2 = iframe2.contentDocument.createElement('div'); div2.textContent = 'nested div'; iframe2.contentDocument.body.appendChild(div2); - openInspector(runIframeTests); + // Open the inspector, start the picker mode, and start the tests + openInspector(aInspector => { + inspector = aInspector; + inspector.toolbox.startPicker().then(runTests); + }); }, false); iframe2.src = 'data:text/html,nested iframe'; iframe1.contentDocument.body.appendChild(iframe2); }, false); iframe1.src = 'data:text/html,little iframe'; doc.body.appendChild(iframe1); } -function moveMouseOver(aElement) -{ +function moveMouseOver(aElement, cb) { EventUtils.synthesizeMouse(aElement, 2, 2, {type: "mousemove"}, aElement.ownerDocument.defaultView); + inspector.toolbox.once("picker-node-hovered", () => { + executeSoon(cb); + }); } -function runIframeTests() -{ - getActiveInspector().highlighter.unlock(); - getActiveInspector().selection.once("new-node", performTestComparisons1); - moveMouseOver(div1) +function runTests() { + testDiv1Highlighter(); } -function performTestComparisons1() -{ - let i = getActiveInspector(); - is(i.selection.node, div1, "selection matches div1 node"); - is(getHighlitNode(), div1, "highlighter matches selection"); - - i.selection.once("new-node", performTestComparisons2); - executeSoon(function() { - moveMouseOver(div2); +function testDiv1Highlighter() { + moveMouseOver(div1, () => { + getHighlighterOutline().setAttribute("disable-transitions", "true"); + is(getHighlitNode(), div1, "highlighter matches selection"); + testDiv2Highlighter(); }); } -function performTestComparisons2() -{ - let i = getActiveInspector(); +function testDiv2Highlighter() { + moveMouseOver(div2, () => { + is(getHighlitNode(), div2, "highlighter matches selection"); + selectRoot(); + }); +} - is(i.selection.node, div2, "selection matches div2 node"); - is(getHighlitNode(), div2, "highlighter matches selection"); - - selectRoot(); +function selectRoot() { + // Select the root document element to clear the breadcrumbs. + inspector.selection.setNode(doc.documentElement); + inspector.once("inspector-updated", selectIframe); } -function selectRoot() -{ - // Select the root document element to clear the breadcrumbs. - let i = getActiveInspector(); - i.selection.setNode(doc.documentElement); - i.once("inspector-updated", selectIframe); +function selectIframe() { + // Directly select an element in an iframe (without navigating to it + // with mousemoves). + inspector.selection.setNode(div2); + inspector.once("inspector-updated", () => { + let breadcrumbs = inspector.breadcrumbs; + is(breadcrumbs.nodeHierarchy.length, 9, "Should have 9 items"); + finishUp(); + }); } -function selectIframe() -{ - // Directly select an element in an iframe (without navigating to it - // with mousemoves). - let i = getActiveInspector(); - i.selection.setNode(div2); - i.once("inspector-updated", () => { - let breadcrumbs = i.breadcrumbs; - is(breadcrumbs.nodeHierarchy.length, 9, "Should have 9 items"); +function finishUp() { + inspector.toolbox.stopPicker().then(() => { + doc = div1 = div2 = iframe1 = iframe2 = inspector = null; + let target = TargetFactory.forTab(gBrowser.selectedTab); + gDevTools.closeToolbox(target); + gBrowser.removeCurrentTab(); finish(); }); } function test() { waitForExplicitFinish(); gBrowser.selectedTab = gBrowser.addTab(); gBrowser.selectedBrowser.addEventListener("load", function() { gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true); doc = content.document; gBrowser.selectedBrowser.focus(); createDocument(); }, true); content.location = "data:text/html,iframe tests for inspector"; - - registerCleanupFunction(function () { - let target = TargetFactory.forTab(gBrowser.selectedTab); - gDevTools.closeToolbox(target); - gBrowser.removeCurrentTab(); - }); } -
--- a/browser/devtools/inspector/test/browser_inspector_infobar.js +++ b/browser/devtools/inspector/test/browser_inspector_infobar.js @@ -1,13 +1,12 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ -function test() -{ +function test() { waitForExplicitFinish(); ignoreAllUncaughtExceptions(); let doc; let nodes; let cursor; let inspector; @@ -18,64 +17,68 @@ function test() waitForFocus(setupInfobarTest, content); }, true); let style = "body{width:100%;height: 100%} div {position: absolute;height: 100px;width: 500px}#bottom {bottom: 0px}#vertical {height: 100%}#farbottom{bottom: -200px}"; let html = "<style>" + style + "</style><div id=vertical></div><div id=top class='class1 class2'></div><div id=bottom></div><div id=farbottom></div>" content.location = "data:text/html," + encodeURIComponent(html); - function setupInfobarTest() - { + function setupInfobarTest() { nodes = [ {node: doc.querySelector("#top"), position: "bottom", tag: "DIV", id: "#top", classes: ".class1.class2"}, {node: doc.querySelector("#vertical"), position: "overlap", tag: "DIV", id: "#vertical", classes: ""}, {node: doc.querySelector("#bottom"), position: "top", tag: "DIV", id: "#bottom", classes: ""}, {node: doc.querySelector("body"), position: "overlap", tag: "BODY", id: "", classes: ""}, {node: doc.querySelector("#farbottom"), position: "top", tag: "DIV", id: "#farbottom", classes: ""}, ] for (let i = 0; i < nodes.length; i++) { ok(nodes[i].node, "node " + i + " found"); } openInspector(runTests); } - function runTests(aInspector) - { + function mouseOverContainerToShowHighlighter(node, cb) { + let container = getContainerForRawNode(inspector.markup, node); + EventUtils.synthesizeMouse(container.tagLine, 2, 2, {type: "mousemove"}, + inspector.markup.doc.defaultView); + executeSoon(cb); + } + + function runTests(aInspector) { inspector = aInspector; - cursor = 0; - executeSoon(function() { - inspector.selection.setNode(nodes[0].node, ""); - nodeSelected(); + inspector.selection.setNode(content.document.querySelector("body")); + inspector.once("inspector-updated", () => { + cursor = 0; + executeSoon(function() { + mouseOverContainerToShowHighlighter(nodes[0].node, nodeSelected); + }); }); } - function nodeSelected() - { + function nodeSelected() { executeSoon(function() { performTest(); cursor++; if (cursor >= nodes.length) { finishUp(); } else { let node = nodes[cursor].node; - inspector.selection.setNode(node, ""); - nodeSelected(); + mouseOverContainerToShowHighlighter(node, nodeSelected); } }); } - function performTest() - { + function performTest() { let browser = gBrowser.selectedBrowser; let stack = browser.parentNode; - let container = stack.querySelector(".highlighter-nodeinfobar-container"); + let container = stack.querySelector(".highlighter-nodeinfobar-positioner"); is(container.getAttribute("position"), nodes[cursor].position, "node " + cursor + ": position matches."); let tagNameLabel = stack.querySelector(".highlighter-nodeinfobar-tagname"); is(tagNameLabel.textContent, nodes[cursor].tag, "node " + cursor + ": tagName matches."); let idLabel = stack.querySelector(".highlighter-nodeinfobar-id"); is(idLabel.textContent, nodes[cursor].id, "node " + cursor + ": id matches."); @@ -84,9 +87,8 @@ function test() } function finishUp() { doc = nodes = null; gBrowser.removeCurrentTab(); finish(); } } -
--- a/browser/devtools/inspector/test/browser_inspector_initialization.js +++ b/browser/devtools/inspector/test/browser_inspector_initialization.js @@ -30,53 +30,43 @@ function createDocument() function startInspectorTests(toolbox) { let inspector = toolbox.getCurrentPanel(); ok(true, "Inspector started, and notification received."); ok(inspector, "Inspector instance is accessible"); ok(inspector.isReady, "Inspector instance is ready"); is(inspector.target.tab, gBrowser.selectedTab, "Valid target"); - ok(inspector.highlighter, "Highlighter is up"); let p = doc.querySelector("p"); inspector.selection.setNode(p); inspector.once("inspector-updated", () => { - testHighlighter(p); testMarkupView(p); testBreadcrumbs(p); let span = doc.querySelector("span"); span.scrollIntoView(); inspector.selection.setNode(span); inspector.once("inspector-updated", () => { - testHighlighter(span); testMarkupView(span); testBreadcrumbs(span); toolbox.once("destroyed", function() { ok("true", "'destroyed' notification received."); let target = TargetFactory.forTab(gBrowser.selectedTab); ok(!gDevTools.getToolbox(target), "Toolbox destroyed."); executeSoon(runContextMenuTest); }); toolbox.destroy(); }); }); } - -function testHighlighter(node) -{ - ok(isHighlighting(), "Highlighter is highlighting"); - is(getHighlitNode(), node, "Right node is highlighted"); -} - let callNo = 0; function testMarkupView(node) { let i = getActiveInspector(); try { is(i.markup._selectedContainer.node.rawNode(), node, "Right node is selected in the markup view"); } catch(ex) { console.error(ex); } } @@ -107,32 +97,30 @@ function _clickOnInspectMenuItem(node) { function runContextMenuTest() { salutation = doc.getElementById("salutation"); _clickOnInspectMenuItem(salutation).then(testInitialNodeIsSelected); } function testInitialNodeIsSelected() { - testHighlighter(salutation); testMarkupView(salutation); testBreadcrumbs(salutation); inspectNodesFromContextTestWhileOpen(); } function inspectNodesFromContextTestWhileOpen() { let closing = doc.getElementById("closing"); getActiveInspector().selection.once("new-node", function() { ok(true, "Get selection's 'new-node' selection"); executeSoon(function() { - testHighlighter(closing); testMarkupView(closing); testBreadcrumbs(closing); - finishInspectorTests(); + getActiveInspector().once("inspector-updated", finishInspectorTests) } )}); _clickOnInspectMenuItem(closing); } function finishInspectorTests(subject, topic, aWinIdString) { gBrowser.removeCurrentTab();
--- a/browser/devtools/inspector/test/browser_inspector_invalidate.js +++ b/browser/devtools/inspector/test/browser_inspector_invalidate.js @@ -1,45 +1,48 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ function test() { - let doc; let div; let inspector; - function createDocument() - { + function createDocument() { div = doc.createElement("div"); div.setAttribute("style", "width: 100px; height: 100px; background:yellow;"); doc.body.appendChild(div); - openInspector(runTest); + openInspector(aInspector => { + inspector = aInspector; + inspector.toolbox.highlighter.showBoxModel(getNodeFront(div)).then(runTest); + }); } - function runTest(inspector) - { - inspector.selection.setNode(div); - - executeSoon(function() { - let outline = inspector.highlighter.outline; - is(outline.style.width, "100px", "selection has the right width"); + function runTest() { + let outline = getHighlighterOutline(); + is(outline.style.width, "100px", "outline has the right width"); - div.style.width = "200px"; - function pollTest() { - if (outline.style.width == "100px") { - setTimeout(pollTest, 10); - return; - } - is(outline.style.width, "200px", "selection updated"); - gBrowser.removeCurrentTab(); - finish(); + div.style.width = "200px"; + function pollTest() { + if (outline.style.width == "100px") { + setTimeout(pollTest, 10); + return; } - setTimeout(pollTest, 10); + is(outline.style.width, "200px", "outline updated"); + finishUp(); + } + setTimeout(pollTest, 10); + } + + function finishUp() { + inspector.toolbox.highlighter.hideBoxModel().then(() => { + doc = div = inspector = null; + gBrowser.removeCurrentTab(); + finish(); }); } waitForExplicitFinish(); gBrowser.selectedTab = gBrowser.addTab(); gBrowser.selectedBrowser.addEventListener("load", function() { gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true); doc = content.document;
--- a/browser/devtools/inspector/test/browser_inspector_menu.js +++ b/browser/devtools/inspector/test/browser_inspector_menu.js @@ -98,39 +98,42 @@ function test() { function() { copyUniqueSelector.doCommand(); }, testDeleteNode, testDeleteNode); } function testDeleteNode() { let deleteNode = inspector.panelDoc.getElementById("node-menu-delete"); ok(deleteNode, "the popup menu has a delete menu item"); - inspector.once("markupmutation", deleteTest); + inspector.once("inspector-updated", deleteTest); let commandEvent = document.createEvent("XULCommandEvent"); commandEvent.initCommandEvent("command", true, true, window, 0, false, false, false, false, null); deleteNode.dispatchEvent(commandEvent); } function deleteTest() { let p = doc.querySelector("P"); is(p, null, "node deleted"); deleteRootNode(); } function deleteRootNode() { inspector.selection.setNode(doc.documentElement); - let deleteNode = inspector.panelDoc.getElementById("node-menu-delete"); - let commandEvent = inspector.panelDoc.createEvent("XULCommandEvent"); - commandEvent.initCommandEvent("command", true, true, window, 0, false, false, - false, false, null); - deleteNode.dispatchEvent(commandEvent); - executeSoon(isRootStillAlive); + + inspector.once("inspector-updated", () => { + let deleteNode = inspector.panelDoc.getElementById("node-menu-delete"); + let commandEvent = inspector.panelDoc.createEvent("XULCommandEvent"); + commandEvent.initCommandEvent("command", true, true, window, 0, false, false, + false, false, null); + deleteNode.dispatchEvent(commandEvent); + executeSoon(isRootStillAlive); + }); } function isRootStillAlive() { ok(doc.documentElement, "Document element still alive."); gBrowser.removeCurrentTab(); finish(); }
--- a/browser/devtools/inspector/test/browser_inspector_pseudoClass_menu.js +++ b/browser/devtools/inspector/test/browser_inspector_pseudoClass_menu.js @@ -32,17 +32,17 @@ function test() { openInspector(selectNode); } function selectNode(aInspector) { inspector = aInspector; inspector.selection.setNode(div); - performTests(); + inspector.once("inspector-updated", performTests); } function performTests() { menu = inspector.panelDoc.getElementById("inspector-node-popup"); menu.addEventListener("popupshowing", testMenuItems, true); menu.openPopup(); }
--- a/browser/devtools/inspector/test/browser_inspector_pseudoclass_lock.js +++ b/browser/devtools/inspector/test/browser_inspector_pseudoclass_lock.js @@ -62,109 +62,119 @@ function selectNode(aInspector) function performTests() { // toggle the class inspector.togglePseudoClass(pseudo); // Wait for the "pseudoclass" event so we know the // inspector has been told of the pseudoclass lock change. inspector.selection.once("pseudoclass", () => { - // Give the rule view time to update. inspector.once("rule-view-refreshed", () => { - testAdded(); - // Change the pseudo class and give the rule view time to update. - inspector.togglePseudoClass(pseudo); - inspector.selection.once("pseudoclass", () => { - inspector.once("rule-view-refreshed", () => { - testRemoved(); - testRemovedFromUI(); - - // toggle it back on - inspector.togglePseudoClass(pseudo); - inspector.selection.once("pseudoclass", () => { - testNavigate(() => { - // close the inspector - finishUp(); + testAdded(() => { + // Change the pseudo class and give the rule view time to update. + inspector.togglePseudoClass(pseudo); + inspector.selection.once("pseudoclass", () => { + inspector.once("rule-view-refreshed", () => { + testRemoved(); + testRemovedFromUI(() => { + // toggle it back on + inspector.togglePseudoClass(pseudo); + inspector.selection.once("pseudoclass", () => { + inspector.once("rule-view-refreshed", () => { + testNavigate(() => { + // close the inspector + finishUp(); + }); + }); + }); }); }); }); }); }); }); } function testNavigate(callback) { inspector.selection.setNode(parentDiv); inspector.once("inspector-updated", () => { // make sure it's still on after naving to parent is(DOMUtils.hasPseudoClassLock(div, pseudo), true, - "pseudo-class lock is still applied after inspecting ancestor"); + "pseudo-class lock is still applied after inspecting ancestor"); inspector.selection.setNode(div2); inspector.selection.once("pseudoclass", () => { // make sure it's removed after naving to a non-hierarchy node is(DOMUtils.hasPseudoClassLock(div, pseudo), false, - "pseudo-class lock is removed after inspecting sibling node"); + "pseudo-class lock is removed after inspecting sibling node"); // toggle it back on inspector.selection.setNode(div); inspector.once("inspector-updated", () => { inspector.togglePseudoClass(pseudo); - inspector.selection.once("pseudoclass", () => { - callback(); - }); + inspector.once("computed-view-refreshed", callback); }); }); }); } -function testAdded() +function showPickerOn(node, cb) +{ + let highlighter = inspector.toolbox.highlighter; + highlighter.showBoxModel(getNodeFront(node)).then(cb); +} + +function testAdded(cb) { // lock is applied to it and ancestors let node = div; do { is(DOMUtils.hasPseudoClassLock(node, pseudo), true, - "pseudo-class lock has been applied"); + "pseudo-class lock has been applied"); node = node.parentNode; } while (node.parentNode) - // infobar selector contains pseudo-class - let pseudoClassesBox = getActiveInspector().highlighter.nodeInfo.pseudoClassesBox; - is(pseudoClassesBox.textContent, pseudo, "pseudo-class in infobar selector"); + // ruleview contains pseudo-class rule + let rules = ruleview.element.querySelectorAll(".ruleview-rule.theme-separator"); + is(rules.length, 3, "rule view is showing 3 rules for pseudo-class locked div"); + is(rules[1]._ruleEditor.rule.selectorText, "div:hover", "rule view is showing " + pseudo + " rule"); - // ruleview contains pseudo-class rule - is(ruleview.element.children.length, 3, - "rule view is showing 3 rules for pseudo-class locked div"); - - is(ruleview.element.children[1]._ruleEditor.rule.selectorText, - "div:hover", "rule view is showing " + pseudo + " rule"); + // Show the highlighter by starting the pick mode and hovering over the div + showPickerOn(div, () => { + // infobar selector contains pseudo-class + let pseudoClassesBox = getHighlighter().querySelector(".highlighter-nodeinfobar-pseudo-classes"); + is(pseudoClassesBox.textContent, pseudo, "pseudo-class in infobar selector"); + cb(); + }); } function testRemoved() { // lock removed from node and ancestors let node = div; do { is(DOMUtils.hasPseudoClassLock(node, pseudo), false, "pseudo-class lock has been removed"); node = node.parentNode; } while (node.parentNode) } -function testRemovedFromUI() +function testRemovedFromUI(cb) { - // infobar selector doesn't contain pseudo-class - let pseudoClassesBox = getActiveInspector().highlighter.nodeInfo.pseudoClassesBox; - is(pseudoClassesBox.textContent, "", "pseudo-class removed from infobar selector"); + // ruleview no longer contains pseudo-class rule + let rules = ruleview.element.querySelectorAll(".ruleview-rule.theme-separator"); + is(rules.length, 2, "rule view is showing 2 rules after removing lock"); - // ruleview no longer contains pseudo-class rule - is(ruleview.element.children.length, 2, - "rule view is showing 2 rules after removing lock"); + showPickerOn(div, () => { + let pseudoClassesBox = getHighlighter().querySelector(".highlighter-nodeinfobar-pseudo-classes"); + is(pseudoClassesBox.textContent, "", "pseudo-class removed from infobar selector"); + cb(); + }); } function finishUp() { gDevTools.once("toolbox-destroyed", function() { testRemoved(); inspector = ruleview = null; doc = div = null;
--- a/browser/devtools/inspector/test/browser_inspector_reload.js +++ b/browser/devtools/inspector/test/browser_inspector_reload.js @@ -39,13 +39,14 @@ function test() { function onReload() { info("Page reloaded"); let p = content.document.querySelector("p"); inspector.selection.setNode(p); inspector.once("inspector-updated", () => { is(inspector.selection.node, p, "Node re-selected."); toolbox.destroy(); + toolbox = inspector = null; gBrowser.removeCurrentTab(); finish(); }); } }
--- a/browser/devtools/inspector/test/browser_inspector_scrolling.js +++ b/browser/devtools/inspector/test/browser_inspector_scrolling.js @@ -28,19 +28,18 @@ function createDocument() iframe.src = "data:text/html,foo bar"; doc.body.appendChild(iframe); } function inspectNode(aInspector) { inspector = aInspector; - inspector.highlighter.once("locked", performScrollingTest); + inspector.once("inspector-updated", performScrollingTest); executeSoon(function() { - inspector.highlighter.unlock(); inspector.selection.setNode(div, ""); }); } function performScrollingTest() { executeSoon(function() { // FIXME: this will fail on retina displays. EventUtils will only scroll
--- a/browser/devtools/inspector/test/browser_inspector_select_last_selected.js +++ b/browser/devtools/inspector/test/browser_inspector_select_last_selected.js @@ -25,27 +25,26 @@ function test() { }, true); content.location = page1; function startTests() { testSameNodeSelectedOnPageReload(); } function endTests() { - inspector.destroy().then(() => - toolbox.destroy() - ).then(() => { + inspector.destroy(); + toolbox.destroy().then(() => { toolbox = inspector = page1 = page2 = null; gBrowser.removeCurrentTab(); finish(); }); } function loadPageAnd(page, callback) { - inspector.once("markuploaded", () => { + inspector.once("new-root", () => { callback(); }); if (page) { content.location = page; } else { content.location.reload(); }
--- a/browser/devtools/inspector/test/head.js +++ b/browser/devtools/inspector/test/head.js @@ -1,21 +1,20 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ const Cu = Components.utils; const Ci = Components.interfaces; const Cc = Components.classes; -Services.prefs.setBoolPref("devtools.debugger.log", true); -SimpleTest.registerCleanupFunction(() => { - Services.prefs.clearUserPref("devtools.debugger.log"); -}); - +// Services.prefs.setBoolPref("devtools.debugger.log", true); +// SimpleTest.registerCleanupFunction(() => { +// Services.prefs.clearUserPref("devtools.debugger.log"); +// }); let tempScope = {}; Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm", tempScope); let LayoutHelpers = tempScope.LayoutHelpers; let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", tempScope); let TargetFactory = devtools.TargetFactory; @@ -47,50 +46,73 @@ function getActiveInspector() } function getNodeFront(node) { let inspector = getActiveInspector(); return inspector.walker.frontForRawNode(node); } +function getHighlighter() +{ + return gBrowser.selectedBrowser.parentNode.querySelector(".highlighter-container"); +} + +function getHighlighterOutline() +{ + let h = getHighlighter(); + if (h) { + return h.querySelector(".highlighter-outline"); + } +} + +function getHighlighterOutlineRect() { + let helper = new LayoutHelpers(window.content); + let outline = getHighlighterOutline(); + + if (outline) { + let browserOffsetRect = helper.getDirtyRect(gBrowser.selectedBrowser); + let outlineRect = helper.getDirtyRect(outline); + outlineRect.top -= browserOffsetRect.top; + outlineRect.left -= browserOffsetRect.left; + + return outlineRect; + } +} + function isHighlighting() { - let outline = getActiveInspector().highlighter.outline; - return !(outline.getAttribute("hidden") == "true"); + let outline = getHighlighterOutline(); + return outline && !outline.hasAttribute("hidden"); } function getHighlitNode() { - let h = getActiveInspector().highlighter; - if (!isHighlighting() || !h._contentRect) - return null; + if (isHighlighting()) { + let helper = new LayoutHelpers(window.content); + let outlineRect = getHighlighterOutlineRect(); - let a = { - x: h._contentRect.left, - y: h._contentRect.top - }; + let a = { + x: outlineRect.left, + y: outlineRect.top + }; - let b = { - x: a.x + h._contentRect.width, - y: a.y + h._contentRect.height - }; + let b = { + x: a.x + outlineRect.width, + y: a.y + outlineRect.height + }; - // Get midpoint of diagonal line. - let midpoint = midPoint(a, b); - - let lh = new LayoutHelpers(window.content); - return lh.getElementFromPoint(h.win.document, midpoint.x, - midpoint.y); + let {x, y} = getMidPoint(a, b); + return helper.getElementFromPoint(window.content.document, x, y); + } } - -function midPoint(aPointA, aPointB) +function getMidPoint(aPointA, aPointB) { - let pointC = { }; + let pointC = {}; pointC.x = (aPointB.x - aPointA.x) / 2 + aPointA.x; pointC.y = (aPointB.y - aPointA.y) / 2 + aPointA.y; return pointC; } function computedView() { let sidebar = getActiveInspector().sidebar; @@ -184,12 +206,19 @@ function getComputedPropertyValue(aName) if (name.textContent === aName) { let value = prop.querySelector(".property-value"); return value.textContent; } } } +function getContainerForRawNode(markupView, rawNode) +{ + let front = markupView.walker.frontForRawNode(rawNode); + let container = markupView.getContainer(front); + return container; +} + SimpleTest.registerCleanupFunction(function () { let target = TargetFactory.forTab(gBrowser.selectedTab); gDevTools.closeToolbox(target); });
--- a/browser/devtools/layoutview/view.js +++ b/browser/devtools/layoutview/view.js @@ -29,22 +29,18 @@ function LayoutView(aInspector, aWindow) this.init(); } LayoutView.prototype = { init: function LV_init() { this.update = this.update.bind(this); this.onNewNode = this.onNewNode.bind(this); this.onNewSelection = this.onNewSelection.bind(this); - this.onHighlighterLocked = this.onHighlighterLocked.bind(this); this.inspector.selection.on("new-node-front", this.onNewSelection); this.inspector.sidebar.on("layoutview-selected", this.onNewNode); - if (this.inspector.highlighter) { - this.inspector.highlighter.on("locked", this.onHighlighterLocked); - } // Store for the different dimensions of the node. // 'selector' refers to the element that holds the value in view.xhtml; // 'property' is what we are measuring; // 'value' is the computed dimension, computed in update(). this.map = { position: {selector: "#element-position", property: "position", @@ -101,19 +97,16 @@ LayoutView.prototype = { * Destroy the nodes. Remove listeners. */ destroy: function LV_destroy() { this.inspector.sidebar.off("layoutview-selected", this.onNewNode); this.inspector.selection.off("new-node-front", this.onNewSelection); if (this.browser) { this.browser.removeEventListener("MozAfterPaint", this.update, true); } - if (this.inspector.highlighter) { - this.inspector.highlighter.off("locked", this.onHighlighterLocked); - } this.sizeHeadingLabel = null; this.sizeLabel = null; this.inspector = null; this.doc = null; }, /** * Selection 'new-node-front' event handler. @@ -121,34 +114,25 @@ LayoutView.prototype = { onNewSelection: function() { let done = this.inspector.updating("layoutview"); this.onNewNode().then(done, (err) => { console.error(err); done() }); }, onNewNode: function LV_onNewNode() { if (this.isActive() && this.inspector.selection.isConnected() && - this.inspector.selection.isElementNode() && - this.inspector.selection.reason != "highlighter") { + this.inspector.selection.isElementNode()) { this.undim(); } else { this.dim(); } return this.update(); }, /** - * Highlighter 'locked' event handler - */ - onHighlighterLocked: function LV_onHighlighterLocked() { - this.undim(); - this.update(); - }, - - /** * Hide the layout boxes. No node are selected. */ dim: function LV_dim() { if (this.browser) { this.browser.removeEventListener("MozAfterPaint", this.update, true); } this.trackingPaint = false; this.doc.body.classList.add("dim");
--- a/browser/devtools/main.js +++ b/browser/devtools/main.js @@ -104,19 +104,17 @@ Tools.inspector = { icon: "chrome://browser/skin/devtools/tool-inspector@2x.png", url: "chrome://browser/content/devtools/inspector/inspector.xul", label: l10n("inspector.label", inspectorStrings), tooltip: l10n("inspector.tooltip", inspectorStrings), inMenu: true, preventClosingOnKey: true, onkey: function(panel) { - if (panel.highlighter) { - panel.highlighter.toggleLockState(); - } + panel.toolbox.togglePicker(); }, isTargetSupported: function(target) { return true; }, build: function(iframeWindow, toolbox) { let panel = new InspectorPanel(iframeWindow, toolbox);
--- a/browser/devtools/markupview/markup-view.css +++ b/browser/devtools/markupview/markup-view.css @@ -65,17 +65,17 @@ .html-editor-container { position: relative; min-height: 200px; } /* This extra element placed in each tag is positioned absolutely to cover the * whole tag line and is used for background styling (when a selection is made * or when the tag is flashing) */ -.tag-line .highlighter { +.tag-line .tag-state { position: absolute; left: -1000em; right: 0; height: 100%; z-index: -1; } .expander { @@ -115,17 +115,17 @@ margin-right: -1em; padding: 1px 0; } .newattr:focus { margin-right: 0; } -.highlighter.flash-out { +.tag-state.flash-out { transition: background .5s; } /* Preview */ #previewbar { position: fixed; top: 0;
--- a/browser/devtools/markupview/markup-view.js +++ b/browser/devtools/markupview/markup-view.js @@ -10,24 +10,26 @@ const {Cc, Cu, Ci} = require("chrome"); const PAGE_SIZE = 10; const PREVIEW_AREA = 700; const DEFAULT_MAX_CHILDREN = 100; const COLLAPSE_ATTRIBUTE_LENGTH = 120; const COLLAPSE_DATA_URL_REGEX = /^data.+base64/; const COLLAPSE_DATA_URL_LENGTH = 60; const CONTAINER_FLASHING_DURATION = 500; const IMAGE_PREVIEW_MAX_DIM = 400; +const NEW_SELECTION_HIGHLIGHTER_TIMER = 1000; const {UndoStack} = require("devtools/shared/undo"); const {editableField, InplaceEditor} = require("devtools/shared/inplace-editor"); const {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {}); const {HTMLEditor} = require("devtools/markupview/html-editor"); const {OutputParser} = require("devtools/output-parser"); const promise = require("sdk/core/promise"); const {Tooltip} = require("devtools/shared/widgets/Tooltip"); +const EventEmitter = require("devtools/shared/event-emitter"); Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm"); Cu.import("resource://gre/modules/devtools/Templater.jsm"); Cu.import("resource://gre/modules/Services.jsm"); loader.lazyGetter(this, "DOMParser", function() { return Cc["@mozilla.org/xmlextras/domparser;1"].createInstance(Ci.nsIDOMParser); }); @@ -47,16 +49,20 @@ loader.lazyGetter(this, "AutocompletePop /** * The markup tree. Manages the mapping of nodes to MarkupContainers, * updating based on mutations, and the undo/redo bindings. * * @param Inspector aInspector * The inspector we're watching. * @param iframe aFrame * An iframe in which the caller has kindly loaded markup-view.xhtml. + * + * Fires the following events: + * - node-highlight: When a node in the markup-view is hovered and the + * corresponding node in the content gets highlighted */ function MarkupView(aInspector, aFrame, aControllerWindow) { this._inspector = aInspector; this.walker = this._inspector.walker; this._frame = aFrame; this.doc = this._frame.contentDocument; this._elt = this.doc.querySelector("#root"); this._outputParser = new OutputParser(); @@ -95,27 +101,142 @@ function MarkupView(aInspector, aFrame, this._boundFocus = this._onFocus.bind(this); this._frame.addEventListener("focus", this._boundFocus, false); this._handlePrefChange = this._handlePrefChange.bind(this); gDevTools.on("pref-changed", this._handlePrefChange); this._initPreview(); + this._initTooltips(); + this._initHighlighter(); - this.tooltip = new Tooltip(this._inspector.panelDoc); - this.tooltip.startTogglingOnHover(this._elt, - this._buildTooltipContent.bind(this)); + EventEmitter.decorate(this); } exports.MarkupView = MarkupView; MarkupView.prototype = { _selectedContainer: null, + _initTooltips: function() { + this.tooltip = new Tooltip(this._inspector.panelDoc); + this.tooltip.startTogglingOnHover(this._elt, + this._buildTooltipContent.bind(this)); + }, + + _initHighlighter: function() { + // Show the box model on markup-view mousemove + this._onMouseMove = this._onMouseMove.bind(this); + this._elt.addEventListener("mousemove", this._onMouseMove, false); + this._onMouseLeave = this._onMouseLeave.bind(this); + this._elt.addEventListener("mouseleave", this._onMouseLeave, false); + + // Show markup-containers as hovered on toolbox "picker-node-hovered" event + // which happens when the "pick" button is pressed + this._onToolboxPickerHover = (event, nodeFront) => { + this.showNode(nodeFront, true).then(() => { + this._showContainerAsHovered(nodeFront); + }); + } + this._inspector.toolbox.on("picker-node-hovered", this._onToolboxPickerHover); + }, + + _onMouseMove: function(event) { + let target = event.target; + + // Search target for a markupContainer reference, if not found, walk up + while (!target.container) { + if (target.tagName.toLowerCase() === "body") { + return; + } + target = target.parentNode; + } + + let container = target.container; + if (this._hoveredNode !== container.node) { + if (container.node.nodeType !== Ci.nsIDOMNode.TEXT_NODE) { + this._showBoxModel(container.node); + } else { + this._hideBoxModel(); + } + } + this._showContainerAsHovered(container.node); + }, + + _hoveredNode: null, + _showContainerAsHovered: function(nodeFront) { + if (this._hoveredNode !== nodeFront) { + if (this._hoveredNode) { + this._containers.get(this._hoveredNode).hovered = false; + } + this._containers.get(nodeFront).hovered = true; + + this._hoveredNode = nodeFront; + } + }, + + _onMouseLeave: function() { + this._hideBoxModel(); + }, + + _showBoxModel: function(nodeFront, options={}) { + let toolbox = this._inspector.toolbox; + + // If the remote highlighter exists on the target, use it + if (toolbox.isRemoteHighlightable) { + toolbox.initInspector().then(() => { + toolbox.highlighter.showBoxModel(nodeFront, options).then(() => { + this.emit("node-highlight", nodeFront); + }); + }); + } + // Else, revert to the "older" version of the highlighter in the walker + // actor + else { + this.walker.highlight(nodeFront).then(() => { + this.emit("node-highlight", nodeFront); + }); + } + }, + + _hideBoxModel: function() { + let deferred = promise.defer(); + let toolbox = this._inspector.toolbox; + + // If the remote highlighter exists on the target, use it + if (toolbox.isRemoteHighlightable) { + toolbox.initInspector().then(() => { + toolbox.highlighter.hideBoxModel().then(deferred.resolve); + }); + } else { + deferred.resolve(); + } + // If not, no need to unhighlight as the older highlight method uses a + // setTimeout to hide itself + + return deferred.promise; + }, + + _briefBoxModelTimer: null, + _brieflyShowBoxModel: function(nodeFront, options) { + let win = this._frame.contentWindow; + + if (this._briefBoxModelTimer) { + win.clearTimeout(this._briefBoxModelTimer); + this._briefBoxModelTimer = null; + } + + this._showBoxModel(nodeFront, options); + + this._briefBoxModelTimer = this._frame.contentWindow.setTimeout(() => { + this._hideBoxModel(); + }, NEW_SELECTION_HIGHLIGHTER_TIMER); + }, + template: function(aName, aDest, aOptions={stack: "markup-view.xhtml"}) { let node = this.doc.getElementById("template-" + aName).cloneNode(true); node.removeAttribute("id"); template(node, aDest, aOptions); return node; }, /** @@ -171,21 +292,32 @@ MarkupView.prototype = { return container._buildTooltipContent(target, this.tooltip); } }, /** * Highlight the inspector selected node. */ _onNewSelection: function() { + let selection = this._inspector.selection; + this.htmlEditor.hide(); let done = this._inspector.updating("markup-view"); - if (this._inspector.selection.isNode()) { - this.showNode(this._inspector.selection.nodeFront, true).then(() => { - this.markNodeAsSelected(this._inspector.selection.nodeFront); + if (selection.isNode()) { + let reason = selection.reason; + if (reason && reason !== "inspector-open" && reason !== "navigateaway") { + this._brieflyShowBoxModel(selection.nodeFront, { + scrollIntoView: true + }); + } + + this.showNode(selection.nodeFront, true).then(() => { + if (selection.reason !== "treepanel") { + this.markNodeAsSelected(selection.nodeFront); + } done(); }); } else { this.unmarkSelectedNode(); done(); } }, @@ -365,21 +497,16 @@ MarkupView.prototype = { navigate: function(aContainer, aIgnoreFocus) { if (!aContainer) { return; } let node = aContainer.node; this.markNodeAsSelected(node, "treepanel"); - // This event won't be fired if the node is the same. But the highlighter - // need to lock the node if it wasn't. - this._inspector.selection.emit("new-node"); - this._inspector.selection.emit("new-node-front"); - if (!aIgnoreFocus) { aContainer.focus(); } }, /** * Make sure a node is included in the markup tool. * @@ -935,65 +1062,74 @@ MarkupView.prototype = { }, /** * Tear down the markup panel. */ destroy: function() { gDevTools.off("pref-changed", this._handlePrefChange); - delete this._outputParser; + // Note that if the toolbox is closed, this will work fine, but will fail + // in case the browser is closed and will trigger a noSuchActor message. + this._hideBoxModel(); + + this._hoveredNode = null; + this._inspector.toolbox.off("picker-node-hovered", this._onToolboxPickerHover); + + this._outputParser = null; this.htmlEditor.destroy(); - delete this.htmlEditor; + this.htmlEditor = null; this.undo.destroy(); - delete this.undo; + this.undo = null; this.popup.destroy(); - delete this.popup; + this.popup = null; this._frame.removeEventListener("focus", this._boundFocus, false); - delete this._boundFocus; + this._boundFocus = null; if (this._boundUpdatePreview) { this._frame.contentWindow.removeEventListener("scroll", this._boundUpdatePreview, true); - delete this._boundUpdatePreview; + this._boundUpdatePreview = null; } if (this._boundResizePreview) { this._frame.contentWindow.removeEventListener("resize", this._boundResizePreview, true); this._frame.contentWindow.removeEventListener("overflow", this._boundResizePreview, true); this._frame.contentWindow.removeEventListener("underflow", this._boundResizePreview, true); - delete this._boundResizePreview; + this._boundResizePreview = null; } this._frame.contentWindow.removeEventListener("keydown", this._boundKeyDown, false); - delete this._boundKeyDown; + this._boundKeyDown = null; this._inspector.selection.off("new-node-front", this._boundOnNewSelection); - delete this._boundOnNewSelection; + this._boundOnNewSelection = null; this.walker.off("mutations", this._boundMutationObserver) - delete this._boundMutationObserver; + this._boundMutationObserver = null; - delete this._elt; + this._elt.removeEventListener("mousemove", this._onMouseMove, false); + this._elt.removeEventListener("mouseleave", this._onMouseLeave, false); + this._elt = null; for (let [key, container] of this._containers) { container.destroy(); } - delete this._containers; + this._containers = null; this.tooltip.destroy(); - delete this.tooltip; + this.tooltip = null; }, /** * Initialize the preview panel. */ _initPreview: function() { this._previewEnabled = Services.prefs.getBoolPref("devtools.inspector.markupPreview"); if (!this._previewEnabled) { @@ -1109,37 +1245,28 @@ function MarkupContainer(aMarkupView, aN this.editor = new DoctypeEditor(this, aNode); } else { this.editor = new GenericEditor(this, aNode); } // The template will fill the following properties this.elt = null; this.expander = null; - this.highlighter = null; + this.tagState = null; this.tagLine = null; this.children = null; this.markup.template("container", this); this.elt.container = this; this.children.container = this; // Expanding/collapsing the node on dblclick of the whole tag-line element this._onToggle = this._onToggle.bind(this); this.elt.addEventListener("dblclick", this._onToggle, false); this.expander.addEventListener("click", this._onToggle, false); - // Dealing with the highlighting of the row via javascript rather than :hover - // This is to allow highlighting the closing tag-line as well as reusing the - // theme css classes (which wouldn't have been possible with a :hover pseudo) - this._onMouseOver = this._onMouseOver.bind(this); - this.elt.addEventListener("mouseover", this._onMouseOver, false); - - this._onMouseOut = this._onMouseOut.bind(this); - this.elt.addEventListener("mouseout", this._onMouseOut, false); - // Appending the editor element and attaching event listeners this.tagLine.appendChild(this.editor.elt); this._onMouseDown = this._onMouseDown.bind(this); this.elt.addEventListener("mousedown", this._onMouseDown, false); this._onClick = this._onClick.bind(this); this.elt.addEventListener("click", this._onClick, false); @@ -1230,32 +1357,30 @@ MarkupContainer.prototype = { // tag-line that the user can interact with and showing the children. if (this.editor instanceof ElementEditor) { let closingTag = this.elt.querySelector(".close"); if (closingTag) { if (!this.closeTagLine) { let line = this.markup.doc.createElement("div"); line.classList.add("tag-line"); - let highlighter = this.markup.doc.createElement("div"); - highlighter.classList.add("highlighter"); - line.appendChild(highlighter); + let tagState = this.markup.doc.createElement("div"); + tagState.classList.add("tag-state"); + line.appendChild(tagState); line.appendChild(closingTag.cloneNode(true)); - line.addEventListener("mouseover", this._onMouseOver, false); - line.addEventListener("mouseout", this._onMouseOut, false); this.closeTagLine = line; } this.elt.appendChild(this.closeTagLine); } } this.elt.classList.remove("collapsed"); this.expander.setAttribute("open", ""); - this.highlighted = false; + this.hovered = false; } else if (!aValue) { if (this.editor instanceof ElementEditor && this.closeTagLine) { this.elt.removeChild(this.closeTagLine); } this.elt.classList.add("collapsed"); this.expander.removeAttribute("open"); } }, @@ -1263,29 +1388,19 @@ MarkupContainer.prototype = { _onToggle: function(event) { this.markup.navigate(this); if(this.hasChildren) { this.markup.setNodeExpanded(this.node, !this.expanded); } event.stopPropagation(); }, - _onMouseOver: function(event) { - this.highlighted = true; - event.stopPropagation(); - }, - - _onMouseOut: function(event) { - this.highlighted = false; - event.stopPropagation(); - }, - _onMouseDown: function(event) { if (event.target.nodeName !== "a") { - this.highlighted = false; + this.hovered = false; this.markup.navigate(this); event.stopPropagation(); } }, _onClick: function(event) { let target = event.target; @@ -1314,64 +1429,64 @@ MarkupContainer.prototype = { this.flashed = false; }, CONTAINER_FLASHING_DURATION); } }, set flashed(aValue) { if (aValue) { // Make sure the animation class is not here - this.highlighter.classList.remove("flash-out"); + this.tagState.classList.remove("flash-out"); // Change the background - this.highlighter.classList.add("theme-bg-contrast"); + this.tagState.classList.add("theme-bg-contrast"); // Change the text color this.editor.elt.classList.add("theme-fg-contrast"); [].forEach.call( this.editor.elt.querySelectorAll("[class*=theme-fg-color]"), span => span.classList.add("theme-fg-contrast") ); } else { // Add the animation class to smoothly remove the background - this.highlighter.classList.add("flash-out"); + this.tagState.classList.add("flash-out"); // Remove the background - this.highlighter.classList.remove("theme-bg-contrast"); + this.tagState.classList.remove("theme-bg-contrast"); // Remove the text color this.editor.elt.classList.remove("theme-fg-contrast"); [].forEach.call( this.editor.elt.querySelectorAll("[class*=theme-fg-color]"), span => span.classList.remove("theme-fg-contrast") ); } }, - _highlighted: false, + _hovered: false, /** * Highlight the currently hovered tag + its closing tag if necessary * (that is if the tag is expanded) */ - set highlighted(aValue) { - this.highlighter.classList.remove("flash-out"); - this._highlighted = aValue; + set hovered(aValue) { + this.tagState.classList.remove("flash-out"); + this._hovered = aValue; if (aValue) { if (!this.selected) { - this.highlighter.classList.add("theme-bg-darker"); + this.tagState.classList.add("theme-bg-darker"); } if (this.closeTagLine) { - this.closeTagLine.querySelector(".highlighter").classList.add( + this.closeTagLine.querySelector(".tag-state").classList.add( "theme-bg-darker"); } } else { - this.highlighter.classList.remove("theme-bg-darker"); + this.tagState.classList.remove("theme-bg-darker"); if (this.closeTagLine) { - this.closeTagLine.querySelector(".highlighter").classList.remove( + this.closeTagLine.querySelector(".tag-state").classList.remove( "theme-bg-darker"); } } }, /** * True if the container is visible in the markup tree. */ @@ -1384,25 +1499,25 @@ MarkupContainer.prototype = { */ _selected: false, get selected() { return this._selected; }, set selected(aValue) { - this.highlighter.classList.remove("flash-out"); + this.tagState.classList.remove("flash-out"); this._selected = aValue; this.editor.selected = aValue; if (this._selected) { this.tagLine.setAttribute("selected", ""); - this.highlighter.classList.add("theme-selected"); + this.tagState.classList.add("theme-selected"); } else { this.tagLine.removeAttribute("selected"); - this.highlighter.classList.remove("theme-selected"); + this.tagState.classList.remove("theme-selected"); } }, /** * Update the container's editor to the current state of the * viewed node. */ update: function() {
--- a/browser/devtools/markupview/markup-view.xhtml +++ b/browser/devtools/markupview/markup-view.xhtml @@ -18,17 +18,17 @@ <body class="theme-body devtools-monospace" role="application"> <div id="root-wrapper"> <div id="root"></div> </div> <div id="templates" style="display:none"> <ul class="children"> <li id="template-container" save="${elt}" class="child collapsed"> - <div save="${tagLine}" class="tag-line"><span save="${highlighter}" class="highlighter"></span><span save="${expander}" class="theme-twisty expander"></span></div> + <div save="${tagLine}" class="tag-line"><span save="${tagState}" class="tag-state"></span><span save="${expander}" class="theme-twisty expander"></span></div> <ul save="${children}" class="children"></ul> </li> <li id="template-more-nodes" class="more-nodes devtools-class-comment" save="${elt}"><span>${showing}</span> <button href="#" onclick="${allButtonClick}">${showAll}</button></li> </ul> <span id="template-element" save="${elt}" class="editor"><span class="open"><<span save="${tag}" class="tag theme-fg-color3" tabindex="0"></span><span save="${attrList}"></span><span save="${newAttr}" class="newattr" tabindex="0"></span><span class="closing-bracket">></span></span><span class="close"></<span save="${closeTag}" class="tag theme-fg-color3"></span>></span></span>
--- a/browser/devtools/markupview/test/browser_inspector_markup_edit.js +++ b/browser/devtools/markupview/test/browser_inspector_markup_edit.js @@ -676,19 +676,16 @@ function test() { desc: "Add attributes by adding to an existing attribute's entry", setup: function() { inspector.selection.setNode(doc.querySelector("#node18")); }, before: function() { assertAttributes(doc.querySelector("#node18"), { id: "node18", }); - - is(inspector.highlighter.nodeInfo.classesBox.textContent, "", - "No classes in the infobar before edit."); }, execute: function(after) { inspector.once("markupmutation", function() { // needed because we need to make sure the infobar is updated // not just the markupview (which happens in this event loop) executeSoon(after); }); let editor = getContainerForRawNode(markup, doc.querySelector("#node18")).editor; @@ -696,19 +693,16 @@ function test() { editField(attr, attr.textContent + ' class="newclass" style="color:green"'); }, after: function() { assertAttributes(doc.querySelector("#node18"), { id: "node18", class: "newclass", style: "color:green" }); - - is(inspector.highlighter.nodeInfo.classesBox.textContent, ".newclass", - "Correct classes in the infobar after edit."); } }; testAsyncSetup(test, editTagName); } function editTagName() { let test = { desc: "Edit the tag name",
--- a/browser/devtools/markupview/test/browser_inspector_markup_mutation_flashing.js +++ b/browser/devtools/markupview/test/browser_inspector_markup_mutation_flashing.js @@ -118,13 +118,13 @@ function test() { } function assertNodeFlashing(rawNode) { let container = getContainerForRawNode(markup, rawNode); if(!container) { ok(false, "Node not found"); } else { - ok(container.highlighter.classList.contains("theme-bg-contrast"), + ok(container.tagState.classList.contains("theme-bg-contrast"), "Node is flashing"); } } }
--- a/browser/devtools/styleinspector/computed-view.js +++ b/browser/devtools/styleinspector/computed-view.js @@ -386,19 +386,16 @@ CssHtmlTree.prototype = { // Reset zebra striping. this._darkStripe = true; let deferred = promise.defer(); this._refreshProcess = new UpdateProcess(this.styleWindow, this.propertyViews, { onItem: (aPropView) => { aPropView.refresh(); }, - onCancel: () => { - deferred.reject("refresh cancelled"); - }, onDone: () => { this._refreshProcess = null; this.noResults.hidden = this.numVisibleProperties > 0; this.styleInspector.inspector.emit("computed-view-refreshed"); deferred.resolve(undefined); } }); this._refreshProcess.schedule();
--- a/browser/devtools/styleinspector/rule-view.js +++ b/browser/devtools/styleinspector/rule-view.js @@ -1379,17 +1379,17 @@ CssRuleView.prototype = { // Repopulate the element style. this._populate(); }, _populate: function() { let elementStyle = this._elementStyle; return this._elementStyle.populate().then(() => { if (this._elementStyle != elementStyle) { - return promise.reject("element changed"); + return; } this._createEditors(); // Notify anyone that cares that we refreshed. var evt = this.doc.createEvent("Events"); evt.initEvent("CssRuleViewRefreshed", true, false); this.element.dispatchEvent(evt); return undefined;
--- a/browser/devtools/styleinspector/style-inspector.js +++ b/browser/devtools/styleinspector/style-inspector.js @@ -77,19 +77,16 @@ function RuleViewTool(aInspector, aWindo this._onSelect = this.onSelect.bind(this); this.inspector.selection.on("detached", this._onSelect); this.inspector.selection.on("new-node-front", this._onSelect); this.refresh = this.refresh.bind(this); this.inspector.on("layout-change", this.refresh); this.inspector.selection.on("pseudoclass", this.refresh); - if (this.inspector.highlighter) { - this.inspector.highlighter.on("locked", this._onSelect); - } this.onSelect(); } exports.RuleViewTool = RuleViewTool; RuleViewTool.prototype = { onSelect: function RVT_onSelect(aEvent) { @@ -97,41 +94,29 @@ RuleViewTool.prototype = { if (!this.inspector.selection.isConnected() || !this.inspector.selection.isElementNode()) { this.view.highlight(null); return; } if (!aEvent || aEvent == "new-node-front") { - if (this.inspector.selection.reason == "highlighter") { - this.view.highlight(null); - } else { - let done = this.inspector.updating("rule-view"); - this.view.highlight(this.inspector.selection.nodeFront).then(done, done); - } - } - - if (aEvent == "locked") { let done = this.inspector.updating("rule-view"); this.view.highlight(this.inspector.selection.nodeFront).then(done, done); } }, refresh: function RVT_refresh() { this.view.nodeChanged(); }, destroy: function RVT_destroy() { this.inspector.off("layout-change", this.refresh); this.inspector.selection.off("pseudoclass", this.refresh); this.inspector.selection.off("new-node-front", this._onSelect); - if (this.inspector.highlighter) { - this.inspector.highlighter.off("locked", this._onSelect); - } this.view.element.removeEventListener("CssRuleViewCSSLinkClicked", this._cssLinkHandler); this.view.element.removeEventListener("CssRuleViewChanged", this._changeHandler); this.view.element.removeEventListener("CssRuleViewRefreshed", @@ -154,19 +139,16 @@ function ComputedViewTool(aInspector, aW this.window = aWindow; this.document = aWindow.document; this.outerIFrame = aIFrame; this.view = new ComputedView.CssHtmlTree(this, aInspector.pageStyle); this._onSelect = this.onSelect.bind(this); this.inspector.selection.on("detached", this._onSelect); this.inspector.selection.on("new-node-front", this._onSelect); - if (this.inspector.highlighter) { - this.inspector.highlighter.on("locked", this._onSelect); - } this.refresh = this.refresh.bind(this); this.inspector.on("layout-change", this.refresh); this.inspector.selection.on("pseudoclass", this.refresh); this.view.highlight(null); this.onSelect(); } @@ -180,27 +162,16 @@ ComputedViewTool.prototype = { if (!this.inspector.selection.isConnected() || !this.inspector.selection.isElementNode()) { this.view.highlight(null); return; } if (!aEvent || aEvent == "new-node-front") { - if (this.inspector.selection.reason == "highlighter") { - // FIXME: We should hide view's content - } else { - let done = this.inspector.updating("computed-view"); - this.view.highlight(this.inspector.selection.nodeFront).then(() => { - done(); - }); - } - } - - if (aEvent == "locked" && this.inspector.selection.nodeFront != this.view.viewedElement) { let done = this.inspector.updating("computed-view"); this.view.highlight(this.inspector.selection.nodeFront).then(() => { done(); }); } }, refresh: function CVT_refresh() { @@ -208,19 +179,16 @@ ComputedViewTool.prototype = { }, destroy: function CVT_destroy(aContext) { this.inspector.off("layout-change", this.refresh); this.inspector.sidebar.off("computedview-selected", this.refresh); this.inspector.selection.off("pseudoclass", this.refresh); this.inspector.selection.off("new-node-front", this._onSelect); - if (this.inspector.highlighter) { - this.inspector.highlighter.off("locked", this._onSelect); - } this.view.destroy(); delete this.view; delete this.outerIFrame; delete this.cssLogic; delete this.cssHtmlTree; delete this.window;
--- a/browser/devtools/styleinspector/test/browser_bug705707_is_content_stylesheet.js +++ b/browser/devtools/styleinspector/test/browser_bug705707_is_content_stylesheet.js @@ -62,27 +62,28 @@ function reselectElement(target, cb) function testModifyRules() { // Set a property on all rules, then refresh and make sure they are still // there (and there wasn't an error on the server side) for (let rule of ruleView._elementStyle.rules) { rule.editor.addProperty("font-weight", "bold", ""); } - reselectElement(doc.querySelector("#target"), () => { - - for (let rule of ruleView._elementStyle.rules) { - let lastRule = rule.textProps[rule.textProps.length - 1]; + executeSoon(() => { + reselectElement(doc.querySelector("#target"), () => { + for (let rule of ruleView._elementStyle.rules) { + let lastRule = rule.textProps[rule.textProps.length - 1]; - is (lastRule.name, "font-weight", "Last rule name is font-weight"); - is (lastRule.value, "bold", "Last rule value is bold"); - } + is (lastRule.name, "font-weight", "Last rule name is font-weight"); + is (lastRule.value, "bold", "Last rule value is bold"); + } - gBrowser.removeCurrentTab(); - openXUL(); + gBrowser.removeCurrentTab(); + openXUL(); + }); }); } function openXUL() { Cc["@mozilla.org/permissionmanager;1"].getService(Ci.nsIPermissionManager) .addFromPrincipal(XUL_PRINCIPAL, 'allowXULXBL', Ci.nsIPermissionManager.ALLOW_ACTION);
--- a/browser/devtools/styleinspector/test/browser_ruleview_override.js +++ b/browser/devtools/styleinspector/test/browser_ruleview_override.js @@ -15,44 +15,45 @@ function simpleOverride(aInspector, aRul ' background-color: blue;' + '} ' + '.testclass {' + ' background-color: green;' + '}'; let styleNode = addStyle(doc, style); doc.body.innerHTML = '<div id="testid" class="testclass">Styled Node</div>'; - - inspector.selection.setNode(doc.getElementById("testid")); - inspector.once("inspector-updated", () => { - let elementStyle = view._elementStyle; + inspector.once("markupmutation", () => { + inspector.selection.setNode(doc.getElementById("testid")); + inspector.once("inspector-updated", () => { + let elementStyle = view._elementStyle; - let idRule = elementStyle.rules[1]; - let idProp = idRule.textProps[0]; - is(idProp.name, "background-color", "First ID prop should be background-color"); - ok(!idProp.overridden, "ID prop should not be overridden."); + let idRule = elementStyle.rules[1]; + let idProp = idRule.textProps[0]; + is(idProp.name, "background-color", "First ID prop should be background-color"); + ok(!idProp.overridden, "ID prop should not be overridden."); + + let classRule = elementStyle.rules[2]; + let classProp = classRule.textProps[0]; + is(classProp.name, "background-color", "First class prop should be background-color"); + ok(classProp.overridden, "Class property should be overridden."); - let classRule = elementStyle.rules[2]; - let classProp = classRule.textProps[0]; - is(classProp.name, "background-color", "First class prop should be background-color"); - ok(classProp.overridden, "Class property should be overridden."); + // Override background-color by changing the element style. + let elementRule = elementStyle.rules[0]; + elementRule.createProperty("background-color", "purple", ""); + promiseDone(elementRule._applyingModifications.then(() => { + let elementProp = elementRule.textProps[0]; + is(classProp.name, "background-color", "First element prop should now be background-color"); + ok(!elementProp.overridden, "Element style property should not be overridden"); + ok(idProp.overridden, "ID property should be overridden"); + ok(classProp.overridden, "Class property should be overridden"); - // Override background-color by changing the element style. - let elementRule = elementStyle.rules[0]; - elementRule.createProperty("background-color", "purple", ""); - promiseDone(elementRule._applyingModifications.then(() => { - let elementProp = elementRule.textProps[0]; - is(classProp.name, "background-color", "First element prop should now be background-color"); - ok(!elementProp.overridden, "Element style property should not be overridden"); - ok(idProp.overridden, "ID property should be overridden"); - ok(classProp.overridden, "Class property should be overridden"); - - styleNode.parentNode.removeChild(styleNode); - partialOverride(); - })); + styleNode.parentNode.removeChild(styleNode); + partialOverride(); + })); + }); }); } function partialOverride() { let style = '' + // Margin shorthand property... '.testclass {' + @@ -60,105 +61,108 @@ function partialOverride() '}' + // ... will be partially overridden. '#testid {' + ' margin-left: 1px;' + '}'; let styleNode = addStyle(doc, style); doc.body.innerHTML = '<div id="testid" class="testclass">Styled Node</div>'; - - inspector.selection.setNode(doc.getElementById("testid")); - inspector.once("inspector-updated", () => { - let elementStyle = view._elementStyle; + inspector.once("markupmutation", () => { + inspector.selection.setNode(doc.getElementById("testid")); + inspector.once("inspector-updated", () => { + let elementStyle = view._elementStyle; - let classRule = elementStyle.rules[2]; - let classProp = classRule.textProps[0]; - ok(!classProp.overridden, "Class prop shouldn't be overridden, some props are still being used."); - for (let computed of classProp.computed) { - if (computed.name.indexOf("margin-left") == 0) { - ok(computed.overridden, "margin-left props should be overridden."); - } else { - ok(!computed.overridden, "Non-margin-left props should not be overridden."); + let classRule = elementStyle.rules[2]; + let classProp = classRule.textProps[0]; + ok(!classProp.overridden, "Class prop shouldn't be overridden, some props are still being used."); + for (let computed of classProp.computed) { + if (computed.name.indexOf("margin-left") == 0) { + ok(computed.overridden, "margin-left props should be overridden."); + } else { + ok(!computed.overridden, "Non-margin-left props should not be overridden."); + } } - } + + styleNode.parentNode.removeChild(styleNode); - styleNode.parentNode.removeChild(styleNode); - - importantOverride(); + importantOverride(); + }); }); } function importantOverride() { let style = '' + // Margin shorthand property... '.testclass {' + ' background-color: green !important;' + '}' + // ... will be partially overridden. '#testid {' + ' background-color: blue;' + '}'; let styleNode = addStyle(doc, style); doc.body.innerHTML = '<div id="testid" class="testclass">Styled Node</div>'; + inspector.once("markupmutation", () => { + inspector.selection.setNode(doc.getElementById("testid")); + inspector.once("inspector-updated", () => { + let elementStyle = view._elementStyle; - inspector.selection.setNode(doc.getElementById("testid")); - inspector.once("inspector-updated", () => { - let elementStyle = view._elementStyle; - - let idRule = elementStyle.rules[1]; - let idProp = idRule.textProps[0]; - ok(idProp.overridden, "Not-important rule should be overridden."); + let idRule = elementStyle.rules[1]; + let idProp = idRule.textProps[0]; + ok(idProp.overridden, "Not-important rule should be overridden."); - let classRule = elementStyle.rules[2]; - let classProp = classRule.textProps[0]; - ok(!classProp.overridden, "Important rule should not be overridden."); + let classRule = elementStyle.rules[2]; + let classProp = classRule.textProps[0]; + ok(!classProp.overridden, "Important rule should not be overridden."); - styleNode.parentNode.removeChild(styleNode); + styleNode.parentNode.removeChild(styleNode); - let elementRule = elementStyle.rules[0]; - let elementProp = elementRule.createProperty("background-color", "purple", "important"); - promiseDone(elementRule._applyingModifications.then(() => { - ok(classProp.overridden, "New important prop should override class property."); - ok(!elementProp.overridden, "New important prop should not be overriden."); + let elementRule = elementStyle.rules[0]; + let elementProp = elementRule.createProperty("background-color", "purple", "important"); + promiseDone(elementRule._applyingModifications.then(() => { + ok(classProp.overridden, "New important prop should override class property."); + ok(!elementProp.overridden, "New important prop should not be overriden."); - disableOverride(); - })); + disableOverride(); + })); + }); }); } function disableOverride() { let style = '' + '#testid {' + ' background-color: blue;' + '}' + '.testclass {' + ' background-color: green;' + '}'; let styleNode = addStyle(doc, style); doc.body.innerHTML = '<div id="testid" class="testclass">Styled Node</div>'; - - inspector.selection.setNode(doc.getElementById("testid")); - inspector.once("inspector-updated", () => { - let elementStyle = view._elementStyle; + inspector.once("markupmutation", () => { + inspector.selection.setNode(doc.getElementById("testid")); + inspector.once("inspector-updated", () => { + let elementStyle = view._elementStyle; - let idRule = elementStyle.rules[1]; - let idProp = idRule.textProps[0]; - idProp.setEnabled(false); - promiseDone(idRule._applyingModifications.then(() => { - let classRule = elementStyle.rules[2]; - let classProp = classRule.textProps[0]; - ok(!classProp.overridden, "Class prop should not be overridden after id prop was disabled."); + let idRule = elementStyle.rules[1]; + let idProp = idRule.textProps[0]; + idProp.setEnabled(false); + promiseDone(idRule._applyingModifications.then(() => { + let classRule = elementStyle.rules[2]; + let classProp = classRule.textProps[0]; + ok(!classProp.overridden, "Class prop should not be overridden after id prop was disabled."); - styleNode.parentNode.removeChild(styleNode); + styleNode.parentNode.removeChild(styleNode); - finishTest(); - })); + finishTest(); + })); + }); }); } function finishTest() { doc = inspector = view = null; gBrowser.removeCurrentTab(); finish();
--- a/browser/devtools/tilt/tilt-visualizer.js +++ b/browser/devtools/tilt/tilt-visualizer.js @@ -267,20 +267,21 @@ TiltVisualizer.prototype = { */ onNewNodeFromTilt: function TV_onNewNodeFromTilt() { if (!this.inspector) { return; } let nodeIndex = this.presenter._currentSelection; if (nodeIndex < 0) { - this.inspector.selection.setNode(null, "tilt"); + this.inspector.selection.setNodeFront(null, "tilt"); } let node = this.presenter._traverseData.nodes[nodeIndex]; - this.inspector.selection.setNode(node, "tilt"); + node = this.inspector.walker.frontForRawNode(node); + this.inspector.selection.setNodeFront(node, "tilt"); }, }; /** * This object manages the visualization logic and drawing loop. * * @param {HTMLCanvasElement} aCanvas * the canvas element used for rendering
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_653531_highlighter_console_helper.js +++ b/browser/devtools/webconsole/test/browser_webconsole_bug_653531_highlighter_console_helper.js @@ -1,20 +1,22 @@ /* vim: set ft=javascript ts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ // Tests that the $0 console helper works as intended. +let inspector, h1; + function createDocument() { let doc = content.document; let div = doc.createElement("div"); - let h1 = doc.createElement("h1"); + h1 = doc.createElement("h1"); let p1 = doc.createElement("p"); let p2 = doc.createElement("p"); let div2 = doc.createElement("div"); let p3 = doc.createElement("p"); doc.title = "Inspector Tree Selection Test"; h1.textContent = "Inspector Tree Selection Test"; p1.textContent = "This is some example text"; p2.textContent = "Lorem ipsum dolor sit amet, consectetur adipisicing " + @@ -37,49 +39,47 @@ function createDocument() div2.appendChild(p3); doc.body.appendChild(div); doc.body.appendChild(div2); setupHighlighterTests(); } function setupHighlighterTests() { - let h1 = content.document.querySelector("h1"); ok(h1, "we have the header node"); - openInspector(runSelectionTests); } function runSelectionTests(aInspector) { - aInspector.highlighter.unlockAndFocus(); - aInspector.highlighter.outline.setAttribute("disable-transitions", "true"); + inspector = aInspector; + inspector.toolbox.startPicker(); + inspector.toolbox.once("picker-started", () => { + EventUtils.synthesizeMouse(h1, 2, 2, {type: "mousemove"}, content); + inspector.toolbox.once("picker-node-hovered", () => { + executeSoon(performTestComparisons); + }); + }); +} - executeSoon(function() { - aInspector.selection.once("new-node", performTestComparisons); - let h1 = content.document.querySelector("h1"); - EventUtils.synthesizeMouse(h1, 2, 2, {type: "mousemove"}, content); - }); +function getHighlighterOutline() +{ + return gBrowser.selectedBrowser.parentNode + .querySelector(".highlighter-container .highlighter-outline"); } function performTestComparisons() { - let target = TargetFactory.forTab(gBrowser.selectedTab); - let inspector = gDevTools.getToolbox(target).getPanel("inspector"); - inspector.highlighter.lock(); - - let isHighlighting = - !(inspector.highlighter.outline.getAttribute("hidden") == "true"); + let outline = getHighlighterOutline(); + ok(outline && !outline.hasAttribute("hidden"), "inspector is highlighting"); - ok(isHighlighting, "inspector is highlighting"); - - let h1 = content.document.querySelector("h1"); - is(inspector.selection.node, h1, "selection matches node"); - - openConsole(gBrowser.selectedTab, performWebConsoleTests); + EventUtils.synthesizeMouseAtCenter(h1, {}, content); + inspector.toolbox.once("picker-stopped", () => { + openConsole(gBrowser.selectedTab, performWebConsoleTests); + }); } function performWebConsoleTests(hud) { let target = TargetFactory.forTab(gBrowser.selectedTab); let jsterm = hud.jsterm; outputNode = hud.outputNode; @@ -93,29 +93,29 @@ function performWebConsoleTests(hud) jsterm.clearOutput(); jsterm.execute("$0.textContent = 'bug653531'", onNodeUpdate); } function onNodeUpdate(node) { isnot(node.textContent.indexOf("bug653531"), -1, "correct output for $0.textContent"); - let inspector = gDevTools.getToolbox(target).getPanel("inspector"); is(inspector.selection.node.textContent, "bug653531", "node successfully updated"); + inspector = h1 = null; gBrowser.removeCurrentTab(); finishTest(); } } function test() { waitForExplicitFinish(); + gBrowser.selectedTab = gBrowser.addTab(); gBrowser.selectedBrowser.addEventListener("load", function onLoad() { gBrowser.selectedBrowser.removeEventListener("load", onLoad, true); waitForFocus(createDocument, content); }, true); content.location = "data:text/html;charset=utf-8,test for highlighter helper in web console"; } -
--- a/browser/extensions/shumway/content/ShumwayStreamConverter.jsm +++ b/browser/extensions/shumway/content/ShumwayStreamConverter.jsm @@ -183,16 +183,26 @@ function isShumwayEnabledFor(actions) { /sndcdn\.com\/assets\/swf/.test(url) /* soundcloud */ || /vimeocdn\.com/.test(url) /* vimeo */) { return false; } return true; } +function fallbackToNativePlugin(window, userAction, activateCTP) { + var obj = window.frameElement; + var doc = obj.ownerDocument; + var e = doc.createEvent("CustomEvent"); + e.initCustomEvent("MozPlayPlugin", true, true, activateCTP); + obj.dispatchEvent(e); + + ShumwayTelemetry.onFallback(userAction); +} + // All the priviledged actions. function ChromeActions(url, window, document) { this.url = url; this.objectParams = null; this.movieParams = null; this.baseUrl = url; this.isOverlay = false; this.isPausedAtStart = false; @@ -351,23 +361,18 @@ ChromeActions.prototype = { } else { log("data access id prohibited to " + url + " from " + baseUrl); win.postMessage({callback:"loadFile", sessionId: sessionId, topic: "error", error: "only original swf file or file from the same origin loading supported"}, "*"); } }); }, fallback: function(automatic) { - var obj = this.window.frameElement; - var doc = obj.ownerDocument; - var e = doc.createEvent("CustomEvent"); - e.initCustomEvent("MozPlayPlugin", true, true, null); - obj.dispatchEvent(e); - - ShumwayTelemetry.onFallback(!automatic); + automatic = !!automatic; // cast to boolean + fallbackToNativePlugin(this.window, !automatic, automatic); }, setClipboard: function (data) { if (typeof data !== 'string' || data.length > MAX_CLIPBOARD_DATA_SIZE || !this.document.hasFocus()) { return; } // TODO other security checks? @@ -897,17 +902,17 @@ ShumwayStreamConverterBase.prototype = { aRequest.cancel(Cr.NS_BINDING_ABORTED); var domWindow = getDOMWindow(channel); let actions = converter.createChromeActions(domWindow, domWindow.document, converter.getUrlHint(originalURI)); if (!isShumwayEnabledFor(actions)) { - actions.fallback(true); + fallbackToNativePlugin(domWindow, false, true); return; } // Report telemetry on amount of swfs on the page if (actions.isOverlay) { // Looking for last actions with same baseUrl var prevPageActions = ActivationQueue.findLastOnPage(actions.baseUrl); var pageIndex = !prevPageActions ? 1 : (prevPageActions.telemetry.pageIndex + 1);
index 53a0322aad8d22d0384443adc5a89a881df5a345..86e367fc1698218dcfe42cc2693dcb3a023b7dd9 GIT binary patch literal 81529 zc%0O|d0-Sp`aj%HcUO1UOs>g&00sd;K>|oT(8Uc&P*w!F)ZG=?Axt1^uFXsY++~dj zh^PpPsHhkb5xE2e55W6YP!2D=?-M0ncp<!>s_vQTnM?vJ-}jGS^r`2mr=F^Mo~NFA zs=KFWNK<+%ISQ#BtqFhDLHwCJ*Ywb>v~8bsntf%L!ap}rliH*=S!UB?+QDx%C8g@T z%&R<)vOi~!*|>S#Me3jTp7QH~LA^gcJ>`>Qvi|+zTc;nG-ZOAU)=#JB9$z;4x6b^} z+fKc-a&hygr`ven>5BYoIq2X^ZTvIv`<Q)iiQ@d-?>)O{Ofb+8ED8lHCe;S2CIlLa z&S|LoXP_b!EDBC)teNbeS`=)kDDq!i)30b^pf=Fp4+SO^jc=@~4pr3_l?8$o4OQa< zqo>vfdi#UyVt;jG;AjZfS2qR+ObmoZH2SNnDysq$Mm5$48Y-&&!Qe1|O+Yb5g&L}A zCw5FES{jxraJ7~NDywR%LREFOgQki$WB7QXrFQ<=Ls@0De`1j8Xo^}_l{Uep(qBdm zAC~0k3iZ17F`W@RPYQ+V2NV_gC)AA(^scC@DJmItLQ%gyeNGIUW`@xJ{F-XLqON|b zUMpq{HTbJSK{nnW3<QnpK<&iPBwbW^Rj_O-B&sURs^H+NT7R`w6&w}vhpH-4BHGBl z-Z7)f#|;@hvSi5UQ55i0`YS?p4O7J|Vm7b8zCKVpp>$GJ^@Pl@>27oM#;V#7RZx`) ztA|zvL!Q8t5PAeLf(K2328;;ZY%;kDBLr#!wIR{&t*EOFh8h~hj43V}O%Wx}@i+Ku zg8X8CLzRDgbs%VkDZ^iZaf4Pv01m6Io@x)~uMLTT!$P19u|~m3+!n8E2wGvv8zzLR z69Nc?hQNeDQ_Y${Xj0vTpkZTfBO%ie2sJj;3adT!LahKy3Pu+9>_Esr!5{KlH4c&8 z%3spq`o(os6ST&Ls+4fMt)W^ofoi<r&X|w<hQDDV2<@W4)ZF;N!qc&mh6evss;A1N zh*Idos%j?$rdW0LB0T-ot#q;d+G^BrgpN3}J?Mz^4%K0~FAh{A&?yKXk8hkf2n%*m zN^M<eNH`P%6IeYqUWg%>D*_{rY-WTn3>L=Jhk950YbRoA4S|IXfdCb|)Erb-R~_)z zT8*_Z+Fu{wjc^s#ATYtO8De#Vfx*>v{*W=Ov1WXrK^x{D#v1$+s-{@7ZAfiM6LDW_ z$=y{I3a~1qhhgb~+KRdffiWY8l-AYM*VV#09$}>;#^M_zEDNr=q^i2wf5ws7Z>J#P z?{hoZbP=3;F=Pgeo8%7;pInR4koH1TyT&)yI}JmE<+Y797#ADJ6Nr$<M&#>4z<~(^ z#sveRa}spw7(a4V5MrS^kx+(L=Ek>%>g>eJ#K?8IZH?Nc_59fQAt4YM-RsDRB}s`j zH#3oW<~SkQwGXckkC4;G>?w`4L4PIo#xUg2ivx~tdq?{9j<GR0*5bN`s)-^8_jY=P zEw;i$PP~LXw1!uj4o+`vYLa8Bw7}<7>=OWC5#qIh$wOr=t_}8uR{-bBfEY#dTn(|_ zJId9P$dSoTyNy!gLe0QwR__mGB@D5Y*zn+yc+iDqOxnlf`8)W?wuT%7J0n99MUkZw z?(*6^Sz3b|Yh@Nm3Dpg)tMFF`oJ9)rU8{~(9LFef6Ex!Nte(nbIpoMxj|}b)W>p}8 zh63d^^`WU@cGo@~!emi`=Ily`C=7FY99#JBY1xrJF|J6&M$lwL*wG*}LzAk4B@GjG zoKrzxL_h?I@QDSsJe|^GnxsRb(qSSaf#zH!>|i~Pg})q`10&<WgPD;EEJWg;B24i` znK+sSCo9SkT01*2JC+-cRl^=BBib6F@}2X>6&T@Od99nAY0f+{bHn4pSci<!Hp9(y zW=sw99PArben!dYA?M=aaM<7>!-kChtM8oQqlUzA_}N2-jXSqw=$P^}`<(0@Veqp{ z&W~#fGpJl2I;?LWZRoIm#?aws^zBp5fcllI=L|7NjTkw)@3?+U;C@MEqlb?gJ#yUO zA?KHur8$*zMwXWj88u}1uylt}e*QVbhm{W-J)~slQO;CuPrG+;UV?VJCAU?J<Cabd zx|C0`Pwy&!b#hFtLjvJ*8UhtnLBz7xMYo<VR5!S4N??NN;3K>WaEA8>JvLS3A0PCF z$%?ut9jvO2(71RG(-ncL>Tp+xhsnT{`Xq;}tgfqT2=lA!CWiS9b-0=e^If9{|J4u* zvoR4(O>#}!FWgdJH(7581S66){wWc%sx~=1hrf10T}`KOpYU}=t*noBRuo6N<7~YK zmsmOR(zso?7)McVJRkF%E0#XPX>jkGbQjNwkK7nBS1e@5G(=WDJ%%mUI&7A6sfnes z&i02U^#-Af#o~4ilrGlGse;ml@0=<qU96g26_kb!_CO#sES+5e!R*D8RY_`CK)V$L zvzJg-C8=R5yH(677Sf(XP#Wg7+dwdTEh7r`)3Ba)D+mpXX;(mKHQ_Nq81{mC?I|Ul z8n>EOc)fUJflOS0<3caQ#T{soC^Br>-jxk?HKmjM4W(H3BzGm(5pZ3#iMu3RXF0e- zD*;>(gofCSUKgFyO0WWRl!Oc$6(npHDwKpgQ8K|Ob+r|^>#$L$*4oHqCT<^aubLcF z%WfrH;}2C#VzO*)ctfDR8i$kV;MST11OA4JNrsJD4<4+pst7PyCTOg$#x<KP^IJDI zjt|;9G(shqT{amuYP~8Vtxpa#l=_1KUpU1_I9^w4qON1=>*KmcI9^w4UV^U9VbQ9^ zNd`xlwVh%8K`uf>+!zKO&&YJ)j^r$dA5B|gnRT(6Iq^--5@*Nr?WJWC0@YPD3{Vwn zHL6@fpTmt__qCeB**@tmZ5>uYyr^pvWxK>(Y@ds5Z>Ov{j=g(g<m857b)iXthFtfs z?k$q$YLNbN>+5*QJ*eZLV91X|7pR@k+C3ut-J`Z<2nXi1>K6|DOt)y5*?OU5iw6Ju zTFJY8*gEbb?3fJ_+b0t6iTXquGh!N@<Iu%(c0jVja;}Q)u(ZM<XgV=x*nwq*<0i=+ z6^89rPt;4ML#CFNj4B^8Y*hI$`#RK9Rz9k<d|26#VP|;83>z|HO!=tcBS)u?EFV2) z<gg*b%F1!sSTb^C$zRQ3W6mxgIiytoXI)h-4yAg%zA-onxeq5w5Ls+eRb|M;4ZgsJ zP^}`M2kRO@qZnSxgxpl461z0eU{=;Ol=~|t;W(>@P)*<n?@Hwjl6*)NsMqQmL)0rn zvZBi`lunQ1!G`QOmSas;9NXR*nQ>fcDwWWnRt++v$1xq(#1CzIDqdaTGbSER2Tl9w zW3W{}>D6RA7Rv31fLvrZ83FN=J28?U$96@GC2lsy`ztQ;i2G6d5k$OQatY%Vu~#&H zLT7>FU5@o`ZE8(`$@S62iiq3b3M;%2rsI0UarQ-4*pD>9bbQC8FU+cmwRH^v!Ihpd zY;8?PKP#+}E_7gW)-+p_XiAP{;C`L^t3&n!AXeVcfXls+{@RHFn+#2AsGBUw+*pgy zZ=CZf!USNw5rx+lk!6ZQW5WxplcQCb?`jm=%NMOW9!ZOGY}cel8PYNCx^Rzc2*-7< zA#4x2CKb+0i>(f?bq(PJQdX4XiXqq3()q4|>{W0L<Sd8FdaO%~A~gX|)RpPu#(_I- zT<?nm6$8!<h)+0<@It{J1W6;W_0UeE*VSU!tOUYc>*Qw9t3rXAK5aHf!lMHI8eKOU zn@oI8kQ^g)*8hb`;xhr5)#K2r*jwbd!w_^<tNUSyiiJBoxrPv3oD%B}Rw-`gIMTQu zrqwkt;nbj6F)2`S5snQfH5mcu5@^sxy0eDV$`25y8a3i$wgz5dU#C>odT~(}2;sV= zu{P9>LNT@D53P7@U~*X7u5C+4lggom5anIk7Lp!0EE!C8OxrT<i;iB05_3QBIl6se zk;C}-#)-v!`WN>rrhj@}4oY@X^qGNbERDCS7SSC#2OUDk9!7-N`K=wZBZ;P4`!Y`S z8V!M9V|A!=`@#sLLs%>l^Wkh7Cu1LCG<p72Ry71etfJaqQ-3)7Rwi}>@m+Cvp0roN z@qfMWze87u&0*A4RwBNN4tpSbIS=7<=dd{(_3Xo;o!E6>KWMm0K8*Y#ec8_5iyokd z5VDm!M9|>mK7>1*U^qlHOP3u=Ml8W0gR0?BcG*jL2(6vtzMVyky9CHiAc=5Glvl+Q zr^JV%Cn}LhKX*s_2ul<XF;Y$jNaT_jIk6cJL#QEP5)ndCGEs(b593OcZ4DMZL=GW= z-CNj(MCABrUtnV5^F;`_4~|5E5S4MC8b`GwDUOUpyFJFs_U3F$>qzQPv=PDr&Z>86 zM+6C_JxxWbKV)z@Tf4n+L|h#*l;y@gq+jfPo9F~jAZBOk_I%`|=|fIyr@^*j#GRlI zwSQ!0ZY!XpX}LWKTbcIO6$!aLtDPllFS?{#+7%EEZhz6+&J)oL+@6|F`fY1*;&N|J z0x|JOzjqt_HnOkRC6Z|7n67sIO?1r?E9Pv)M0+4su&tdEFV&DRUp2aNu%LS$Y9v<{ zPP7n>v1#~FBEiI&x-ljZA3~yyTZYE?RGjF1aS6F|aiS=Wh>B#KD8<D(yzM<18AwJ# zn~UPg$BFh#L<R|@U3Eo9K6JRXv%edo+p{KNwvCH4u}2T>AA2t+y39!=XQ$<MedXlk zL(gpI!Zt(3otY20uViX&GsvU4xm^)kp>`J;*|}Zgo#ksc+@yQjmk_Q#L<lsrZ;QQL z?ds~}=r$KBE=ebPfHa}5aeQ?k(X%7F<q*$~Y@tM(BCdPe&wJtp+j-s-FWT1gp4M^= z38UGOi>W2rM{(mF;xUXP-Tq^kSfMr^u{c8QK8A^rY2z`BBjZlti7w(|#18cs#w9G$ zc%rNG_+@DOp^9s;HXo|EWSkI5v;$pIZ9GkJNwoDeC0_A%o~F1)6VZ4$J3;1#!||7$ z0TMkMNi5m+vk{k;?K~T4wG-QXHWDuhS0BbcZ1cH@OTD(Ai@4Xn&F3QS#ZCNN#3j+r za}oElw)I@ZEz{Q3bWC_8elFq`Y4f>=OQ@aaA}%Rc+SkPw0+>H|LTyk(Q~8lS=of?a z_6PM)U`i-kR3ghO->fgKYX~Ge9~U>sk5tmaweU-!+C+c-u`xaEkBO@)r;e%*R8;w^ zt1byl2){WbKIN^#I(Zsv?PU*4@Q#{V8}d()AAs@5tj2l%>Wcym`eY!FJ-leL!qYSP zHm6w?tQ21i;39k5pQaT?{&evMQ-}7n$q<kD8$*Hco6+JMGbwViNH1zgs&};}`K!fO zT&9*xm=KttS5;Tl6vo(|U_dSf<f=&%cx3Ek+r}ar8I$U&v3wq!N6x3PUZ-97Am9BZ zr2%KlZ^4vS*999J0zKMpihY+jWn`cd9<QyiU6uR?M!~Vwo?uKZ^)%<pNA}y?1G7(y ze&1+F_`66&15-|Oec7mJp!c-!SBC^0{_2pR!(R|88puzxzXnt^FzK|&*KCAH`1?9V z1C7)C4HJt7nx{#%2YOC(zDiXzF#XISW#!|BjT$p@$hfo1N1r*oY*g})VWmUIfIFyU zRQag%aBax2(d8otmz0)|@`YLFmW&)yGH9raQ&NidQNu@$N)2<ILDO9IlCqM~B^hBx z>F{BrMvoj*I(qoXlrYc!%9mH_Hhj=u%1cKl4<1@F>dd}<#sQx(>Ih#!j;@(eE59<K zRa6I^?`O#6Ne=I4Sxb*uNXbfxyh1IjNzRMOvf{i}YQL|KOI1n8Hv#Qtd$&p2BeRIj zw)32%ov%f>I4KAbS2cA^Y~5ETv!jcX<o?!FMuIN{W+nUzVSd}+L6kdJ3YwBPv4RDr zAdGZ>W2lZtqE@f1s|{!+=M2$KJgL8TSYTrKgTy1u&CZ*pK7nK2h>hnsZ*q#)in&dM z-%~A$yrNnZc{^3UPHcAwzmqCnC6+IW+Ak8@yz07%Hc!5GYV#taiMLH{7KZgiCg^AL z!Y`T@MO1^<UVAY4La9B7D0E?vc$H#G_)XfbeI;c_i%e94+0R7u1^p9`^aY>rfLW3d zSy2*a+k<QMbwR^<%?bHKy!J|csUMv{{i&EvOp3m$(X~%%Twh~kU}E`{dZXLl`y7AD zSelq-&k*gR4k%QsCRpPFqB5k+Yq!fXBcjcykiX)h(c-IjKAY!Ai?-yLcGGD|jmEj` zaa3HrD6Y=SFZNeEvxcdn2$dCmF{LO<c8onaisBBEQGtra1`+V0RuEGwimBze&iW!3 zoe?>|iz1YT1T6_)m?#Pp$Q%tgKkXr1bdifrA5$x>woiP?>eiZ7)S8uC(lD`698$J5 zF}0$YT4p6;bW(4*4#5FKhD&CLM9d-wC%GF<hnXAG<Lp2Ujww6F+G;3r(OwsQbnM*K z)q#or>f!Zb3xrqy<U@-TCFEu$=Ee+HK76p`c1p-C3KLeCu(!9|>6vj+YM%j-u(Z#B zXh6z(Mr;_`Cs$0oNmiOG(ClF&F=iiq;jpr$T`?pXIjuv)o+&04Bug(tuHm8(DeHu6 zNx5I*4vns=378I!Z+2eV%!y-%@9_&-@dd+fOLA9Q3yRqyg3(IHR)X%w1fAPxjZT!_ zn$=dP=%nebS^2FxjTv2PH+fxjRx2@aJ{vZQw*{#Z9S1j7SN}EOZ%B!)#!qOso?TZP zn&frSc?rb?(-S3g+Yp+{BUIw)&a7)}2zp&~hs0V{)#8RCm>g3}EQ-*t!{v5zOf7yz z!sAp|R|Rc}^wz8d8i#%7b<y#miYDneD@??PxVS0<$8>P~ic0oW!$t`Pmu|AcM1tjU z+~bLo34|QSc%o#dL?cDLks4PoY%MN=`4U_P4wo;$_*!uiI6|fZ2gmz6=<;{4ZGQ*D z{tmX`@1Wyv2Pf!nyENE_OM_9D2HSLLFzV7^n=TE;xiqdGzkP$T+c((m_6@qWZ?Ns{ z8;ow>D4BRHdv6BY-kZUgy%}t8Zw6!bX0W}z8Em~bTeFVvuEIH;JObO#RP?FvDXS4K z({b}0V3UN!W{qu*=}fEE1TEPI>ZD1c#PvN~d$jo95Xg#UJLjG+mWwSwq3DP)LhfhW zSuRfWz;Sb<CrXZs>ps&8TJhtK&6u|6c$u!Y$axf<B=&U4jN{8-%!p%(YI-Z))cC$K z__E^%v{xpc?F3apE9q$L#0($S*XL$RWz*aoL8rztrp9!UI>y-NC>X<Yw9Ac^i#u|{ z4YA2HExswQRa}K_$!^sm4Qd^Tg3(%7nv&ak4BMX84WnGyE3QY>GA6!5+owY-8CS@( zHYgl7t><)tCt(+<e?h{*Y!ec;jdC4pt@|a4o?xQr3ATzJ$IrS*C>jQdk&O$RV1lp- zCJ382=jON@5^lm^qD>g}W4vJ8jteH<alynpuC>o>lPevq<Zm0qa?mA3a%2LQk7-?1 zv?j}{NX+sXK(rR=Df4x-r(KQg8z+y;ucgE=A`*_OQHd}E5?M}{s73PE7+P~7n;Byn z93ezoGlO9tNE02gI8H=2VW}e$%ZQ93&5ao)!Vy1|;WpWkYQ>AlImX1uxp?B&T3T7| z2q$ictX~LeE4qHNhPIqYEt>6wRFY%rR8dI{SDcyT4W>NDHE^u(v8s!cG|8O|c_jZ? z;grWCS<dT9Y+hF`ON&omsd2d~$(^3Ou8frAPC_ZMc_$?{)np{dE><{3lH5-8x+2F4 zhll9|ffZh9PjoRoQSYb5t%fcfof7M94SqFj-%7vQPp0ihdEzF%f0|9(KUWi&5~$Ec z5yQpAAzc(l_(h_~tW;zKVOd4Mm{?sm-e2vpzmg`t`N?Y<AzNKl8!#r+g~ZRg*dMY4 zCiv{`MRrfQ8yt>3h}^dhCpw<oq7FAQw3`|E=rJ)f7@fw}M{XR46IM+|vBQ&Z1;B&u zy6~M@?+=9n4YkP*-$pz#lh@_(Bm8#L5G=HRNNs(i_+>6Oek!Iv$$nzh2>XKrqU}YA z*#pF9fP+LouS<Xnj{JDzls_<<{c*~Pe@u^o|9iJFwLwHdKz9EF){mN0*AP1Wk54Q6 z7ys}5ht!7t^v7pK?>}Ix{O!4u|M!0OM^vWxj{K-V=+ysxfV0Jh6btkR95rV2-~oTA zmyGK_|8HI89xA!mUsWwXti^W!1Madv0{kCmw!b`5E`HTdeA8Y043>Beq^hp=$Oa1j z&n6(#RrzcGfbC94@j`DK9iwD+VhJ8iNUmgJg}G_*8=gnDu&>poG@Wgh8n<zHWGi^0 z!JQlvrlu1@N$yZei46`<bg5HfeQwx}@I=iT`hSq;hc|}oMBjQh*iEgM#pVx~+)xz? zNE1%`V_of4XnhzQ?!13YW4VZ}L-26AVwGF(n#1Yn3X?ykw2Z6P8S-$t+JV^m1U#I+ zk;wgH{z8biK8y~tqYNy=-T{BiaC;LC`d|CD&X`9!1TxqEyz0?x`A55oH)z}H>Y{Vn zknu#fcanR%@(AA(-A-0`d!)sOyC)jHUROA##D<p@UiIXd<@dVQ(d*L9=hhh+Z_3mo zeCt7uA)fk%iLNIf+iDKm;o?_8r%V-3+QKif9YWIe6RpR!FDZT)<S+-F<PU}=d$liV ze>eXyh2>j8!3so>zcv)?*}iZsZePS}K=StEn8TCperPfIb7_Y#zbaS~3N=)X$31OM zYe`x5bAF(F^5n_Aqd!tEf1up`tIPJVMRHiLYf-(fWlM4|jMwG*lvwAc#JVpz#z9uN z#v?|0U1l(;wAW>%9gFgQPMug`RmqqBDgs`~w)ceiTngbkF=(BC_D~VEr+DRq3Y(ca zAyDbZ4hz?l{1YZbUQ*-!@XVeF6=W6Sm(xA6w3U`*L!j1z^W=+(AZkN?DC3<FwbTkY zuPyrE9P#s2)qyFckOM0XoKf<<Mu*|6svT7Ue?>G2rrF<+m%rLxQYn7C#KjmCsKlK( zm%of<doSECD?*jYUqhEKT?DP#x(R`^s%j^2+`-q#cM=ToD;2e&Y<N|?APH9tao$W5 z9g^xB0+An$$2DC|-NjMeggZ+e7bJlm8dgJmVkmHugKg<h9oE7A@T2U=5|X{1*+AiE z5sv@Ezm_~iauW$aqpnf*ISGxH-_tmxka(OI6*_Mca<Tnuu@l>B5TE3V2KY1-ntG1- zopk366*`|IxrqeC!_g>PgkEhZVyUdPZWfPsV@>bSo;7Ll%BIBXkmMdbP0Z#zu$Fr# z!^H^ir<^b^ZlBRPYHd*SH%yGY87(4&jx*$D>T9EZktxET-(`mxvA;&$A<V8%@N3kW z;YRn?j_B(8*ARRWUU;*ox8eko!z`&`dQ45GgR~eH_C%E7EP;z*r-`H}qkd{e)MJs{ z5$O@mA$cx8#O9J*S4&JziF9d|TXLf<u{kC$L4%!ZQo=3vx0NEEi=?24Ng@F`(H#C! z*YL!iDCu*HOHRjD9CrqbI3%3Oq6368T0})VvvqQXNP@`Ej5bFzT_kwpGG1zwFTY6O z4*YN?%yjW088Owx6!pxwx*R>?I`Msq$k48|nHjY;R8?73AzhN=9?Hc}i4GuMaT5Qg zhe(_eO`H$kliXva#*LBb{6ioyTQS4UbVkW{^J2GXp}Q&3HqCMiMD|;DLT=FKX3LG1 z*^1*VLX_!jzc{A71=C`g^;2`)4A)kS=^fsV9pmhd-I5*RT4T0ly1UWdO)<KN@Q-m# zB*0^O+ChGFoRbr!_?WdsgyqC8ZY0WbV?6IloiQC;=`*|43dnpI<C|8wr$cOOZ2sxk zMx&jBGGo1B=YdEBwoW%$@osI+jij&631!_GC^y#C(WFt}Ziq`98L@5l@ASHPt^|@B z$B!hDjJO8Dix-Z}m>#>jA_b&|Sq^3+<k9=DT!|CBG@2nBiXzUDA3@qY=ZE!ucFmOu z)QSpA{HsOdAfX*MV1PPcKzdXtJhInj$x>XN+u!*B;as}IcMT1=E=+P%wO_S$Dz=PA zcD6rplcdd#dB#~*C1c;;Ff}bo%5Mqu@kAL%_<>N&$kyb5|Dv-3Q-fU+>k$c0@m6r> zHe{k?YLv9!FOJr2a|_)=i0soKc79!LN%h3KhN{q{8vSoWhYZp$sjeDtmX!}4S~9xa z#1&b!c(G#W-_9=4t1qeXAK^d95c%W^_7@*YpQ1#}%tXxA&u8Rg-F~hI+Wk`8b9X(- z>uT!O#-p2l?&mxGlH8RncbCY>+2fo`*x|7ct`Z6tC6>ij`Jd#mlaER)AEDZ4k37pS zOxzxQaCLNh0`}vqHs*+o(cT>L(O0K-1cuaxj%laa^_Z-^Y2?GR&g}@;kJP%hBN2Jf z)}FRwMi1`NezSCH8=AO2S7~FC@Vd3(lK9VH+F5<+`0j@uM#^<;TMmxzcIZ(c)s8N5 zb=p|I=;uIfI5y_fp>`C7@7suF_a{khXc7HshV}*GKVXYn{{-1MRt;N#kE?JpT^sf| zxtaUJS+I?Tbp4QmJzg7O6>j#9YpgxQ)K#@|>SGTvVRW1}tc?n^p;P!Gphx27)*lDA zvAXqjLHU)R!x6#qwJ}}mPc#lsFxY0qL_eKqUjQQ%w6SVP6b)Gkj|MC3T+>-EPt@O5 z*jy$Dh#rpe+F=1@>nt@pEho+LF)K&Uwel^tka8CiTSU2wD1Q;LHI%!C^4Ac%Aoqg& z3z$FGpYLaZ+(3SSb;<3L--WSUme1H(xo72{#dc}guW7lvH1>v;1L6%W{|$}Zs^{LS z=ijQcr}W&X^!%rEwnxw1qv!9@*#efkfaNb>YzfO<!t$3ewu$9#V)>gGTfuTyFgDj< z4{2<kk^7LA4`QW}yVA&CX|UBs?rI}{wZS%Nxtp~7O&Z(6a<{PjEsU)*a@QI8>kQV+ za+_IxGh@qH?sAsDoUx5Y?nWbjqro09avw1AAHd)k*~ov$V4IEH%|`xagFS3yKWgMY zY_P|T+{X>}l#%<C!Jaj8pEYtGHP~wgd)>%=&BzCFyTR65xwjknw;S2pE$cJOx`wk` zIJ=GKY`1c6;rX|4>mJTH+st!$K4-_|9+Q6z+u_OG;mP0OVIO<gC!XAoJ^3KM_T+x; zVLyA=eoyXxPyT+-pBSl{&J0ow)8ZznCQnLEkvKKYCvkd4ro>s<ITGjQ<x5=9p`*l| zjw+P6^U+-_QhB%TJ&rw25<PnMI^HIF7xl4;zWq*+vi*xsl=!5RPXXpB15Q1SNCSsd zl~n4cliIBZ^&pdU(ljzpH_7CGE*Vs60hg5Ga?&QwICF?i{N=2nHgWc_;Wly3h><oi zYV;VJIQP8sZQ`$g`@2nCF!n;5821mqO^mOYfayx8Qm2y2S}|)~{l6N(suy%H)Oa!I z5KXp-Prl^e|C#n*PQ22lbejC{{|Kojx{RjN<s|6KXa>!sSq^;#&8911r+FEP0sb?G zuA-~y8W=hGKUc|ep4~8y=F@d_J<)$&#iB5iER#4-A)Zb=gLo#TWD?IJo=rT53MeI) zcpjzX5znWTeB$5~5br>|Bk`jsrI1Y4*`zL9fgZ>3?x^PQ9>k9$-jjGQ;>Q#3O}r2B ze#B28UP!z@@nYg9Q3{wRQ_9K2Pa%FP@zW^fG~$0EK9Kn77_Ed4qLe|z2NPF`pGkZO zrQm-RO=t08lro(7Ih1k^su!5#Hz{CJ4xg-=>=Kjxn@>~uB`W{7%KxkKOI0{X<(C6y z0%jo?jBCj>=EHN7P2)O{*Mo#B%>@|l1{iS}@HCSa5}&4_qsDImT&nTgfNlrOBF&^l z5Lyl4+W@yiQpfN*q;)jwCaobpNH>{Stqp)dCYuQ%#xG@%VUT5z!vc&qX$#sP20TLi zQp2RjKt2I@67Uq@X}~jpX93Rvo+mTw1;C4dtr*jQdIknHFsQ+I0CoXh1<WE0w+~%@ z2K)-x4>$m5!iJa*m;smtm<^Z%xEe4QFb{AYU;$tu;3n0~S_HTSa4Vd=12*phyh_4v ze422d#iv>PQqG%I&Zlv{1ZXK>8DKeJ1z;6m4PY%`JzxXie!zo(O@J+chXIcQ9s^vC z*o4<GI5%0dYIgbxum|up;2XeR=&}U31$Zf78DKeJ1z;6m4PY%`J!HNG-T=5C^mo7y z0)G#@3Ggsr3*a%pqp<e}z>k1^fS&+At9+V=|Dt;MuYk+R1DAXFZ=m-BRsennYycbp z90W9J9)1~M3FxJO>7XwM%mB;;%o2bxF7$Bb;axmD$5Q}FaPI=#4OkAi2k<R=`#t<D z4_}GeYQP%6TEJek1U$S2XdSBS0rvqm05$?@Jp3V`O@Pgs$LzdRGdte}xErt>a1Q`Z z>AVtnHDC>3EdWmK42O1J2YNl=KEMXRM!-(ZWWT^kzXE;(><9b~H~=^ZXhQpCfa!qC z0W$zI0kbr-+v^&i=7rO|aG)0s^unQDIL`~`fCrcXSc3Xez)aA;0`CX>4%h&g1zr>2 zAm{_4PF}OeTN?imypI460X6|P19H3tUX$HI{6a5hUU<_BCwaN$HM_6Z;3qHld-<0d zsv9);*bA3=;aM*{>@^Afei3cIYP<%z=+FgCUA%Cz7psGo4>hwpJk%ZD=?>3yhxfYw zg0%1vFudCxUhWR>c86!X&(!&}Bv_sV%adSv5-d-G<w>v{JirXV64aLhW`h0|ct7BG zzy`o9@R|SzK_3uxN-}#a)?s-PEKh>vNw7Q#mM8I?q=F=VVG?IausR7=Cvhvu>?y5I z;{GI9jjFUd305b;>LgeVr37DC3>|dn09KbISeyil(E^Km!s4E=xF;;`35$Ee;-0X! zC#>xWYkR`lp2FH>Sep!MlVNQ#tWAcs$*>kYzzo0=)RzKgg8mhFKj3%32EZ)vng9nu z9}sm)HhVlHtWAcs$*?vV)+WQ+WS)~;kjyVk<}4Z3Cd1lfZY7(&rM1c2pA2hJmDVQ1 z+GJRp3~Qm3;0tS^gAN_Q>XHm=lVL4dU~O+$+Z)#QhPAz6ZEslH8`k!QwY_0&Z&=$~ zSepWCQ($chtWANnDX=yL)`AC^0a$|iQou~mzXI<E{0`Uvm<3)F;2`J&qE0Dhk8gyv zDX=yL)~3MP6j+<Wb5aUY_=PE)rNG)0SewGF6tl0iHii3BU@fZB+7wuu0&7!XEtC>` zVJ&pfp#xZ5QebTgtVIi~?F(!B!rH#DwlA#h3v2tr+P<*1FRbkgYx~aB&HgtqzL2Hz z3sa$4DsKk48Tbz1C4f5tizt<!mCBcc#Q*+Oo|DQ~fSRGE@`ZqvpjWZff>g8r9n9>% z635^Mz(&xkfEO^nn57k@@w3u+P8w%v7&MJvn8q(i<Nh?$ypx&cV!#px4SakH^Fafj zsXolOmChGZI=3>QLppz$WddjMHJ~p@=l*maNatPBIZNkfrSsRdY;NW7x3pZoSkL9p z0KNe%K<g5g$8VqvK0X}|O*iRFjb8|KA@81V>fQNqD39a4Q1;^eQ1;^|pge(}gz_YQ zD#}y&FqFgiV3dRTnJCZX=_u3rQ7Dh%TUmjrZDo9$z}pyqN#K_le_7y{8Q(7OcE)!I zyo2$b0`Fvem%zIie?{O|81D$wk-sWxuQL7`E5L|^(Fh|EMj?zq=uPNJ=tY>BAWx?D z8a%xaP-9ZHNf(*)Pm`)lnq<;MlO~w-50l23G}ffQn{>WO=b3b_Nn=bs#ng{C^?|0| z-_$#p+84~!z65*)*u%^}{Rp_)F#mK7E<IK-EIU@C1E1EBXLjWCjgHtQ9r@&rxGd<% zKLeT5v7jSXu_Iq>;Hv+21J?!}O|#H6hnw^nF3Gx?=Gi7ar185%`(g~b8A*0NFm4|1 zG0Z>TCFU{sv`##;lSwN<9mO+`!lfCgLY|3Rh)p<mI`d3isBU3=+R;4oXp`2VwF}Se zVp21BU3q3#la_<(#xuJi5Yc)J&pgJY2S9b_ncblRs2)7C2NW>O%Etgt0-gapZ@>Up zc`RRPq=P(`uLj-())pAQ4s<i{a==E=4+t5+X24^{agguHp9g--=*gc1eg-hDC(jf$ z(b5x~o_ty_erYeh+vtVsp<eJ|Fa8eb_W-wm{t)<Mz^8!E0bc_40KNgV0KNnK0N4lk z8SpD$Ki~kM$?V0a17-kb0cHc{0Imki1<V6n2Uq}D2)GHb2yiQ4F`yZ+1h5pa46s~` zL%nc`V3=&KiAx0FdB6vNuLGVASOAy-SO}N}xCt;Dun6!a<ZcDt1H2gc8{lT(?|_#8 z{{XxccpvaG;Gcn)178i;4SEi66QBiwalK{M;o`n-0q_l$$=*Txdw>rC9|Jx$%({hW zyAf~`;AX%gz%77V0k;7b18xU21MUDU0o;k(rlo*YCIStx25>K6E#NNjmjUhuEC<{J zSOHim<N>PzYXJ8G)&gELxOF_A)*GwZn_t?SZ?Jmv`vJ31eF%6HU^8F~;9<ZcR&PWj zI&K1N25bSm2zVIq2#nkSxZmPdkvVyXWls4V@CD#Yz*m4hfUg1H0QLe}0N(<>1AGto z0q`SWAK)jz&z3p)7mNR5^)aW;<>u6D0rLR!0oMVp2P^>G09XjP5pWaWW&rw675x`+ zzKHihtoKDU_d~YsYhJp7o0qNxtOBeCtO48$SPNJOSP!_5o0B(iZk=FG-iTWS-k)y) zJPerDpIiO08T#`}`tyJHhx7XLN5JpgpFaxp7~pZh6M!cHPXV3=Wc26H06hzM4)8qS z1;C4dt$>{Vd>c?^fBq8C%Yf~G9e|yHU4T~ruL9Pp{rPLauLIry>;~M#`tvt|KR5bg z_4;#=UjV)Yd<ED8_!@9Qf9~&(`wraGYymtBcm(h$;4#4CfF}S?0-gdq4ahLrGeFM* zo&!7&cmePtU@Kr7Ak$<o0lf^^4%h+M3D^aA1%TUq_8Q=Iz#D+wfSZ`f-sF6qr@u*` zW4C<4anb?40_*{N&CMp7=P}t`fMtNskpqjlRSdru^Gk~Pzl-7bV)(t7cP@tCi{bZT z_`Mi@FNWWX;rC+ry_jbd!|%oLdolc848Iq{@5S(YG5lW4bBf{jVxCzHzZb*r#qfJE z{9X*d7sKzx@Ov@*UJSn%!|%oLdolc848Iq{@5S(YG5lT(zZb)xVsi!*=hIGvekX!= zBH!UT5#e_t((s80zY|T`;o*Z$#H=Us9B|bWaRUnOHqJRx`APhkbaYAQ(@w^yCu4+@ z`2srGoI?x9ocyVWTc?=xjmM-Gz;_;V@;=-hS_91g{Nh2N1OJA}Pes>L(e+e*oA*?{ z#Ct0Cyw{vhzeCnDfdA$h0Jme$Vp9<|6=72mHWgu05jGWJQxP^5VN(${6=72mHZox& z6E-qoBNH|<VIvbZGGQYVHZox&6E-qoBNH|u?Gt<tCcX_fHfsUvgceIctOtk(6xi-` zA82e>!gi$%UcS%Mhg$=n=xI>&G`_>?bAw5wXw5201Es2%&8n&?c}F*?9<^9g|J;<6 zC2(m|r*6Ol|I(Cnx~Pw6O8S$kDtRNDy&h7OG0me<o-51qWO+WyQswW<LH>=1#xAD| zX*7s&Hc>|ZSWe^VZ)zEpPgt&wC(=e&P-)pj(V-H=B%2_r5*-GC_-B~7C`?q_L}__V zbETr_idw2reY3Yt;C}&Y#R`=MK{SA)gh0T|#%3=St5cdTR+On|R`l7Js19HgYB81S zO_>dfas|ARH=7Dc$(uvXR}y5eqUBP!RJl4rU1L*<G8YI+D9TM#s3<o(HPUSySc2h{ zrD8bcE@~dC<Smo*J+unt-J(%hNy`O7Z3TF%TqDR))?!>*CyMoGqWee@(i>^E?6iRf z+0#EHrr#t7-%P?LRq&~->~(5R0(!$nyKVHQjoz}++ctW~M(^6_JsZ7mqYrHKp^ZKQ zQp?K9KB4B1Nm8GJ^DznPb6TZxgBwou3sgOZM^u%vFR4(avhuH}xw1k7QT`36J@hr2 z_Cj@1THsb)t5C|mmDTT1C2c6E?<3R?Hl-*(+MYsrn>rgT)u}Cx))abLU8)Ww<r%ek z^iU1GmFEQYtg4oi^1NiCHE%1Z7t|NA^4ruxG?l-kib+Oy16}sAx?H_5Tr1y>7Sn0j zAzMU^sBEX~(hc6(Wm5{hqAsO!<uz4=%Bw03R9=_lP!+`+s=eya1v0zC4dvx;qk%M2 zdsBT&RmG~f>jM?#UD+|o5c&%F_u}N=kExfJe-PLHQKbH%s?f(O0_zh|d@731P$-{^ z;tN%T(wFe1PPS*s-Yc`UnE1@oXUO_M8w(Bf*_a^7n5WIxoMs8aT>WZzW}$YYrk2aC zN7~KWqHx8uE2RNZNlLm+Tdb)A-Id}tTVnb^$*Z&`?`#dk)fzUwqFe(6)dtQ5UM|Am zS`gYmrL62a5JFQmPZ@Z<Y!z|~Yzk)GASq2O)lDFU?i$oBzXcVcuB_aui50i&rAk1# z9gRX2%n(qT!<AxG?r^s(30Fj)JDrM>cfTgG&jT8=4>ZWzqBUJf4?=@SwHZ+TF)h3b zk86cW-V@qVW#E&j4SY(2o1fO?5<H{1*Wg*mD9?%J=QX(uFKF&{cu{jN#8%C{65BMl z!(Y<eYw>bS+jh;p8apC2J3YOsIcd@f7kjJ9fcjXI(NXGDKh-|dWT>>NeD1EOUnHi# zf>3w{8IZC;ZZ)|B+|vjRn)cXzXS}I?Eft|{s`94RwA)^lcifBju6w=Sb1EW8-gm+! zuk;(~c#kLNBTd<+?iDN4BA4@9aoj4U-)YTfE2ZCqPd{Kf`ccEiqJ8kz9F0DPMg#Y2 zO`ixCs=rGYN)HqV)C00+yC9%8$!M3h=3S;QRf?zUg)mLMT#xiYY|oNA(cb#P{{v_1 zO~TAiG*+CKm#5}ktv6q(&(Xg|_+F(edDrMmF}N~UZ?+8!DAzh<URmCJy=k6~l(9{{ z4y(0I4XD>gCR3CJdLdeF5I$cBRkk7Z-{_iMIQ1s!YteGE?QOM8En6g$fLK6OZjng< zmH$^lWv-^&riVl2cHI>!&AKa8?$D#5vP2Ju%AI;NROV_+;lRARbP+1cbQvml>mpQ^ zgHQK}WV=G-U|Ok1LuHljgvx5&9V%;dcc|R!3YE1YRMy#{vOXqM?u!qV4RN8eF*a20 zw?pNDXsA3G4V8zSP?@W3!fMUc0_x^ysFZJkMRT=>VN&@cP-U)GR`#enRN&Odq_0KG z<Ka*#FMA>yD&=KQ{=XI~TQ%kBNT@vP4wdKJq4K;dR9=XL%8Ra0*{W?7p|VYe%1d^r zyevayy9kvXB2;$DP}$`Ql~=-{@@h<|ycQEGue(C!4G}84?NE6$CRE;v50$s$Lgk&< zP<hu5mG`2d@_sZ_K5#;1tM(yQYb!$KqiCq)eGH4XYM+S9j8CD;Rs_Un?ofeKKbO81 zEnkE~C9mwuXsG0sef9rZsLauouOp$d*BvS??oj#G6)N9FLgjl`sLauS5TWv;43&L$ zsQe^D<!2Epzlc!zRffuMu29(@4wc_yLgheAs2p^KN)tn<T*hRmOlR&;xtzs?$_y48 zDl=J3sLWzARIXr=P?^mlp>id&LuHOWhs{vu=mGVr$Ye^{)vORL*DysXn+sLu=w)Ts zx@H$noyUZ)Maz8VgbJ?zSR_=+%C2XwP$89WX3aM-T>lOX&~2<)UBqr-w=!Hs2bRj2 zZ(@s?Ca%#|W2Pkhsduquj0O%|&zjaillzzxXMy`!(_*G7Dm}!S9$=YvY;0yC1Rr5d zxS4sBMFR$k{KUeyQ$I@z*X_SBdF%D7jefJyejEL6qXRZNXrm@WqRVVF-A0$&Xoihu z8uoqMEF*j$cZCt2c(xI9A9tnUx{sS<;11y`Bcign6Cf(T-e?{!uaxzsHR=N6218ZG z<}ER16%H2>zRZ}d>hSe^W2vFg-58>Lxe?hStK6*)Z%qQ;y$N`06Y$n2;N6#iw;=)V z{sg=S6Yw@A;B9Heqw+@*@SaG(dpZH{xj3E-s4``R**w}TQ|!O><|?LY<ovhNTxG5{ z*O>R3Yt410Qg)w-Tj}x*X7g~Y_(ro?+=1UOYs!PBxb1wvROlfS31O2cHj845C>|EY zBcgZ|McHGbcw7`uh~i06JSB>!Me&R%o)yJ&qIg~uFNoqrQEU~(Hc`AJikC&PT@*V+ zvC~|tl<gA5E24N+6t9Wmby2(_iZ?~^mMGp9#XF*SR}}Aw;(c?eCY<qsxm>edVmm<v z^F!0V(H3=>{E;+ywW&+zeQGY(9VaRun+w%X;4wMUXOWR?rWomSXCzU_NMFd29QiL3 z%=}gC%zI*H{<^K1zezCj-q@L2oSBufZ?SYr*>`3WvhDYh{z1||N_wB9f0FdilKw@~ zze@TyN$;2R?~*<s>4TDPvIKpZCFtprzFg8XBt28ovm||mq-RU|N=eU=^i`6+TGH1@ zdak6ewUDFdiDJH`m|-7WXU&e#ZbwGq*p+G!kXE5m`j{vl7sV4O%Ad4w%#}Z7HFr}= zpBCI_1cRQnmXl7SK|N<v@&?$lPvqyV8zYCcR=nBF%X`@phX!dcSy$;fO6gAMS-uNJ z-YbH7RZy=9>UBZAA*|RfiZ?AJ_dI&rYRY*Fw!Q<PymwJk^4>$c^1cxN0NmodkF2H- zL3}JK^oiAkRQjo<#A@-ITMH^K-4D-Eary5yI$)!N66G~<fyyqk(R3iSnCNogw=B#s zL-J{+M9LMsX%<)VW^<v*m0W5vM^xx4E;PBCM>G+S(s)y*TC6Cy3ygX*pOHzW${nZz zEfGjj?i8rJd?{}ljy!XhKuY;Cfl8ITc{2{X<<UVE6$2OBS3q7_ah}C~%cGP$PBfe% z-*Jf^=TrdnJy#0QAjluMQjWn2Y!E#WS(@EAn||b!8Ksm`NR`S*RQAPK(?0$ak5Y0Z z)lm)1D{-=KAR#SdO>?{v9a*TPI{Jyr8ljHraA6v}5iP%yI!l94G_BB-(ziLnvh-cv z{0>)2-;<U1?aBwT@}XV%NLD`K%^yR<PerBtbKd+JSJZFBdZ~N4xCDU@Ti{*#83Lus zFMN5}mA{5bP5F&COGobK&C-*<^Jd}71L#tq9OTWynN6NTrQkAAO!tV9F84IUp)))} zWTt0%*rl@^QYpOxvZb?847}1)D4aRR(;W8eRW_-VU+sZcuYr&{R|=J1>uKIV%fxl> zd{5ZV*P)%RN1-i1q1+(nTo|5HQ*MMRKsR}sZFk@7X}0~nNHi2Hw|JU`uWz-{Z9+^b zSS$v;9YvrS<0*kVJk4ig1(tZQ1R`cV_9Jp_smEU3|GOwG`2Qse3;vIzknR?1z1$<T zzQ+@ZniXNPKw0ULQL;)d$7<norC^QdbgvAMwIV<Y*2&iO(EzzG5+EByfNVrje!mES z2SnEg#R5Mh`)mpe7Hk$vwFOK~dDvq|>LVURs#5-_2(QQN@OnHNUQamTMNis6^;9IN zo(>0<`i$uMtQ_<?JAR%I$IlDl_<7NepRIP_Y_rizQU!%xb|PiFC-Py6ee}E{Y<LyL z75p^>5547)>-o0FK2cucnb8C)eU0?}g{S#*4|emH7|i4O+!MV&{L$kOKZS{3J?_p* z-T@d<oOjUEj8v^Od7T5|GVk)}0b#SIyIGj@a<4G2e1?r?+Gv(Uc~?kOHrqy50*NzY z4zN5ku9AGZ8b~R<#)~zaiz4q@LCq7?d_i3&sOwRbEfB>G-a>H%F7!6bBk)EqE<5vX zLQToL*(>fq7kTad>TSjmq}+-=#f!b-=xO#g-|hvz!#kVydgykqQhcXuSn4#~6=|Sl zV$!=svD_;hbC0)K+&Qk0m6cxMC0ZpawA$N@8P-4)?mT-QDVLJ+6QTG;6u*h$cTpS^ z#bqjr%SAC$6jzAiN>N-Tifcr1ttjS;;(AfsAc`ABakKgcquuOHCecEY=q<bUwvFDg z(YrQ!&qnXt=mQ&lXrqq=Qffp|EsBdo@lR1yiDHr{CW>N$DE=XeaiSP2ioc8EJW-r0 ziZOD;kL`g!vC*eC`pib3%h^&y@ueiclI0$h`fm0OJLOyUHQUQt*mvxE@PDv-U2916 zf{k9Z(N-I6v(Za7df7(XZM4HiJ8iVfMz7fDRU56d?7=Llzyth2e#-rPFMEi$U~1-Y zzGd%t_Of?9E$lsyoBKYvAGG3r2<}G?H$3K7;O+6W*ye2X_E8nfQjTMTRq8faQ;+#O zY2D8xy~hw@$BtDkuW~$HkZ$%0s8+Ia61zY(`(2<}Ck#HC`cEWYJc&Ff)~Mbz<#fGX zHBbGQYMs_V{7-e{8Ca@%Gn7)bj?9t<vIdo@-fU$YL-lW{{(Tt{zu;~n&)DTe-V4iB zZ=T{;@>FjJr9vUqdz5lH<zbe8K2Ig)MWnnyWL3XNCA@}|t>{*}4c+QqB1)=%nJD>R z+lf*db`YfocM_$Ab`kj+U!jupi%EHvC}Z+#M43}wr;@Cxq`X0teaUVr$@w=aZxZGH z=Pm5kX>Swd|MwlDf=k~e>d-`W)bTQEpia|iplThhtfnlevw{Y9QN^d$WUiunRI=94 z3YGZ1v{EI{T3V%&cO5~Yr1i8~rR4hvlc#K;H7ccUq<d9LyPwvo<a>bDsg(X8!K#dh z2=-)dqV+0eZKnHF%HBd7RLXgnHma2S2;Gm&@+du^s-9z%$4S`+;#lP^D*O&QyiL&Q z9ih{^^u4NC@1a!r`_SF<0lf-kJ`_qOeMCP%nUBR3DW8bJQ$H1hr+p^W_I*x2LYXhb zL>XVwK9w@RqMxA59{Lo@d@YpE`G($tGJEMgDAPhetCat(Ftgx0p>T)qg%djdAk6Rd zqcHraeZug<pM<fUe-?%x{fjWR%df)NuD{V-P4)Cb9w}U>5$*tC#f>VgxJeaO+^h;K z79n&zx2VF3TUBAjZR&cBk{7GOirZCTMYAfbxI-0IEK%>%DE&@VSg};ypi$;s>PC&S zmZ^7Xlzq1<tXQtzqEYTW>g^iktx)gRD1W6YtXQQAD^{z*iZ!aR;$Br)u~rpUtW$** z>s4XJeX6ixgDR}psBVQ7Mas)+;a*s=U0AV0Sg})Du}fO<im>8UX~k>Oir3W^Sn-Cm zVz;#7O=-nj(u%j$Z(+qd(u#N0?_kAy>i4kXef1+)@qx7BLv=T-_(**RR(!1f04qL` zR(vY0_)J>yxwPU7X~mb)im#*<d!!X#ODn#SR_s;h=&Glma-~+dS|@Xk1}m=8V8zv% zu;Lm`STR?F71wIQig}u_V!pOUr{wE2Va4^DuwsEGthhlFRxH%+)hYc(O;~Z0wpORi zo3(X1Wi8U~&?);CO;~ZOcB4+Yw`sTNl($%0uT%c*ny{i-6IR@z2`iRp!iqaJVZ~BS zSaFvotXQTAEAG~W70WeY#XZ_KUG)|#Yc*vzh?AA|n)*7-xlelo*AoHd9X;SZRe3;D zFF4xVs9n&-x?kJQRPR9LB~AH+sn#InRgHZiA4-17#6`_ljLg@xJxtNcm9HW2y4HeY z#41;IYs&Y6I#YQ|Q}zk!Eae?d`9)B}l=pC}A*d0`2bywFP@|QPG-bL0>OAEWO_?dE zzbT(-%4|W6Rld-as|58A<tt5@E2s+PYfZUUP!pBCnlevNRm!)TGG9;^Dc@_#b;h-Z zRipf<DR-Kx^)KaB{qGHA{;K_Elvuyv(rv#{!uJFJZj^X_2i|9p_W;%~>7aJNpyVd~ zpg}2@=~&Oy>3Wk%X_xDlndF<HPd6!jrv8~h8ME{+49dJh|H`1O+4?UAWnZa(ZBWh} zeXl{eSLxpxly|lMy+Qfc=(`Onn5(~KP={;vcMR${PygAVPV@Em3_9vM{R4vvuh%~^ zsPh8-6N8SvLI261E(`S^4eENMez{rF?IxX+8D_~bHv`W!OS&%to@JKwKx`;im?g*F z3Ow5^Iqo*#E6tLgi-G5uCB1G3zRD~)z8Uyxv!pkoOS#4@DOv(N*DUFCC-AjqN#CWw z^URWdcLBd?&<V@*w+-rlxBjj{#mn{g4Lb22{X>IJTA_bz(8(+HPYpU{mHxRw16J!_ z8g%L!eUCw>-K&3N(4W@oEd~u-r+;VA>Ff0$4EpnZ`g~I@*?>|Tv{Ap#R7>wisg*sT zUvH}A52DltKcp`()iXAs)Xv<j-(adkwxHDh^02<pRL^<@r8e|Y{YFzg`!ST-u*dbA zOm+AZD7AB*)NeM`5l^AiMn0`CGSyMfpwvb`tKVX(W1d5)o%_6gtEry%0!r=t7xmjr z^{-n|YJc0NFE-V`zl2h|;AQ=GQyse<rFP*Cz1dX9?bPou)qm{Lmzb*m75!a=d86`? z?pbH5=5BpGBI`~4K2zmy=^ISd^R~XxRK4$@)RNxS?>E)t_w)x$HRXN%L6cHH&>un= zeW-6U6{h^6XKXRa{8WF~B<nN%5tI1m`lBX!zR({t$@``LxJgN0=}(xHyhnf1q?E7q zr!dAh`qLO=ul@`|u|<Ctq4=%-oJkqq>Cc;L=J)ywCT0Dgzi6u2Kcduf_UT(qHTNf! zTHeq4J7VUmS=!5(dB46LGyksdz|05qotXKcz6&!qu~#tjW$abVJe|FUnJ;IrW9Av` z4a_`~?Z(Wr*qfO73ig(%X3R#ZWnRhNHr1>-D7EaX*dBOfnsPJqe29szWglUpdF*3M zG@pHfiLPUxVxsHWXP9UK`y3P9z`npl3)z>L=tlMxCc25?;Fyl>$$H{?Xcs%Pv;GPj z(wV)=mRn@K#&U_j&hD`YS8XdS^6q9UElPTmt+FWjEw<XCl(*R$i&EcV_ga+pE?aAn z?>$^TQu_OBy+s)xu=^~^{E%(1DC;A((W2~++5Hyfe8L{EDECv=k@7xc4_cJ}IeW;W zf-l(17IpZNZL+B2S8TIIo%XOT79I69d)T7FZ`dOib>7P!wdm*;_LxOozGaVF)b%@t z3$1S7vnMP%<_GqqMcsd7Pg&GsAA8!OV}D}LSajUa>{*L?{=&9f)azHa!=mGVV>>PC zy`MEu(E(PVdS;Tc-8dFbxZA*0;c^3>xW|AeRv0j!uQXu3XO#i-y{iovp0vh*;mP+J zFg#_g0mD<*88AF;y#d30_ZcueeS-nRGd3C>Df4~<24+2Az>w?*4H%O1kO4z-HyKbb zZ?ggQ^0ydpdcng+E_HasfQvgmYQSZk9y8#wqaHWlvce|}_@nca#zFYtDFc4$^0Wc} zb$!Nwx4J!Rz$?c*XTU4npEux@9xoX1%CRpR@XB#p4S1#JHUnPi^^yVWkAK;?f~&Zz zQ(iapYq)CeGH}`a3hd{v8aQTOGj8Onbu}r-Q42YlZyC2j{901pGnB<#VRRe+z)+e6 zbvysaP&OkTuOoFizlVQr^jv`+Ul=PnSzj8fIPtHH)to$gj5VCRUmN#wO8UlF%PD!U zv5r$pi?N<l>bJ&yoYKBCHgNKNZ*1h0{)2Hpr;Hzs2RLQ!GalrW^^@@sr|h4NO`LLm zF*b9`{ngmQDepJqVNUt`jYl{Y{BAtTslx%|F-{#18jo}8)MVmn;xZFg6VuHnI30Dl z`6Q>p8Rk=*I?ptp=5+Kd^BGQEt}vhF)OEJ`9H(wqn$L4OW{&v+r|wsoFLJfV)#g^N z9(xT+?YOz-Hm>%(7Nyo}p7|12kDrfH>wTU1GN+>J%{)HXpmJ3kHA2fLW%LNW0QZ7E z*5TX{hzI4o5oRY+&i7eI{nf_{|K{^_?Mb$LZ`H<?BecGxoGiGCrv2GzEjz}>Qw+7t zYfLq?GHzUA=w%+`-v%pFrWr<=@?XO&BjZxTDpQRnGI*J0Tt<ecOgE;J;Vomv<zyt4 z8O97UlFLkECK)MZmNB13XyruL(FlDosn^p8b_Qt+XoP`#up4NEiQBM+G{X7|88^}h zeioTG(Fo5_vTmjk-m{4>q7g~MaG6Xal856unMS0Xg9~LEkvalb$}}QvBrcU{gl`nC zm1#u!Xk0AQh>S6~TBZ@1=TiDC8k`EVaLUr)v~mr^Y#Qt<XCUsR5n1PvvXn+-pHIqN zG$QA(xaZdX4L=y#G(#=Nsb*;CJYMkfNbvHK44XH>u>JKE`Lw{(u&t7mXJFezQl5ov zlSp|EwpHQOr=0f)-Bs;<LVr#BfDA2<Yaf!K=Xtb`$Y6P1Ok`lN8&#inGu@>6^hI>D z>SMRiBGqTyO1G#!^ESFw^;wJQHr2;(r^Twz(@eLkKJOjWtoo9c&>gBT`A%A*`cjtC zovJVOE?TPk(w5O(s?T>fEmM8z%js^_mvIk0M#>Y!zQ$ay(Kndub=r%$-k=uDwVS@h zTyN5TVyiTtHcwrx`Skhf8qLS9Q}5M$#`Wr2&1Wva<vaTl0xzgvLEuGo4+OTVUqfJ< z`V9nLQd?BET=!`+w0m@)K2uwv``9dPrS3DX&{pX_bGEiZGw#z3s$tf1kXxxe54ly^ z3y@o_y$HEA+E&QjtF6<N4Vv<h*73gv$?#L{@YC$@)9vtMcK8`~_?dS2SqQ(E*a&SC zR{Lc(Lf;HLpL}eKb{+YQhqdd;XFh^h^jVK;H;|7%rY$6&=W*>u@_C=oZX#dOlUNU5 z@>AL(@})ej-9o<9XS7?%m-ei78~J?CX^Y91{=9ZO`7&P6wlQo8ZZwlG^F{3r@?~w+ zmXI%do3>rk>|ptf`Lvzd=gg<SqP?LxJMYaf@phPaH%z=ACO!-kABTxg!^G!d;>$3x zCro^!DJ`1vou>StDL>gdWC{msKWo1lKK&OB_Ybz@C%W>RuFPf1Ei6Uk1yy^&O4puY zFIXA+v+PAHojr##!+4%;wbIQOP-a*!vTas6--<HBvyHuErF&mOnUVA|yUX$=Z)eLa zU&;=4x8+OS$qq8IhuvW~d1tqwylZGTbA!aQzeU_o>01xo!VQhS_t5)>@}Z%8Y-r1P zy7sBDjA!7A{cf&Mp5lF3qwUB@>2&fwE$tIf8FWeuP$mud9w>`W-3OFSr~LwyLx0*2 zluH8-0_D-^(~-yW>CZEP3aDf@PzM@x6;MYioeR{7%C0qZ<*4gW96b+3;d~UGU(@MM zB)=|H{;^K)vNGyQgMZa&o2e_^u3_{J@G*;+`i%+WF17ZUWomai;|)X8%9I{-W<i;9 zEDgDWmnp~5UvT?yBQK+#bk+h>zm@Asuf-^iZ${C3vstDT(a;CAGwx%CqA*K*K1>;7 z%M_x_K$)Ua);oHc;-l=EGDV}D(lQ0Nm}O-+Df7z96x^!hl_@3_P?>@Yo`Gcw&Z!No zgmS6VMpmNc&{6lZ5-pnwA7CYV7Il7*m9R`Y`XN?gWKfq)ti(*GuA5nj<)dy}SP4&~ zV;*KDo>c1o2rKcXP>)AhNm4Q$JH=3NO6=2tJjTzuc+ulAR+5}V$34zUQoMBB6RafF zLp`5lC25>`JpeO2bo@3TFZG@arzBC)R-k0+GY9shP~WFmiO-^bzhW%hV9N4<ESs1p zr^|ANEN97bwk+q!@@iSmmE}BHKFuiIe1=hm@hqcEL@#CO*P#Vzc!5Bgwoo9h-6W7l zTO^QIyH%hRZLvV9TC+fD+7f|$+ERhiwPgZjXzK-1o@bP;y}(LxR65~BR+3B9e=94= zQ>b_w%t}>HJZZPCD`~qlO*cv1N8eJ*%e1{uVUt#2>?Kusnf8*0MCny`piI(sqD<D` zqLv*rR^PF6u&y?#uea=^v8<8}HWYN!TXxZSeOIOFw_0AIU6s7jQ|Y}pJxx(sUX=}C zbX6*o{K+k^$$E-EwdHk5rTKj=Z%8WLpV6|L#%4<S!I|o@EpN&B#%5J!-6JQhU@dRc zcvhHqilXeLPV8N3d6&j!S7zH<7%lJ7cw;<+8aXZRi;+ad9_j;3G2R%QOUJf+1oBKp z(Y0h!XBaSl;`TnBr&s8OGb@<%0tmAZvst3xqVT{C%EY~tMJT$eRePzM_D@j+MR7Ta z@>*HmMlDr4D)XBPUT^tlSEa(cv(huYA}MWvqVVxa<Go!l&tA&atA+ZtE!E?7OxjXA zwnJryunl!WYZ&maUAqQ%RK^?rq?Uj7OY-_2K6ubdvb-%pd)Q8`hdtJ46vIw#X%xen zEtAI{Re4l+*eMuR_~nx5utE2*|JhktSeVz-cI<y!{yVmFW#@3;OUE8P-az}{qv6RW zijHtO*~)Yg%7kK+DKcl080rdYnF)srceY$X<E_fBz4~S*E6UhzmEGj{X&jYfDvyyB zIo)irik1wfD{Znn6u*kb_7Ii9J)p!jF-izKraN|A>!@_BQ0ZSWDxGM}mnvOPE%R-a zc+2%P_PEO9!iFvo({VZT4K~@;-?L?*O$sG$w8>un<6CaF_2E*VTU`2FEA^S_=yRK+ zPj8`5V~jonaM2|6xr176xApO~+(BcDDvQGUED_Urq&|1rq?mcBP4@BkZCOTQX;A4W z*XC|pCy&(W9+ytHNu4fpbXrMcPuQ`uvj5-{AhwFmKvu_9sCp9if`5A-sHFH2%j9pV z*jagEVIEQ`q)+sp)G`5`3iCWzuxS4Px*J*&sqczZGR)aaP@jP?1YwHZGi)!NPQ%3R zIcG1GX(L+Bakj|__{Z8gy@ID*Exg0WTef$~^-)lQjcFM*_T<WwZI7^)(c{^8T{>h8 z`guT~GQGl^c8cig9q&O`B(XD$^IHDD*1iNzj;dO}@4a=ayF<hfKm=bj=mbq1Ohgbx zgn-Zqit<p9AXKmhQZ@?e{|UUOs7xlAO!j?WC;Ptd`@V18J(*1QeIX=a?quKdKj+-K zUDdovczMs?FPT2~)OXH3b=UJ%Rad{_Jmx*7t)i+~LA1d3e&jx`UM1;=?vK@L;3-NA z!pjfcR^yIE&^EWlYA8oHnuJDs@u7I^jz54t9*e0r;54Hm&iGI~%_sF~!hh*6oHZj) zR<a+nC-DrQ!j<4@^^|{xPeY_pIKx=2moUs&LG-J1hR<8iRI;DTXVo(qIFFj(OFip6 z={;$BKLK57<*`z?v1&p~8IHiFBt7Lmt$rGG@6!*(qIV33NMo_6dLAOZmoqMI$G*HL z@^JG+9!n-b`!!2)@pRQ6)w8hP7jLa(A4Hz5WOuP9s!3j?A`L;+s*Mp^WnV4cs-AP2 zq%+@4G!gYxlZPyE$6@eq#f179c&f|5e;hphGSx`E42t_X7vhVR>`#%`LF=T_iKYw| z*{^5SE~pDY{T!;lVF@AbgQ_DfD%pRq7I5r$PLmYuEm&I;{N)KQ<OiUCHPz^q2IX)f ze~-MXUUPox{S=h1H@@Hfnfk3?avOi$eO|S|QsZy9KL^e4QFeZjU&s=_VD1a*4}Q6b z%Kxt3@yo5L{Gxi-`6X%+zl727N2t94W3W4>8iA|#p!jVLMZYy4%C_c%j9K!wRHM{$ zk*YpSJp!ulSX@|5H1Inb=rs45<NwAD8<e|mq+`teEvel6fh^%Nx4<tArqY|vTUbuK z#p;8*MiFLNc2y};k3;FZoLlz2SbEH~$xs$~j1D($1g+W-q89P|;S5zzf#T6ngrIsA zocsV!<y%3793p(%d%N)u-QUyaW9}cEcTh^aLm~VSJ20=3d66GgO~#t>*nKh*$+y&F zP1TQ5h3}TD$J5>)p}NNqTMw~K5Ss{N6HQeU!ZuAO{uBZ5k1009eIEiO`zKKK1ysTG zQcr2+lMt``GAf_PsAsUq^Ofvr_B<4yi$H*l1ppq-ZSuc(e{tT+pT!dIG53AHG@D8v zIRA;|#DCJs{e*E#)~=ev)T8KtM;--%ygq|okMkV2m8^h$xA}kNMe-uUlmAu87BK!$ z^S|a@$Kqcr*+M41*ZgmJ42yrOWQ&;faPtqHzvnMziNCYthn2u_<s;4i0h*<x`9~#N z#_Y$M|Fa=W{<9Ka*!Fnyj~epiN0n>^i#^f2kTn#^Lf9ZyvUua>Em=b=*%C^tSdFKf z7qNyiSp=ok?6zl`w_*+LBqkwiSj|N9*0A*^TSI9r`$E&^#jIg0i97K+c6&S4ybX|q zWE&{1XJ72VnwP+0l`Mhc23D&RYu=XSiDX;mv}0a727zwGK;8C%ViPGk1d7e1=olzc zr05hVGNkAnD7KKI3v;^qx<pq-z3~Qie@#7Fw=zf+LHAo6uGBe;SY#4w{^5hRZb~+p zHUGzhQEbI~_;luX{8;9UW8OHH7^_{n*ui(BxA{!>@O%=fj|;-SSAg%li0^wP_-<HJ z`(A5&!=^cbc@tP-y!QQO)_2<XohFfdzl94yJCQSfQqDw{KZGYHGUiTV&Sd6IW~dsf zRZ~K$;aW8{q#CJJ(?Y7zS~WeS8mm<^LaOmvH8Z4|s8zE<s>xb4JEWSbRdYhB=~^{6 zq?)N!^FpfGS~WkUnyXa{j4FFzJ{^FiaUsLCanXMREHZ$_1XvO}v{XA?8d5FOs%6Ys z?rRgvb$7L5da^I)>LcA<AAvxf)tcc*$k1#*!y~sh-;1+kFHDQGoPDwRKAa@`pmdJ& zTFv*fJS({$O6R$_qxk`r7fT+1(gkkSZhnyE)k+?O(mnFq{-`;`yhA|knR}S|avSCz zF>(oWj~clxb3Zn6JLVoUa(m`}V&o3YJ#OTV%spY`PRu=N<j&0f)W}^JByzso74kVF zcVljqk-IbZl#zQd_q36FGWU#;dolN{k$W@uoRRx5_q>t&GWP;=F8am9McwTE7>@~S z9;+%5xRpb~1lizikyk5OTfyM(OZ>G;)=r4mA!V{0*gK@7UduR_n0JZY7lW;%y(lJz zaG7Niml-QAu7KU-^<to$PG01_H>GohrM;^xafPw>-n45y4fzWsWA+>BjWpy**BDf& zep?W&dnaVA$^{Y_Ek)yk0tuN_fn5`Pt2ZIzA+1v&yMa&04DGvTUB9kc5K}B>kc^1C zG33ZQcp~pQ*BPWT*BOVSZE*C6+fo0XQ^-?^LXIg;2^oD2lF>*@I{TI^Rro}?Dq>)q zhST!R+cItg^C!qKh6yE_3}@IXY$S3@J_`PNViW@ofG*(tM>5FIM!{C|UIm`~dyu#_ z(I;+=VQLIZm(iI^-~d3E?*zJ~#<5I+Onl5DrC>UqmEqZ2HL+19undG#N*>F)y;o6I zVBI|pPJR;0Kz!|(1@QsT9fOx>ArRQ9FsK#qca#t-C~%l;S6Iq)!&bE-C$zc{np(+H znfDuXdv7Z?FU99xV=)O(Q|ET5a}}W6t4yt?y4zFJei3U(Gp8lAZA)s~4%D{yP)s{0 zfSt@K;?N~U9NV;GU<cE#71*^RyG{&vj(D(=brq3MA^vWHeM;sqNm}!iQ_RzeV$R?* zgP)1kTr|ZOZa~&6x^e{e;W+3l{9{w~eRw51z*&Eet-%Mt$2cJdavaU0x#|l}+i<T9 zpI#gLpdWWiFv=2$QuXJ#Z8m^YdsV1GJcF&4pbniNWGPoec!AXel&X~DP=(6XP>@nH zYTpTNhw)T_odOC$ZCVR!8o^WWzrg<eHOOcO0W>di&tz!d(G<U|RS&yl4A@!NCvRGB zMhl{PzF@CJ0cU1H+1*>fZXC}*wJ>nF?w<)9`=<-_!-Lci^Qe91(@FHMjpJ$o*VDME zTEv~Uc;0QPce;jpZw*<k<xV?1-j0rUgQQo82P#=_5h;U~>?0U{OcuP1&|A3Eo+DRi zPu<g<Iu+pkMC1VA`U~g~#2w^L2f#TUp*K51Z+0Mfj}ZKXhMyq#N$zwC!#feYCqt?N z(|C}GoCo+|0SiCEFL0+bMbVjXy+XJmLAWA;xK_gHLbxu3>rJ?C5U!)fbtGJ8;dCWj zR}K`Yi-7suC*-rQ#`Yy_KjCyEY&Y`RH-sCmal;8WLO9(C*PU<^0B6a!@W|J2uWQF5 zU^GXtuc`afFc`fnY<lh6py$4xN<$~tdIZF8I6e4-u|yBfYJUxic}`F6_2lr`i+jCz zy#=*Yy>y~Cgj_2Mhrg*u$G0H(iLh>1ay?D2@1z=it0FsbB=!7vR72Q(aXo3I8qvt# zs=k}aW8*()@Imb_Mk4joc@Y?nFc-aIKK+B*P4UXqLczZW>6#D^tMBRK?^mdY%W!5q z0u$rTdmx7I>k$0_Pe#iqJV@*dtfW3EoSZ*akEb$FRzKDg;vSq3Po!XJqFIo<21m>k z$lQGZra)sjsYx(&EqO1V)9<haVMF+xdON%uOogS#l6N6uJ*0(dV|d+~M&|#92g@mE z9D~nEs*0S&YkJ^eFM8M?UcqL-=(XfSc<>l`fR*f+UdfIzb^K<_*et4?Os8?b@@ZVn z2+i1;*%>=KXU5JB;$Ch{A&**_Gi~R<w6)~75wwv&z3GJ%IDI$j>AR7uRPNlJOQ%CN zyU7M7<!(JGcXPG3+6g=_usLdMj%u5uTpg=+7SE@VpNBT5$OdN0DLtD{adjqlDlZ7Y zR}J{824CgsI?tKU3km!!1h*AFxUHUpZG~!|GpRe^q+S$w?_msjXoDU?^~#;uiv#dr z10JlwgM}*1o!&rPAdLkW)UuR?$kZ00DzNHhl;@s>apTP4-W=X|Ii&L3msdazZ{(Gb zyc>BHWbH;?&D@1XUIWRxk=HVJv60t7j&9`jkenNN17zh!-pJhLM&88S6-M67+?7U7 zF?W@b)689M<P3Ay7`cqOYmK~xx$BHv&fN9f+29uw8#tw+Tk)Eebq6-b53nt_i^vbu z57W?c%PGeh%xGZ$q>}9ytSPKa{Fitg?^n39TQ8y6v(6@%iJQQkYMLtC$hWHJGu~!i z`%5r)e(t2W2g5tfy)>_PB$mi<USIt(<CSsP(|<*)pL!{cMObeB4VRl&GG%GlwYPA0 z3s=nwEZm6ItC$X{mmyJ=ckh5@=e10kTgKJvWf12I%4=#@?GkwP*^fnEPFYEZ?!-fb zf<xsTkFBB&_t8prNJL5*LWX7`X=f|<w({|{!?|_^EFcw}6(`2hqO+Reg`KZdvcn=W zg6u~I_K9tTSwq|E4=dRb5g7vwc~n4KA`~e3cAiCNoE_ZT0qKw(iDZgu@kpd@QDQp{ z-?g-heV;r`)E*|1he_<_<~wQmSw{i<xRQM=V4WrNX=D!cc-Q9yunTAB47}d3o`R^4 z^l%n9IVRXFaxy#AV7p;y*$s`mju!k4WbqVQ%+nV0$YOptYQK$PZ*}%?Zx4T;n%Q^s zeqsZ8+GrZ!xPXaIXRw%DE%}@V*h>wtDHs7Of)Ox|M!@Fa*t+1@1Ui-qj%^B#O{8OK z3h9kXwgazjRN~|4X$mQmMS_?eLy!XGMa;RS>D*Kq%Y+U;82!wt@1xi=q0v*JPrZWb z*~XmxpVN8!Y4nuAIZ!Hk0(KDa(1h(F2YXoh*R1e~t+1Cm2eO_ri33#c7D_dLNau$v z{v_m%497V?K-LGdr)f^eL|&FSNcEP}-1;ibjU!}pG;8Bc!rd{6745{OS=PZt<RSk$ zawR(`*h8rK9xT%8VHkb;Y1RHBdT9tdf1?ykK<tJCz!5Dup4@+rNHpY7>v`m1oYL$O zWYOQprRq`ja0(Zga+t~Lk>ahu6u)0wp&r$X&krGEy4{ULYW>=$ia%06O40g*O!4so zsrzX{Z_L1;uj&JJghK6_u)6)Y49Mdl4&<>yJpn6K18|tW3%Wr)S?hNA+qf))haP(X z??R}b;9_-<2?)9a1)WGW@-shJf)W@zYl2ZX#i*ZCO(<#%w<$PzE-&&I2=}KM2p1Zq z*5efJA!a?Vo-eS#i~4yPBx(>hR{4tpYe@sZqy7cR)hMWcr=VUeOC#cE58zEs^%4ek zh}khquBD(}p_a5UuqgH4E41=At7PW||4k*kAjE4Js`Z*`mP#Dvk)o~YRR{`$g}-@` z#1Ss*tKT?B`GYkRM>+eTLcK;!jjKGgDc$bDeMY`s$u0}qQLn?$&`Um!52#!@Wwl{G z&@NF+M#%Sj5RYn}p&M0LQ-N)&dQI;Uv^R`^Q*x1n9j1Kfdw51~;n0F9^cKyaw^R#Q zdQ&%BbB~bi_t7?qwy<_4$u<dD?XDZy(sKEG=$fOH^1X!S@1Qv>T<?%M{Jk#tyXxH= zcz_kFs6zb_TkZ?ka(J+;2q}afgGCVAC&78W@1NKE<otd00W1nP@T^yZ<IIM!_a55* z6>VX9|CLrR_}kjzf5UDSA7YT!Kh@t-X>9hy#~gR+4^x?J2}a@HfmYPSA@vVp8UNG+ z0mj-#1roZ(DjNzzp-gvAOI9GE@)OLCTCx+3vDVD5zO`OmTI<yXKU!M?ugjNM7G96P zE@WH0Qyz*1s%yh^GzHdBt&*xO^Fy%d<~YOvalkazA#`9Ve0%~dWEqT_W2ox{FbJy( zRv=4uqp&_<G$AX1KUmPvbP3}Vih>_!>I6$S{6ftJaMGW`np+AolEO;5rc(>3nW|#W zCmfC>KH&^hkAmtnAaBPj4`&#S;fh-KL*X1tr|(4L^V%2zm&jO~sw)g<<ta!oEcqo| z3tM1&!CY*iyp+i;PQ%*nFP?AvBjIft32&?4WBPlX*7CEoc;1PqKf&Vp=TzbZkJMNH zF9pZW2Od7~JLCfz4<D%iL=Sowx<IG~i256%U@Q3>?j#?o5B*K#@34oYX!EGA{(+7! z(hR7FCLj5ZKcbHQi2C^>RR}$rF`LCDS`t4%bSwX!K`Wgz;KvS5<Dwo73s`oF84jOJ zXlxcU9E2%9hx{t$kZqV=!qfNRAT7a1Tj+U<>^gxtWLuVZ9jd>~gtX*`SbYb-`VPAK z4!Zh|{(~&4V{W~jSQ(b?LW<v+mGuBs7gh#~u8yH=P7K{BhA!}T>0dC0yvUa^h8{YG zD**?&$i0ia@m1zt;=X*1xtEQ6ow-+xT*%$4MsCU7Yep{O?sX%#;%=ev<<{J7Y2;$= z78$t>cUu{`guAVc+?Kn=MsCO5Hb!pG-4Y{r;BH$ZcjRt6BX{C%dn0$|ZU-ZG;ciFa zbn=UdPJ%c{SG+H8-GyB+ocSbVI8!4S(vYh}L#AjNGKtY{G#xLw@`cnfi|CT81xX>8 zPhn0MkxFy{zmVdl3OkFfkfmn4uA(+x1)0N~Zo=yZ3{iO91@e&|0tW3uK=c&Qe~Xw- z3Kp|8DquK$4ZCs~)10K2aC_lpU7wWbi?oo4(vaFp7ixi*^b~3}P2g)e#KezF6O-#X z#KauZ#H5f{Lri=~+UYI4-XfQn^bzSqACV;{EqOJ>#MmeL3d9ug8xxa$B8$#A{e{<G z<ZdSY1kT!4{KmxOrse|#t}3nhjfu&?&k10lFvO&o-;kJO8*GrsBnAmXOxo}p5|f}# zzm1WY3>MyCp|f+}ks&4}{KmxOrr{5v258H}#KepMO-$PHFflR5G%;z<!^FfK)5N3$ zzcDfKkwi@V^9vD^K`?~y+=!S2hYc~wo_Z<8-jUy!m<;`#&KpXjrxX8tVlpi2DU%pR z^>*f;OH6zVb3!Kavczzzw+pX^nD{m!V$utjWBGMlQ3f&1Oa?KcCWC018H_iy)eszl z19<i9WFWr<b^>e7t+A6{!mtzStbY|d8N_deoj}yL%1(NTTW2SO`Tv%kj1U1k=_xcj z87YXJj1s_3Mu{*x=_zi&PPz!a_~Rf6vlBRf%}ziFu@kI`*a=ltEjt;)Z^BOciPX&& z-co)Ob~5lrwzOs<I~mIVC+wv9WsBGewA>f4<wER)#vrnj5&S0XWay1z(@Vlgeyi-H zr$~$zS$5)=s%0lQ5dIrGq3-e7$teDRWG6xGnw|6%$WBP5*$IX4S$0Ap`0NBMh@FfP z0XrGb|CQ`ytUz`$R#eMQF#ow3I{{;D>a&vxoYPGoSPfImrza_T)W_r)6FCxx`w_8) z`J-Wr-YvE;yww8~V;rsPlW7elrn3WY?(Bs28ZUsR>|#hKrUs7onZDVlx3_&v?I%Zi zW12?DMu<Gbe6n(gwwJ?99X5N*5!_o+w5imG^&hL4PP3vu8XhxM9Mj!@On3h=hCP6+ zMeigtc$T%C^l8IMolu<AwBb|APAfHQsbtVEp;1}^U87lxPBV!yD5jTo%^0ePF;sq* zwVcw7VJ6SAma~5KXLa>wb@k_{`il(uGpFA3$Xa>|Vl5XmYq=O=Ethg)xJ)rz{48s^ zs$-bNv#e#d@Mep~v$;D*`0^a?&NcE}?#?svJnqgn@_g<tF!BQKE;RB&?k+O&BJM6W z@?!2TG4c}bE;aH}?k+R(GVU%n@^bF3F!BoSt~ByW?yfTOD(<c}@@np`G4dMjt~K&n z?yeKgdcT-huUX5wfVFhuK5Oa3RcDT@Wi}77mcE>JsX-iB%V>NILX4qDBdoC;&HHm_ zBjg|(1wEFKD%=39Whl>hn>1?~#+}W=+bo=v@KVHD(l~NQ10sX0WsH9OLXG8VR2bGW zfos-MCfqWiCUT#(Oya~^#_=p`nF2sJBh*xm6MGI1vlc%t&06O2Fl#Y~G;5hx9c%F+ zX=jV@wtR-Ql*58j9<Y}A)v*?1pV&$(%7UA)mI{$YXPj-q+xD4Frh;be!ke&`o0@N@ z^<~jbSj&#j31CN-wJg3dYsog)PRLt#W?9RU8?%<6PQQ(jwd@k!E}ymdjtpyAdK1=i z)9`mw11!s7EoKC0*0MZ@wU}d?wXDctE#{bJEh}%rT6`q27XSQ0tYs$*p`C`c1cwc4 z$)5Tiihb2hSj*ne>Abx(dRG4rSj)bwr%YlW)w|~NS&MIBPRK-Fme^1AuC0!>_%<Qd zvN>QaCH_O&C7f7G2`w{iIkJ{^yb1jbPPMFM>#eYs%&oGPvMg(%&iYremWo?qEty+q zEoJ`;*0Sw?%~}qGSWCudEeADgIfSg`P!4O!+=#Vo6ngQ;K@w&y8N*sKIjkjPSj)j1 zvX<>PV=Wbw-QRHG-ElM4vg1a!I}B^t`9ESU)h}DbS`Hy=Ih4a%Xbd82*>f}2viC-@ z=_O(BEwh%4NE{AWi(jgiwH&C9wHyqwmVN(M))LgNSxbgk%i(~v9H@@991O9RjL%w* zgjmbre<^D@O04B*^{gfHIjjYYwW-fqjs&b_9`{+xJg(;B8me=QqXF4i#Qo8*h|_Rb z#MNSYvgl)4*N+9PWd$JBN}fs_L)Nm2Bc(VVI9kvBzFE&{Y^>*M13A(g(}{q!Z00^| z+01c!N#V0${@#-2u(zaWQ>m{qRK>}FwQMm}Y~j@X_yPU6g{yK-tVQo6p9ZXD8`rd9 z8>fT<KY%uDr|fhGr{!n|SKE1Llvb!6T(g!;z*=^4y|in_u!|#O*ug);T6S|{43z<E z+2>ckPglQBSHGXCKg8Ak+<Fi2GAt2mImn5%9O40MIh+&25sKju{|sySSjV6O)^b*O zXGP;G?w%9Ae2Tm0jeMHB7mR#{yBCdomb;gXe2%-9jeMTFSB!jtyH|~Tk-OK7e2Kf) zjeMEAg_bX0;ciPKU*&F*k*{&Lm65M=x3!TAg<EXomcngg<Ramg7`c^j+geULznExe z5o>8JvaIF2VJ+vmx<IU@D$H8&fnC_8N(8c&ZUQ@|yCBxmL!fzU;dHQ4i4Ndbm55Yf zduyxeATnM@i&zWZ=drv_mebktI$OwEx>zuBy8)uB1^w4u=wzXXNTb5Amfk|MmTs2Y z%~E}Y&szElVl8-C#b+)30qAzM)Br&@OLJI@AD3n=r*l|~Iiy+3nd(@J4@o=SEw8(k z%UXI^=|m4J%UaG>$6AbiqNjzJb2nivy{s%c<Mg(?-d64=)62qHd;TV@<)-F+EV_ex z6V}rAa{}mV8P;;~#;hgVVEwF2qMv10%cUE$mY`0*jghtVx4izAPThS+hP7P232V7& z_yec`uH>*5GXgYgxthaT%rVVcuH~>6b4;_A>o;L7K9X3Ae|{m>(hr6Zo*S{2;ILsW z*;5}#u@{OPvz9@h(|Lnv^t2S8&sqj&J!KMusoo;-xva&vFehXpFG~!edRvKVSc`8H zVlC}NmbFy*tffk`mMUT`r--$jM%L0rRL@$vid$eUu-4ofYw2bg)<T{2uVO9T#I3Lv zi27DpOE>G*Sxa~Ezhy0@R=`@iTAH;CwTQJ0vw*b>v%;*Ut91j`(!tV;KMs;GYk~9E ztObM+Yr&d`wNO>nvX&m=Cak5GmAcu&+f&?xwe-D_Ev=czT6&5932Ui-*&@~gE%ya% zxe#liF^H_Cueb?o8FZuA^pem|+$wA7Y9)qSS=Qp0s%0%W5dIr$q3-cnOMmfyWGzAM znzeMbkhPFXvla^Bv#f<e@L3C35NjD>1*~O=_*b%)kruL+kyf>=1@oVqu@*4arao&a z6<O9YLins@1f&{3P5m5WsK}CyF~T1WV}#B%Akjb$n;1pw`f!nDEt3GLCW};Jv<0kX zia<&+GH^6g_<b`|(AbzM)GTtOH>Ob{%Ub3NpS8>txV_8+awYt|WxjyDB}JP`eYJq9 z7%j4_Ws#|3k)ZC!59t0yLM;}=TJ%mbMr2vbGNEb1GC>IiegJJ)PTA=SLCet!p_Yr# zD6LQ{gk~)|%_PRKQs|{!Glo?H8N&+k8P>8|5MvlCvaDsDU;R2={W@L!da8aC5WU=b zH;6JU5o_5fh_!4I0c+Wu6GMt(*d#u~S~5C@aROP(-B^1s8F{7objz7xc{8l!bPK;$ z23;YnR;;Rz#5n?>zA}QdXRu`WF+}oC45mi9?gZGp?x=xZBy(n3-b`!42b8S0W6mtg zn`Jq(EpN6}??fyy$D$VsIdfqw&b1i6+o}u8)WsK9sID+7RX66$vob%OXR&<i&t)&e z-^6?x7?WUe5!Pjn!OZsWP0fZNMoNYsqa??V(NZjhkAc!!&F}%NAhL|J!15MY(}VIt z%UcNL-W2L06Y3(%TV&Nc?uWXRee(nTOjh8j%vo%Civv$(&JxR8qRSR;;1>AAwqMEW z7U4IjSJT&n*D_;RyB-`|$I85=R$h(CwPDkQRU@xPVjJVMpq3TkHr5zG>sd9<Yd-+Y zVXRaeV0JspEN>Zj(Vu0B<&a6PusFGbIyYh4;+w%XGiRlhu3H!KiIo-%h!ivDPiLQ| z!_F#5wN_ayiz?Gr8NVEfOco+8tZ~!{+nC<T@eN+zd^1+bCdtS)X#2^MZHJF3lJB6K zg*)NzREe)N+s=NCyZH|Gas>9Foy_00m(u)MO}qAfA@?xcyzRZzo6!SI?P9QbCz`U} zm>#L!tX2>B*n>)MwN*rKBEuNe0qm=oIs^-^X=qHM4nr9Z*TR+c2p;=PgU0A9keEd- z#<!L&1{wy=K4t~cx;iO~V;>hpp|ak3SRZ3he8638!HS;AK>P(!EbI*5RQ)bYvXk`1 z>M0`h^)P6+{+M%46~Xt%rqMclKOQ?x1ADNHoMtFLM+<v8xye#zkt|IY)@_!YAP{<< z-Xr&f|20qgk~DtdA6f#sVz!K&XZW@u{M*&y7nr<ZOmT!WnlFMper4QqVv>@f(`^ zg}8)WWnHG*%JMRU?ZStYU=`{z^%cGiDsAC6|6ioenj`Hi3^o&3Z=4LAbRq+zuMCn5 zXuqq_e$E!l+hW~^t$U50X8iqTt9~u)lR~;p2+g(`eTQBIF#7uSS_=WtikJDVe;duP za?2~XriT<;EpKa%B4c?ORMh?_p3?Kt_FT2+d675cWkqk+sP!7YYN<v+{HxSF5O^o9 zI`FcBSaw`><TT?t@f2$Pv*^sTXMw407qp<0z}>YQmh?Ha=MUA0-!b6&^;nHqT=nEB zZ<B=^&F|=*J`Dif*E2zu_HFovpci4A{AFFd;VmSWb_3MCjNSiy<Yo11XNTqOu$-Ni zx6`UOEtda}{Dm^H%d+aLSHR6|?I!1KLvw%_F>AbjlTyC3$Ew>blH~Zdq;ETWEr)x& zk+aY8_F2tXouq?S4V?WsdR`~l0`)IA2Yh`CuSFy0pyeI3n%z;SPI9+}?*?k%9Lg~h zb&~Jmdwm)>hja8+o#apOl`RdNBRP6mC;1HO$1~?>&H=kl@>x8vlsO;g9EjFQ-it32 zY2X~o(Z}i}={wK2IiKX{<8_ix;J(_xIqvIESl$V%S&cf$*~I=DIwx}u+*T*Kkq-1@ z&Zjv?YSu}1*4L%DQ<-z*3w4qk=*XwsQ8`C$uaj(V;T(P0smh7#i%<<6fK={O&XHPm zlG`olxBt-P#A(Pi%d1{he|FAT{!5V5`_5TQzw+purT*rex9(3~u&S8)u5;0HgeUTs z$fsHIlBFJUE^Eb7R9vyt_nfO*u?!X0EcLK+T`QKOqEM<woR*RlD^O7+)%TrNTCoxp zt)+U@Db|WrsAwbA51bOMSdEIdQvJ|rrxj~Z(O#;@oDN#C78M<(`jOK~E7qZ+GtghB zi&m^hMOUe2FsGZ$W6ADP&16mwna7horJBW@UNX;0_QtK>=_B)E$-WXFyY46RY9;$i zHJ3R9WM19mK&j?2XOPTmoE$9iiqH^QuaG56rCPw8p&;|*FbR2qGaO_qIYO$%kQaj- zOOBFi8FNNUXN>g5NPNr2Sm}+GWAWO~IO&Z8X(e;UOK&_#tC%xEdJ{le&76tSn+Vbx z=1h{_B#_oJXR`DrgS4JGQ=~Tqq>ap(D!r+4Y;u~cs*tK-x@n9>AhpHo6os_xFJqDK z1aGKf4TCpS@kZe{REh6~-%w>e6n;aM{9gDCRrbT-H&jI*3Eoh3R>pqbe52Ga)Y>Rv z`Pd{vFEBeN=|yv!VMSr$eppdJ@ogt9^^na-FC&}Pu9HkjJkS7;DPh5RJfknPH1gla zQzpGKnLk&!TVx=Y`*P#7aJNdQ!W?SFzL}_y0xu?);e{8gh(k)jBfqX>J#7Ymd)XZR z_O`_<Ro!j%O5XJ%*qEB7^H^e=WN*s+KDPQzfxP!gNRIm2_;#6le*$7ZTm2S9@S5M> zR&Rp%GY|*Z$^-EQ5C_`oZ4iGIiPU;M5{XIm8@!x=zF)5eQ01uRc?^qh;3X0DTjl0) zEEd#y5Z`8_-pUhLY=OzQ^3?D0EG#C8vAwN+pC_^S2gt!9>Rm{`uTRD;3pSJb&UWd+ zza6lvP9_a7b5g-ByHhrWyz$SlIcyP9TK3=YlwZI%(Y<NmH`8tLLJO9Dm$hJ-cX<m| z_AXF`x{((u*$WK*9A*3k^->F1DvI8<)XOaxTK~FnnJ9W!s{d%g-eJxz>Ftsr@x*S) zoITRpBlAn`{Gs;K;O-rn*emgqKg~r%?voMbME1)Fzc+F~M#K}5gA#wt?vRW?+MX78 zt@;ssk>#^k&$Fq*!?Ii@(q6gDw<h$iT^lgo7MVZXE=65wF<x;&O);Eax!^$eet?FL z<KXFLM;@<aBW(7#`Z1<IPeArlF4dE%O!A0SjZ=ECfN^q*R8K*#R?x7$9Uc59Y^Z-t zc}MVSz&6|q<yZ80Y-wIA_NnwfmHFdsw-QJEcA%Qleh5wV1p}xYXA0Ebn&Cx&bmlp( zN?1zUd6hC*iB|%^bVt~I7vsK4dQ~!iqV1kS_d5gk-HrS1+I@GXdiw5rfqSy^PRZmc zsd}UPU19fwjQcavJ0tVw3HL0z-yOIwHSSBbd)UH<`R<2<d$RM+%H&z8MxgsWVfW*W z`}5K}FY^~j_X4`#8@Qij+)vW(Cowg}cRv-}lbv@#CND@e4c+exyPs>^Uy|M>nLpKb zFQfbYf%^r<{Q~WN0Ru9o-7g0BWanL$$;(nLLH7s3?pGW4SEY9qhOl(6q5Fe@`*p_s zI_-WP1LCCJZv^*b=UtP@Ysi+s^ibG+xp7};dxduX80ogO0dqKTzs<PcrrmF2_^>It z-wE!?&TDBWTiU>5!1PGi{XyftmF=~%^Jm&_Yjl4!aDT+OKcd|qVZcnZ`(xmq?7Y@? zvb7Cd1WZ2;yRS0t+t^+kJAbzAmZ1A%f%`MY{Tc243_aLM?tx!uJFmn}me{}_!1R-_ z`|HMiJKJk#=g+m>_UQh2;J%3a?r|*#_eC7iDDA$Or?j2d-cGi+RU2eQC&KPKao>GM z+v{lO&$r!9=>BBjzN>NHRlD!XRd?Tg4{%R*UMD-*$%d31Og{}J;+5p)>*!{naWhc6 z8OYUO-^~y-Qz5gez-)pso1o1ma5c#{n~Y|sLT0A}vl+&0hBlkQ)hyp^HkzHG)LxsN z1v5KJW+RN*2yHfkt5Lq$Xf!(qP$n;0uvY`RzSG0jdv#CS>uEQ;vrh7`!~_3ydie(q zOYg93<n*?^-gdLQAh}lr=m_~$b%?tGaheeQnbWsA#FqeZju2xYF{=)74<IfQ0us9Z z)gitNh%1DEtZP7Zh<gEXoe&x346F`uA0UcsMC^l9usXz708vbcPnk2gI>f&LqAek= zKn__Qq7EQB5TZSIN~=SB6%d^X(Vsg*a}mRAZ<yWeYk=rNh+*6ro{Q*WdtK~C&IsEZ zVK@6a*4CYn<G3@jI$w1G(UlO>xHGCc#QlKiMu>S_`}IcK`AckfjIHK#XRPgwCCzfu zEa1*K+Z#ukwWL|do$<Ceo;0iM23tXwiSeRx)tBpB_B`dX^K3nRAB5Q(h1om7_9ocQ zL_2?*otS7dcaj~bc4*b)kZPw^O$n)XY1Pz_YL8Y;3#s;L)%1{RzgEo%sSaw@%#iA^ zR?P~jj%wBHkm{IL%?YWFYt`J4>ZDf93#lr#YJNynrBw?;s?%DvFr+%GRf|HZ^IEky zq`Ih8OG2v4TD3H!x~f&nLaOUpwLGM1870+<kgAndtqiG(wQ5yJRiag^L#lRKwI-zM zpjB%_s!m$9E~M(BRqI2lZd$b=r0Stn8$+sITD2*p>Z4VgL#lpSl?tf_XjM9-8l+X3 zkZOolm4#G8wQ5U9HC(I8L#mNlwKb#~tyLAav(48gw%PO?%?wMYvty(9MdL=P#C98R zMbEVKm-azD!@u@D1HTfpLtD)XtY(qbPO_RESOr(i!D^SbniE*fC9B<JH8-%D=UdG~ zt3BFkUSPF=toD-C{J?6VZ?zDu_GzmHfz@KN+D}#s1FI#z)e^Kipsf}KR?EohAXzOA ztd{#$%hBqPwptQctt6|%WVJM~TIE}<LaQU%YFS{lhOCZ~)$+h<t#7p!tv=RPD*~(a zWOa<JRt8oZd@D>k6Q5|SRe{wevN}#ys{^aezSU;5I-#xB1XgLXI!RV*1FMX0l|idd zwbi=7Y71FalGXaas@%82?@lF@w%QO_RghH`S#1ogw)s}u(CU=7+7wvrAgj}4wK=fb z>09kYt25dv6<F;itFvU44y^Y0R(sIuoVLmYR{O~6JXw_m>iwj?K<X`l`T(gflDgbt zF-tZ^wa&E1M=8^u5M}r=G0O2{QdHo_<fw%oQ=$?-rbcZ@#*g^#px$cXm6E!UeSxd` zEh2jV9TD|LM4Nr1PI7n@_4umB3JW*MU*x>3+8G)3Mn)TNv)oZpEjy#5`O{;G(NX4( z38`jk)!3*r&etZ!MX5EmS@<P_59uaMV|tzKQ&qELK$r{n$nyL-G5mdm@zGL{@ZGad zr@aYLyy~3+#*d1hq`iqzx}%?856jP_y-9xgSu8)B_9pw~m#_@%eu`hNiRC7EqvIf6 z0+RP)y)WYVt&K%qq*oHY2xV%eL)lgWOtzKgMZI~^W_fjzdnD?yl@41G2H6Bim(YZN z2~0M|)VG~Y(Ynp*B{xQ~^Z*>boCS8026zd9djR-Aa(5I<2<$9_8!lZWzGCr#s)2Du zw?6NR+^z0$_C~$EQD<M&+ZU}jrdDEql-E~Z#_RK4rG8Zjz`vs5UxC5MzM|^n*o^gU zz6v#TBa?hIsjE%u;$<0DSA9LlW}I*H4Xmeo5Y_|Q=mGVu9Qb%2ULWB-0(icL=d17J zz$f_dh6wK&z#r1^ht$J4@QFVB5rp>&;Pe8&7a~7UkLAGU`tTniymtV9Lc^a>jdS4h zeE3rc?-RhE(eP(fA_qR-hc`iZKN-3<2{ylgChjlISOUeJW-OhIdMBgJzF8-EB8vJb zewAM0J?{Qi&b7%@<AKtxjHVh7lCFY}!P2dYI;W!Esi<>0>YYZxI}>%!_{zpZq<hxL zrP4iT<e}0%Z{%Upy<p_w(!FTp5z@V6<dM?7Y~)eWy<+6i(!FZrG19$e<gwDdZsc*& zEsXi{c<Ht@@&xG?8F`{~TN!zhbXyyFvUH1$JVm-~j67AkB}Sem-L^)aF5PxUo*~`# zMxH6%4o03O-Ht|{E!|E=o+I7PMxHC(E=Haw-L6KSFWqiNULf7>MqVi09!6dy-JV8X zEZts4ULxJzMqVo2K1N<9-M&U%F5P}cULoE7MqVl10WoKwUrY>)=`BdsfHCs#RXgMO z&|LnpxO&bR6!YNU;8^}AarINbG$fXPJg$D`mr7&#C*taPzce(Ke=@Fq?w5weUdHQ? zY00^;7IO4Vi{fpO7uB!Qb??G^jW28L1Mc9M`n8dV#MCQBE{&;XMjjeduWC7uyTfAY zH%%~1Cp`nNCw~DSy?|Jq;W2M`Y`VUq-CV;_@9NWdBa9xBi~g-a4~e<@G~F<|G#6c9 z(4{d~H^ZA@^w3=NTLwKe=IUl@5k?P-IU{1;2!r+vdRPok^mn+~Q~%V*R(xCc&pQv` z3BCIO2KvVbFn~WjfR6v1$Eo25;(9M`M~m6a85v8AjNyhq2>V0ai~TT{;lX*#85K(> zM!`5x^Kl;;92hQS&S=<i$#96o4ffGWRuzw|gzdj3TSxYgLh7M(AHLjv4bwlygI663 z#a74(GyaeBl!oj#k$oB2Z_cr&@3}w6@W~6Z--7n^D?URh@O=F3qg`aUEo*4gkG|{x zt6gYS4q24{yD!72()%mfsW^SLy(ZgFw)?gB1L%EB4BZbWBy3Vk<B`LJJVMAL8hI3v zUq;{v<MMQz9$~A=juY^>2A)9RSaLa1GTqcQBZ?qOM^^>WjiWXlWqh59(*?+y>?{G# zYG3Ej*Ld<ZI`DOgAeXeS%iwDQ`5F@-t`p+AMilaNVj>~N28h-)A6s)m;2WGL5n`Na z1RiP2F`RaUYsV>^_8h{QOyP_VTy-W$XYHyBxSB$)CKy+DMY@x#9)#<mUG+p)Q_0oD z0N96seF@lC1N$Lx8UZH-bq*rPAYJERsB=2iIXOTKBg8O`7!HUTgqRW_MiXMRMvMW( zOhQbRkR;*b_~+u0@et4XIKz*cd;;MoAihFP<YnnhVpfb^ju5h#qAjM7#Z<DGsx79G z#cZ;ePEmXrqnJgQSvrc@5JepfVnz_=d;-kZfCT`ULt)Mg5K9QLL?f00VlE+OQCNBO zBStGIo|Ob#spDD2%e;B9vW$LMe0Jb&9ii4~ck993d~!D@Kx`(&W{pSzVgVuM28b<$ z*rE~TfLKU~c>!WOA+~G84nQm-#Qb0|?54r6hj4rJVAzX;VKEJc1;I!?K){0pJg9+( z5V(YZ3qvcwQ9^!9$d5Jh7$TPvauGGyw`elryZSjhLCtoO@F#V%eM;S!p>Izt4q~Vx z&!-4^N_#$yo|lp5B?QdFvpPq>^8`Gvffo?C9GBmVcpZCb0KQD%D+Iox!B-Ky0>Rf1 zyo|v0=<EswqFV|?w-kgf5`f0vYFDlBmE6mN_HRS5HiFu}M5Gg|sQp(2hz^A4pb;Gb zv6>Jo2~i7wc_PwP<i`=z6(90Bs#8=fcfrSe@x+f43wVeoKCw#}i#?d?%`#rQ7;Szj zynVeU#*6Sf8mYQ@5h~z!OQa^hIOoU|>YIR@!&++6t5W|U5*(i5bK@xt@5T|$x<;|o zpm~907r$3RNpKrK3GOG%9r~D87IU`5ye%kr<uSKBrj?BgZFj4YTiR}gk&A41n~_`D z?sg-$w%r{@F1FpBMs8!fyNq07ySt6t)^_(8xt;CqHFA5~-Dl(uw!7cR9c}l3kvrM$ zK_hpz-9tw1V!MZp+|_oE7`dD69yM}z+x^(cJ#6=wk$c+iCr0jNyT^^(+jdVFxsUCh zG;&|t{nW_)Y`4<L{cTqnd4TO!8F`@Xo-*<v+dXaM!M1zG$U|)RtdUD?_neW3+U|KH z53}71Mjmdv7mYl^b}t!ur0rfd@+jNA5_7Kl#l%&e@D|!ZI<lW;&Jh~$2SiE(DIGZ^ z{B-6B%!98$8X!zM(=ufL3E5YY{qY?8lfqADDz&{5CY>p=v+2wQGCY$t)alGQ(NtYP zt8#Tw_~}e5J6O!FP^DMN_Nw-NO=Q!V)^?E2w6qXeWFfN1B4jJeq%+0FC8RTL2v|bE z5)Eu?Wz(59c6M#(NRW;eMc2tP=}d|71uJM*^3{!i-L$XnRyLh!8~Ex?klxx?AIqdO z?E=IALJZJ|ftE>U+6RbILX>L6P|Kt<9qjA^J%Yj+Nw|?ZoKaRbo#`038b^?E+SPc= zq%)oD?8Y>STumn2WbJB-l}%?l2f*nBoI$`D8aUI+rZZiFI_DB(uC8;QWzw0h0b&s$ z7HPy{%cL{i0>pAcEZ2w?mPu#2+d*=&+M?tHKWg$dgkOXB3bob>(wQD1iw)Xh16gb& zi;dc1lNF>hJ?(5dlOaq-M^R?^=}fO6%nAZjXuvkhq%*w(#4bYY(um!bNoV@lK{~UK z;@MBY{W_ilRydvM8@M|{s3Y3lQOl$={Q|^sLLAqK6P8J5`UeO_2&EBKmPuy@1c<YQ zII9uoER)U*3<kqR8Vr{RcS#S1%T_j>85E4vYXrPb!0Q@VD6{Fz;Lr-tN+Pngq*PuK zvRG!*nIU$N&a{=7&fwpgydB}&NouzCGDv4igBUuI=gx%etUY&;*>q;8oy}0X6R-yX zduU)!Y0{Zq_#n!#0NjVbeF@xGgZoL7&h$s{a63q61`>J@p$BR7U}@5sA&4FkwEr-I z4U^RV!=*`QMh1w{gcz+6W28xEM%h6+GePD*K=}sJ=P`CRg%~Jw3bE48W;4r$pUo^6 zYL&=%pM<iRYvF9>n$Bi6+u3ZUO!(PM8SV{Ns6|tDHk;Y$e-mb>P`iZAX3{pMF7<Wh zGE4fuqgojE3geA4w%ao96vcHhQ54s=25h_<-mH>kYDCKE#4|FbA^z@?kwCFQ*DCI{ zicjxd`##vLwn(RS+-n_oisN2!yxxguep!t~o4Ba2%Awgx;^k_q^f#jNpmi2W-8x%s z{Pma_L_L>C47Q>MWQ&z-TMf2as--gJwT<Un_%nNI85G*Zo2uoctb1Qgh-rHbwSv^8 zP1Q=$<A1MxJdX`cbcnO}%GGL87jIQ-WctCor{MJy8?T={_@zl7)V>$|t|b6|RWfVY zG0wqYz0CMkZ}6+$2$~%=N_B;iNQI4;?*EN?fP&u?kL;BA1%jPY?UK49w%K^a^`%rJ zggxkVj_XUVUE*Gsc(ZTSNzz{2TR2^F4%Dxc{0bhJE}d>U2O88#mQemYjUL9MUapgB z<aCdF-Q$gS*lv%w(=)D%iJrP=chGqKA`PH7@{iQ0I$A@$0rBsm0rpiazDnPytPy!t z{RST(d<{lfFECcG<1=%%xn>N&pPFmNedAu=c(ZTUN%o1O9`8Twp=cVRt50i09&Fw( zo?lrb*)LwnR1Nl>=KbUORW*|Rp?InWZ`gc5JpXi!<bXInHfM*v>~W@s8buGp+4$SS z^z2$zIz%<(VGUPnM7lF)XxtkbZ+zHxhsB-Yaa~Ld*Qb4$PWuiz?IA2b9YfbcaO!+) z>)-K(a+6de_)k4m9L49T?ndojK>L0Q!cC08R`>vpmPGaU{@^HmO>@X?k-w@Bp`8fv zcR;iy`+E@a5!kn6<X|KoF2dm3lZQHZTz@YNTof~WR5Gnm^pIrFD7N;mptS+_Z71dT zemd@@<IVExBnQUvzz>{Eyl%4xlY`^xai<K#?<7m<z%$O49E*l^lJlc@0B`5~e+!v! AWdHyG
--- a/browser/extensions/shumway/content/shumway-worker.js +++ b/browser/extensions/shumway/content/shumway-worker.js @@ -182,42 +182,22 @@ function scriptProperties(namespace, pro }, {}); } function cloneObject(obj) { var clone = Object.create(null); for (var prop in obj) clone[prop] = obj[prop]; return clone; } -function sortByDepth(a, b) { - var levelA = a._level; - var levelB = b._level; - if (a._parent !== b._parent && a._index > -1 && b._index > -1) { - while (a._level > levelB) { - a = a._parent; - } - while (b._level > levelA) { - b = b._parent; - } - while (a._level > 1) { - if (a._parent === b._parent) { - break; - } - a = a._parent; - b = b._parent; - } - } - if (a === b) { - return levelA - levelB; - } - return a._index - b._index; -} function sortNumeric(a, b) { return a - b; } +function sortByZindex(a, b) { + return a._zindex - b._zindex; +} function rgbaObjToStr(color) { return 'rgba(' + color.red + ',' + color.green + ',' + color.blue + ',' + color.alpha / 255 + ')'; } function rgbIntAlphaToStr(color, alpha) { color |= 0; if (alpha >= 1) { var colorStr = color.toString(16); while (colorStr.length < 6) { @@ -291,168 +271,346 @@ function randomStyle() { '#ff1300', '#1f1f21', '#bdbec2', '#ff3a2d' ]; } return randomStyleCache[nextStyle++ % randomStyleCache.length]; } -var Promise = function PromiseClosure() { - function isPromise(obj) { - return typeof obj === 'object' && obj !== null && typeof obj.then === 'function'; - } - function defaultOnFulfilled(value) { - return value; - } - function defaultOnRejected(reason) { - throw reason; - } - function propagateFulfilled(subject, value) { - subject.subpromisesValue = value; - var subpromises = subject.subpromises; - if (!subpromises) { - return; - } - for (var i = 0; i < subpromises.length; i++) { - subpromises[i].fulfill(value); - } - delete subject.subpromises; - } - function propagateRejected(subject, reason) { - subject.subpromisesReason = reason; - var subpromises = subject.subpromises; - if (!subpromises) { - if (!true) { - console.warn(reason); - } - return; - } - for (var i = 0; i < subpromises.length; i++) { - subpromises[i].reject(reason); - } - delete subject.subpromises; - } - function performCall(callback, arg, subject) { - try { - var value = callback(arg); - if (isPromise(value)) { - value.then(function Promise_queueCall_onFulfilled(value) { - propagateFulfilled(subject, value); - }, function Promise_queueCall_onRejected(reason) { - propagateRejected(subject, reason); +(function PromiseClosure() { + var global = Function('return this')(); + if (global.Promise) { + if (typeof global.Promise.all !== 'function') { + global.Promise.all = function (iterable) { + var count = 0, results = [], resolve, reject; + var promise = new global.Promise(function (resolve_, reject_) { + resolve = resolve_; + reject = reject_; }); - return; - } - propagateFulfilled(subject, value); - } catch (ex) { - propagateRejected(subject, ex); - } - } - var queue = []; - function processQueue() { - while (queue.length > 0) { - var task = queue[0]; - if (task.directCallback) { - task.callback.call(task.subject, task.arg); - } else { - performCall(task.callback, task.arg, task.subject); - } - queue.shift(); - } - } - function queueCall(callback, arg, subject, directCallback) { - if (queue.length === 0) { - setTimeout(processQueue, 0); - } - queue.push({ - callback: callback, - arg: arg, - subject: subject, - directCallback: directCallback - }); - } - function Promise(onFulfilled, onRejected) { - this.state = 'pending'; - this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : defaultOnFulfilled; - this.onRejected = typeof onRejected === 'function' ? onRejected : defaultOnRejected; - } - Promise.prototype = { - fulfill: function Promise_resolve(value) { - if (this.state !== 'pending') { - return; - } - this.state = 'fulfilled'; - this.value = value; - queueCall(this.onFulfilled, value, this, false); - }, - reject: function Promise_reject(reason) { - if (this.state !== 'pending') { - return; - } - this.state = 'rejected'; - this.reason = reason; - queueCall(this.onRejected, reason, this, false); - }, - then: function Promise_then(onFulfilled, onRejected) { - var promise = new Promise(onFulfilled, onRejected); - if ('subpromisesValue' in this) { - queueCall(promise.fulfill, this.subpromisesValue, promise, true); - } else if ('subpromisesReason' in this) { - queueCall(promise.reject, this.subpromisesReason, promise, true); - } else { - var subpromises = this.subpromises || (this.subpromises = []); - subpromises.push(promise); + iterable.forEach(function (p, i) { + count++; + p.then(function (result) { + results[i] = result; + count--; + if (count === 0) { + resolve(results); + } + }, reject); + }); + if (count === 0) { + resolve(results); } return promise; - }, - get resolved() { - return this.state === 'fulfilled'; - }, - resolve: function (value) { - this.fulfill(value); + }; + } + if (typeof global.Promise.resolve !== 'function') { + global.Promise.resolve = function (x) { + return new global.Promise(function (resolve) { + resolve(x); + }); + }; + } + return; + } + function getDeferred(C) { + if (typeof C !== 'function') { + throw new TypeError('Invalid deferred constructor'); + } + var resolver = createDeferredConstructionFunctions(); + var promise = new C(resolver); + var resolve = resolver.resolve; + if (typeof resolve !== 'function') { + throw new TypeError('Invalid resolve construction function'); + } + var reject = resolver.reject; + if (typeof reject !== 'function') { + throw new TypeError('Invalid reject construction function'); + } + return { + promise: promise, + resolve: resolve, + reject: reject + }; + } + function updateDeferredFromPotentialThenable(x, deferred) { + if (typeof x !== 'object' || x === null) { + return false; + } + try { + var then = x.then; + if (typeof then !== 'function') { + return false; + } + var thenCallResult = then.call(x, deferred.resolve, deferred.reject); + } catch (e) { + var reject = deferred.reject; + reject(e); + } + return true; + } + function isPromise(x) { + return typeof x === 'object' && x !== null && typeof x.promiseStatus !== 'undefined'; + } + function rejectPromise(promise, reason) { + if (promise.promiseStatus !== 'unresolved') { + return; + } + var reactions = promise.rejectReactions; + promise.result = reason; + promise.resolveReactions = undefined; + promise.rejectReactions = undefined; + promise.promiseStatus = 'has-rejection'; + triggerPromiseReactions(reactions, reason); + } + function resolvePromise(promise, resolution) { + if (promise.promiseStatus !== 'unresolved') { + return; + } + var reactions = promise.resolveReactions; + promise.result = resolution; + promise.resolveReactions = undefined; + promise.rejectReactions = undefined; + promise.promiseStatus = 'has-resolution'; + triggerPromiseReactions(reactions, resolution); + } + function triggerPromiseReactions(reactions, argument) { + for (var i = 0; i < reactions.length; i++) { + queueMicrotask({ + reaction: reactions[i], + argument: argument + }); + } + } + function queueMicrotask(task) { + if (microtasksQueue.length === 0) { + setTimeout(handleMicrotasksQueue, 0); + } + microtasksQueue.push(task); + } + function executePromiseReaction(reaction, argument) { + var deferred = reaction.deferred; + var handler = reaction.handler; + var handlerResult, updateResult; + try { + handlerResult = handler(argument); + } catch (e) { + var reject = deferred.reject; + return reject(e); + } + if (handlerResult === deferred.promise) { + var reject = deferred.reject; + return reject(new TypeError('Self resolution')); + } + try { + updateResult = updateDeferredFromPotentialThenable(handlerResult, deferred); + if (!updateResult) { + var resolve = deferred.resolve; + return resolve(handlerResult); + } + } catch (e) { + var reject = deferred.reject; + return reject(e); + } + } + var microtasksQueue = []; + function handleMicrotasksQueue() { + while (microtasksQueue.length > 0) { + var task = microtasksQueue[0]; + try { + executePromiseReaction(task.reaction, task.argument); + } catch (e) { + if (typeof Promise.onerror === 'function') { + Promise.onerror(e); + } + } + microtasksQueue.shift(); + } + } + function throwerFunction(e) { + throw e; + } + function identityFunction(x) { + return x; + } + function createRejectPromiseFunction(promise) { + return function (reason) { + rejectPromise(promise, reason); + }; + } + function createResolvePromiseFunction(promise) { + return function (resolution) { + resolvePromise(promise, resolution); + }; + } + function createDeferredConstructionFunctions() { + var fn = function (resolve, reject) { + fn.resolve = resolve; + fn.reject = reject; + }; + return fn; + } + function createPromiseResolutionHandlerFunctions(promise, fulfillmentHandler, rejectionHandler) { + return function (x) { + if (x === promise) { + return rejectionHandler(new TypeError('Self resolution')); + } + var cstr = promise.promiseConstructor; + if (isPromise(x)) { + var xConstructor = x.promiseConstructor; + if (xConstructor === cstr) { + return x.then(fulfillmentHandler, rejectionHandler); + } + } + var deferred = getDeferred(cstr); + var updateResult = updateDeferredFromPotentialThenable(x, deferred); + if (updateResult) { + var deferredPromise = deferred.promise; + return deferredPromise.then(fulfillmentHandler, rejectionHandler); + } + return fulfillmentHandler(x); + }; + } + function createPromiseAllCountdownFunction(index, values, deferred, countdownHolder) { + return function (x) { + values[index] = x; + countdownHolder.countdown--; + if (countdownHolder.countdown === 0) { + deferred.resolve(values); } }; - Promise.when = function Promise_when() { - var promise = new Promise(); - if (arguments.length === 0) { - promise.resolve(); - return promise; - } - var promises = slice.call(arguments, 0); - var result = []; - var i = 1; - function fulfill(value) { - result.push(value); - if (i < promises.length) { - promises[i++].then(fulfill, reject); - } else { - promise.resolve(result); - } - return value; - } - function reject(reason) { - promise.reject(reason); - } - promises[0].then(fulfill, reject); - return promise; - }; - return Promise; - }(); -var QuadTree = function (x, y, width, height, level) { + } + function Promise(resolver) { + if (typeof resolver !== 'function') { + throw new TypeError('resolver is not a function'); + } + var promise = this; + if (typeof promise !== 'object') { + throw new TypeError('Promise to initialize is not an object'); + } + promise.promiseStatus = 'unresolved'; + promise.resolveReactions = []; + promise.rejectReactions = []; + promise.result = undefined; + var resolve = createResolvePromiseFunction(promise); + var reject = createRejectPromiseFunction(promise); + try { + var result = resolver(resolve, reject); + } catch (e) { + rejectPromise(promise, e); + } + promise.promiseConstructor = Promise; + return promise; + } + Promise.all = function (iterable) { + var deferred = getDeferred(this); + var values = []; + var countdownHolder = { + countdown: 0 + }; + var index = 0; + iterable.forEach(function (nextValue) { + var nextPromise = this.cast(nextValue); + var fn = createPromiseAllCountdownFunction(index, values, deferred, countdownHolder); + nextPromise.then(fn, deferred.reject); + index++; + countdownHolder.countdown++; + }, this); + if (index === 0) { + deferred.resolve(values); + } + return deferred.promise; + }; + Promise.cast = function (x) { + if (isPromise(x)) { + return x; + } + var deferred = getDeferred(this); + deferred.resolve(x); + return deferred.promise; + }; + Promise.reject = function (r) { + var deferred = getDeferred(this); + var rejectResult = deferred.reject(r); + return deferred.promise; + }; + Promise.resolve = function (x) { + var deferred = getDeferred(this); + var rejectResult = deferred.resolve(x); + return deferred.promise; + }; + Promise.prototype = { + 'catch': function (onRejected) { + this.then(undefined, onRejected); + }, + then: function (onFulfilled, onRejected) { + var promise = this; + if (!isPromise(promise)) { + throw new TypeError('this is not a Promises'); + } + var cstr = promise.promiseConstructor; + var deferred = getDeferred(cstr); + var rejectionHandler = typeof onRejected === 'function' ? onRejected : throwerFunction; + var fulfillmentHandler = typeof onFulfilled === 'function' ? onFulfilled : identityFunction; + var resolutionHandler = createPromiseResolutionHandlerFunctions(promise, fulfillmentHandler, rejectionHandler); + var resolveReaction = { + deferred: deferred, + handler: resolutionHandler + }; + var rejectReaction = { + deferred: deferred, + handler: rejectionHandler + }; + switch (promise.promiseStatus) { + case 'unresolved': + promise.resolveReactions.push(resolveReaction); + promise.rejectReactions.push(rejectReaction); + break; + case 'has-resolution': + var resolution = promise.result; + queueMicrotask({ + reaction: resolveReaction, + argument: resolution + }); + break; + case 'has-rejection': + var rejection = promise.result; + queueMicrotask({ + reaction: rejectReaction, + argument: rejection + }); + break; + } + return deferred.promise; + } + }; + global.Promise = Promise; +}()); +var QuadTree = function (x, y, width, height, parent) { this.x = x | 0; this.y = y | 0; this.width = width | 0; this.height = height | 0; - this.level = level | 0; - this.stuckObjects = []; - this.objects = []; + if (parent) { + this.root = parent.root; + this.parent = parent; + this.level = parent.level + 1; + } else { + this.root = this; + this.parent = null; + this.level = 0; + } + this.reset(); +}; +QuadTree.prototype.reset = function () { + this.stuckObjects = null; + this.objects = null; this.nodes = []; }; -QuadTree.prototype._findIndex = function (xMin, yMin, xMax, yMax) { +QuadTree.prototype._findIndex = function (xMin, xMax, yMin, yMax) { var midX = this.x + (this.width / 2 | 0); var midY = this.y + (this.height / 2 | 0); var top = yMin < midY && yMax < midY; var bottom = yMin > midY; if (xMin < midX && xMax < midX) { if (top) { return 1; } else if (bottom) { @@ -465,81 +623,218 @@ QuadTree.prototype._findIndex = function return 3; } } return -1; }; QuadTree.prototype.insert = function (obj) { var nodes = this.nodes; if (nodes.length) { - var index = this._findIndex(obj.xMin, obj.yMin, obj.xMax, obj.yMax); + var index = this._findIndex(obj.xMin, obj.xMax, obj.yMin, obj.yMax); if (index > -1) { nodes[index].insert(obj); } else { - this.stuckObjects.push(obj); - obj._qtree = this; - } - return; - } - var objects = this.objects; - objects.push(obj); - if (objects.length > 4 && this.level < 10) { - this._subdivide(); - while (objects.length) { - this.insert(objects.shift()); + obj.prev = null; + if (this.stuckObjects) { + obj.next = this.stuckObjects; + this.stuckObjects.prev = obj; + } else { + obj.next = null; + } + this.stuckObjects = obj; + obj.parent = this; } return; } - obj._qtree = this; -}; -QuadTree.prototype.delete = function (obj) { - if (obj._qtree !== this) { + var numChildren = 1; + var item = this.objects; + if (!item) { + obj.prev = null; + obj.next = null; + this.objects = obj; + } else { + while (item.next) { + numChildren++; + item = item.next; + } + obj.prev = item; + obj.next = null; + item.next = obj; + } + if (numChildren > 4 && this.level < 10) { + this._subdivide(); + item = this.objects; + while (item) { + var next = item.next; + this.insert(item); + item = next; + } + this.objects = null; return; } - var index = this.objects.indexOf(obj); - if (index > -1) { - this.objects.splice(index, 1); - } else { - index = this.stuckObjects.indexOf(obj); - this.stuckObjects.splice(index, 1); + obj.parent = this; +}; +QuadTree.prototype.update = function (obj) { + var node = obj.parent; + if (node) { + if (obj.xMin >= node.x && obj.xMax <= node.x + node.width && obj.yMin >= node.y && obj.yMax <= node.y + node.height) { + if (node.nodes.length) { + var index = this._findIndex(obj.xMin, obj.xMax, obj.yMin, obj.yMax); + if (index > -1) { + node.remove(obj); + node = this.nodes[index]; + node.insert(obj); + } + } else { + node.remove(obj); + node.insert(obj); + } + return; + } + node.remove(obj); } - obj._qtree = null; + this.root.insert(obj); }; -QuadTree.prototype._stack = []; -QuadTree.prototype._out = []; -QuadTree.prototype.retrieve = function (xMin, yMin, xMax, yMax) { - var stack = this._stack; - var out = this._out; - out.length = 0; +QuadTree.prototype.remove = function (obj) { + var prev = obj.prev; + var next = obj.next; + if (prev) { + prev.next = next; + obj.prev = null; + } else { + var node = obj.parent; + if (node.objects === obj) { + node.objects = next; + } else if (node.stuckObjects === obj) { + node.stuckObjects = next; + } + } + if (next) { + next.prev = prev; + obj.next = null; + } + obj.parent = null; +}; +QuadTree.prototype.retrieve = function (xMin, xMax, yMin, yMax) { + var stack = []; + var out = []; var node = this; do { if (node.nodes.length) { - var index = node._findIndex(xMin, yMin, xMax, yMax); + var index = node._findIndex(xMin, xMax, yMin, yMax); if (index > -1) { stack.push(node.nodes[index]); } else { stack.push.apply(stack, node.nodes); } } - out.push.apply(out, node.stuckObjects); - out.push.apply(out, node.objects); + var item = node.objects; + for (var i = 0; i < 2; i++) { + while (item) { + if (!(item.xMin > xMax || item.xMax < xMin || item.yMin > yMax || item.yMax < yMin)) { + out.push(item); + } + item = item.next; + } + item = node.stuckObjects; + } node = stack.pop(); } while (node); return out; }; QuadTree.prototype._subdivide = function () { var halfWidth = this.width / 2 | 0; var halfHeight = this.height / 2 | 0; var midX = this.x + halfWidth; var midY = this.y + halfHeight; - var level = this.level + 1; - this.nodes[0] = new QuadTree(midX, this.y, halfWidth, halfHeight, level); - this.nodes[1] = new QuadTree(this.x, this.y, halfWidth, halfHeight, level); - this.nodes[2] = new QuadTree(this.x, midY, halfWidth, halfHeight, level); - this.nodes[3] = new QuadTree(midX, midY, halfWidth, halfHeight, level); + this.nodes[0] = new QuadTree(midX, this.y, halfWidth, halfHeight, this); + this.nodes[1] = new QuadTree(this.x, this.y, halfWidth, halfHeight, this); + this.nodes[2] = new QuadTree(this.x, midY, halfWidth, halfHeight, this); + this.nodes[3] = new QuadTree(midX, midY, halfWidth, halfHeight, this); +}; +var RegionCluster = function () { + this.regions = []; +}; +RegionCluster.prototype.reset = function () { + this.regions.length = 0; +}; +RegionCluster.prototype.insert = function (region) { + var regions = this.regions; + if (regions.length < 3) { + regions.push({ + xMin: region.xMin, + xMax: region.xMax, + yMin: region.yMin, + yMax: region.yMax + }); + return; + } + var a = region; + var b = regions[0]; + var c = regions[1]; + var d = regions[2]; + var ab = (max(a.xMax, b.xMax) - min(a.xMin, b.xMin)) * (max(a.yMax, b.yMax) - min(a.yMin, b.yMin)); + var rb = regions[0]; + var ac = (max(a.xMax, c.xMax) - min(a.xMin, c.xMin)) * (max(a.yMax, c.yMax) - min(a.yMin, c.yMin)); + var ad = (max(a.xMax, d.xMax) - min(a.xMin, d.xMin)) * (max(a.yMax, d.yMax) - min(a.yMin, d.yMin)); + if (ac < ab) { + ab = ac; + rb = c; + } + if (ad < ab) { + ab = ad; + rb = d; + } + var bc = (max(b.xMax, c.xMax) - min(b.xMin, c.xMin)) * (max(b.yMax, c.yMax) - min(b.yMin, c.yMin)); + var bd = (max(b.xMax, d.xMax) - min(b.xMin, d.xMin)) * (max(b.yMax, d.yMax) - min(b.yMin, d.yMin)); + var cd = (max(c.xMax, d.xMax) - min(c.xMin, d.xMin)) * (max(c.yMax, d.yMax) - min(c.yMin, d.yMin)); + if (ab < bc && ab < bd && ab < cd) { + if (a.xMin < rb.xMin) { + rb.xMin = a.xMin; + } + if (a.xMax > rb.xMax) { + rb.xMax = a.xMax; + } + if (a.yMin < rb.yMin) { + rb.yMin = a.yMin; + } + if (a.yMax > rb.yMax) { + rb.yMax = a.yMax; + } + return; + } + rb = regions[0]; + var rc = regions[1]; + if (bd < bc) { + bc = bd; + rc = regions[2]; + } + if (cd < bc) { + rb = regions[1]; + rc = regions[2]; + } + if (rc.xMin < rb.xMin) { + rb.xMin = rc.xMin; + } + if (rc.xMax > rb.xMax) { + rb.xMax = rc.xMax; + } + if (rc.yMin < rb.yMin) { + rb.yMin = rc.yMin; + } + if (rc.yMax > rb.yMax) { + rb.yMax = rc.yMax; + } + rc.xMin = a.xMin; + rc.xMax = a.xMax; + rc.yMin = a.yMin; + rc.yMax = a.yMax; +}; +RegionCluster.prototype.retrieve = function () { + return this.regions; }; var EXTERNAL_INTERFACE_FEATURE = 1; var CLIPBOARD_FEATURE = 2; var SHAREDOBJECT_FEATURE = 3; var VIDEO_FEATURE = 4; var SOUND_FEATURE = 5; var NETCONNECTION_FEATURE = 6; if (!this.performance) { @@ -1847,16 +2142,19 @@ function ShapePath(fillStyle, lineStyle, this.buffers.push(this.morphData.buffer); transferables.push(this.morphData.buffer); } } else { this.buffers = null; } } ShapePath.prototype = { + get isEmpty() { + return this.commands.length === 0; + }, moveTo: function (x, y) { if (this.commands[this.commands.length - 1] === SHAPE_MOVE_TO) { this.data[this.data.length - 2] = x; this.data[this.data.length - 1] = y; return; } this.commands.push(SHAPE_MOVE_TO); this.data.push(x, y); @@ -2008,16 +2306,18 @@ ShapePath.prototype = { colorTransform.setAlpha(ctx); ctx.lineWidth = Math.max(lineStyle.width / 20, 1); ctx.lineCap = lineStyle.lineCap; ctx.lineJoin = lineStyle.lineJoin; ctx.miterLimit = lineStyle.miterLimit; ctx.stroke(); ctx.restore(); } + } else { + ctx.fill(); } ctx.closePath(); }, isPointInPath: function (x, y) { if (!(this.fillStyle || this.lineStyle)) { return false; } var bounds = this.strokeBounds || this.bounds || this._calculateBounds(); @@ -2689,32 +2989,32 @@ function extendBoundsByY(bounds, y) { bounds.yMin = y; } else if (y > bounds.yMax) { bounds.yMax = y; } } function morph(start, end, ratio) { return start + (end - start) * ratio; } -function finishShapePath(path, dictionary) { +function finishShapePath(path, dictionaryResolved) { if (path.fullyInitialized) { return path; } if (!(path instanceof ShapePath)) { var untypedPath = path; path = new ShapePath(path.fillStyle, path.lineStyle, 0, 0, path.isMorph); path.commands = new Uint8Array(untypedPath.buffers[0]); path.data = new Int32Array(untypedPath.buffers[1]); if (untypedPath.isMorph) { path.morphData = new Int32Array(untypedPath.buffers[2]); } path.buffers = null; } - path.fillStyle && initStyle(path.fillStyle, dictionary); - path.lineStyle && initStyle(path.lineStyle, dictionary); + path.fillStyle && initStyle(path.fillStyle, dictionaryResolved); + path.lineStyle && initStyle(path.lineStyle, dictionaryResolved); path.fullyInitialized = true; return path; } var inWorker = typeof window === 'undefined'; var factoryCtx = !inWorker ? document.createElement('canvas').getContext('2d') : null; function buildLinearGradientFactory(colorStops) { var defaultGradient = factoryCtx.createLinearGradient(-1, 0, 1, 0); for (var i = 0; i < colorStops.length; i++) { @@ -2764,17 +3064,17 @@ function buildBitmapPatternFactory(img, ctx.drawImage(img, 0, 0); cachedTransform = ctx.createPattern(canvas, repeat); cachedTransformKey = key; return cachedTransform; }; fn.defaultFillStyle = defaultPattern; return fn; } -function initStyle(style, dictionary) { +function initStyle(style, dictionaryResolved) { if (style.type === undefined) { return; } switch (style.type) { case GRAPHICS_FILL_SOLID: break; case GRAPHICS_FILL_LINEAR_GRADIENT: case GRAPHICS_FILL_RADIAL_GRADIENT: @@ -2796,19 +3096,19 @@ function initStyle(style, dictionary) { gradientConstructor = buildRadialGradientFactory((style.focalPoint | 0) / 20, colorStops); } style.style = gradientConstructor; break; case GRAPHICS_FILL_REPEATING_BITMAP: case GRAPHICS_FILL_CLIPPED_BITMAP: case GRAPHICS_FILL_NONSMOOTHED_REPEATING_BITMAP: case GRAPHICS_FILL_NONSMOOTHED_CLIPPED_BITMAP: - var bitmap = dictionary[style.bitmapId]; + var bitmap = dictionaryResolved[style.bitmapId]; var repeat = style.type === GRAPHICS_FILL_REPEATING_BITMAP || style.type === GRAPHICS_FILL_NONSMOOTHED_REPEATING_BITMAP; - style.style = buildBitmapPatternFactory(bitmap.value.props.img, repeat ? 'repeat' : 'no-repeat'); + style.style = buildBitmapPatternFactory(bitmap.props.img, repeat ? 'repeat' : 'no-repeat'); break; default: fail('invalid fill style', 'shape'); } } var SOUND_SIZE_8_BIT = 0; var SOUND_SIZE_16_BIT = 1; var SOUND_TYPE_MONO = 0; @@ -5741,17 +6041,17 @@ BodyParser.prototype = { finalBlock = progressInfo.bytesLoaded >= progressInfo.bytesTotal; } var readStartTime = performance.now(); readTags(swf, stream, swfVersion, finalBlock, options.onprogress); swf.parseTime += performance.now() - readStartTime; var read = stream.pos; buffer.removeHead(read); this.totalRead += read; - if (this.totalRead >= this.length && options.oncomplete) { + if (options.oncomplete && swf.tags[swf.tags.length - 1].finalTag) { options.oncomplete(swf); } } }; SWF.parseAsync = function swf_parseAsync(options) { var buffer = new HeadTailBuffer(); var pipe = { push: function (data, progressInfo) { @@ -5848,13 +6148,14 @@ SWF.parse = function (buffer, options) { options = {}; var pipe = SWF.parseAsync(options); var bytes = new Uint8Array(buffer); var progressInfo = { bytesLoaded: bytes.length, bytesTotal: bytes.length }; pipe.push(bytes, progressInfo); + pipe.close(); }; (function (global) { global['SWF']['parse'] = SWF.parse; global['SWF']['parseAsync'] = SWF.parseAsync; }(this));
--- a/browser/extensions/shumway/content/shumway.js +++ b/browser/extensions/shumway/content/shumway.js @@ -151,16 +151,463 @@ for (var i = 0; i < size; i++) view._bytes[offset + i] = temp[size - 1 - i]; } } function fail(msg) { throw new Error(msg); } }(this)); +; +var ByteArray = ByteArray || function (undefined) { + ByteArrayClass.INITIAL_SIZE = 128; + ByteArrayClass.DEFAULT_OBJECT_ENCODING = 3; + function ByteArrayClass(bytes) { + if (bytes instanceof ByteArray) { + return bytes; + } + var initData = bytes || this.symbol && this.symbol.data; + if (initData) { + this.a = new ArrayBuffer(initData.length); + this.length = initData.length; + new Uint8Array(this.a).set(initData); + } else { + this.a = new ArrayBuffer(ByteArrayClass.INITIAL_SIZE); + this.length = 0; + } + this.position = 0; + this.cacheViews(); + this.nativele = new Int8Array(new Int32Array([]).buffer)[0] === 1; + this.le = this.nativele; + this.objectEncoding = ByteArrayClass.DEFAULT_OBJECT_ENCODING; + this.bitBuffer = 0; + this.bitLength = 0; + } + ; + function throwEOFError() { + runtime.throwErrorFromVM('flash.errors.EOFError', 'End of file was encountered.'); + } + function throwRangeError() { + var error = Errors.ParamRangeError; + runtime.throwErrorFromVM('RangeError', getErrorMessage(error.code), error.code); + } + function throwCompressedDataError() { + var error = Errors.CompressedDataError; + runtime.throwErrorFromVM('CompressedDataError', getErrorMessage(error.code), error.code); + } + function checkRange(x, min, max) { + if (x !== clamp(x, min, max)) { + throwRangeError(); + } + } + function get(b, m, size) { + if (b.position + size > b.length) { + throwEOFError(); + } + var v = b.view[m](b.position, b.le); + b.position += size; + return v; + } + function set(b, m, size, v) { + var len = b.position + size; + b.ensureCapacity(len); + b.view[m](b.position, v, b.le); + b.position = len; + if (len > b.length) { + b.length = len; + } + } + var BAp = ByteArrayClass.prototype; + BAp.cacheViews = function cacheViews() { + var a = this.a; + this.int8v = new Int8Array(a); + this.uint8v = new Uint8Array(a); + this.view = new DataView(a); + }; + BAp.getBytes = function getBytes() { + return new Uint8Array(this.a, 0, this.length); + }; + BAp.ensureCapacity = function ensureCapacity(size) { + var origa = this.a; + if (origa.byteLength < size) { + var newSize = origa.byteLength; + while (newSize < size) { + newSize *= 2; + } + var copya = new ArrayBuffer(newSize); + var origv = this.int8v; + this.a = copya; + this.cacheViews(); + this.int8v.set(origv); + } + }; + BAp.clear = function clear() { + this.length = 0; + this.position = 0; + }; + BAp.readBoolean = function readBoolean() { + if (this.position + 1 > this.length) { + throwEOFError(); + } + return this.int8v[this.position++] !== 0; + }; + BAp.readByte = function readByte() { + if (this.position + 1 > this.length) { + throwEOFError(); + } + return this.int8v[this.position++]; + }; + BAp.readUnsignedByte = function readUnsignedByte() { + if (this.position + 1 > this.length) { + throwEOFError(); + } + return this.uint8v[this.position++]; + }; + BAp.readBytes = function readBytes(bytes, offset, length) { + var pos = this.position; + if (!offset) { + offset = 0; + } + if (!length) { + length = this.length - pos; + } + if (pos + length > this.length) { + throwEOFError(); + } + if (bytes.length < offset + length) { + bytes.ensureCapacity(offset + length); + bytes.length = offset + length; + } + bytes.int8v.set(new Int8Array(this.a, pos, length), offset); + this.position += length; + }; + BAp.writeBoolean = function writeBoolean(v) { + var len = this.position + 1; + this.ensureCapacity(len); + this.int8v[this.position++] = v ? 1 : 0; + if (len > this.length) { + this.length = len; + } + }; + BAp.writeByte = function writeByte(v) { + var len = this.position + 1; + this.ensureCapacity(len); + this.int8v[this.position++] = v; + if (len > this.length) { + this.length = len; + } + }; + BAp.writeUnsignedByte = function writeUnsignedByte(v) { + var len = this.position + 1; + this.ensureCapacity(len); + this.uint8v[this.position++] = v; + if (len > this.length) { + this.length = len; + } + }; + BAp.writeRawBytes = function writeRawBytes(bytes) { + var len = this.position + bytes.length; + this.ensureCapacity(len); + this.int8v.set(bytes, this.position); + this.position = len; + if (len > this.length) { + this.length = len; + } + }; + BAp.readRawBytes = function readRawBytes() { + return new Int8Array(this.a, 0, this.length); + }; + BAp.writeBytes = function writeBytes(bytes, offset, length) { + if (arguments.length < 2) { + offset = 0; + } + if (arguments.length < 3) { + length = 0; + } + checkRange(offset, 0, bytes.length); + checkRange(offset + length, 0, bytes.length); + if (length === 0) { + length = bytes.length - offset; + } + this.writeRawBytes(new Int8Array(bytes.a, offset, length)); + }; + BAp.readDouble = function readDouble() { + return get(this, 'getFloat64', 8); + }; + BAp.readFloat = function readFloat() { + return get(this, 'getFloat32', 4); + }; + BAp.readInt = function readInt() { + return get(this, 'getInt32', 4); + }; + BAp.readShort = function readShort() { + return get(this, 'getInt16', 2); + }; + BAp.readUnsignedInt = function readUnsignedInt() { + return get(this, 'getUint32', 4); + }; + BAp.readUnsignedShort = function readUnsignedShort() { + return get(this, 'getUint16', 2); + }; + BAp.writeDouble = function writeDouble(v) { + set(this, 'setFloat64', 8, v); + }; + BAp.writeFloat = function writeFloat(v) { + set(this, 'setFloat32', 4, v); + }; + BAp.writeInt = function writeInt(v) { + set(this, 'setInt32', 4, v); + }; + BAp.writeShort = function writeShort(v) { + set(this, 'setInt16', 2, v); + }; + BAp.writeUnsignedInt = function writeUnsignedInt(v) { + set(this, 'setUint32', 4, v); + }; + BAp.writeUnsignedShort = function writeUnsignedShort(v) { + set(this, 'setUint16', 2, v); + }; + var codeLengthOrder = [ + 16, + 17, + 18, + 0, + 8, + 7, + 9, + 6, + 10, + 5, + 11, + 4, + 12, + 3, + 13, + 2, + 14, + 1, + 15 + ]; + var distanceCodes = []; + var distanceExtraBits = []; + for (var i = 0, j = 0, code = 1; i < 30; ++i) { + distanceCodes[i] = code; + code += 1 << (distanceExtraBits[i] = ~(~((j += i > 2 ? 1 : 0) / 2))); + } + var bitLengths = []; + for (var i = 0; i < 32; ++i) { + bitLengths[i] = 5; + } + var fixedDistanceTable = makeHuffmanTable(bitLengths); + var lengthCodes = []; + var lengthExtraBits = []; + for (var i = 0, j = 0, code = 3; i < 29; ++i) { + lengthCodes[i] = code - (i == 28 ? 1 : 0); + code += 1 << (lengthExtraBits[i] = ~(~((j += i > 4 ? 1 : 0) / 4 % 6))); + } + for (var i = 0; i < 288; ++i) { + bitLengths[i] = i < 144 || i > 279 ? 8 : i < 256 ? 9 : 7; + } + var fixedLiteralTable = makeHuffmanTable(bitLengths); + function makeHuffmanTable(bitLengths) { + var maxBits = Math.max.apply(null, bitLengths); + var numLengths = bitLengths.length; + var size = 1 << maxBits; + var codes = new Uint32Array(size); + for (var code = 0, len = 1, skip = 2; len <= maxBits; code <<= 1, ++len, skip <<= 1) { + for (var val = 0; val < numLengths; ++val) { + if (bitLengths[val] === len) { + var lsb = 0; + for (var i = 0; i < len; ++i) { + lsb = lsb * 2 + (code >> i & 1); + } + for (var i = lsb; i < size; i += skip) { + codes[i] = len << 16 | val; + } + ++code; + } + } + } + return { + codes: codes, + maxBits: maxBits + }; + } + function inflateBlock(input, output) { + var header = readBits(input, 3); + switch (header >> 1) { + case 0: + input.bitBuffer = input.bitLength = 0; + var len = input.readUnsignedShort(); + var nlen = input.readUnsignedShort(); + if ((~nlen & 65535) !== len) { + throwCompressedDataError(); + } + output.writeBytes(input, input.position, len); + input.position += len; + break; + case 1: + inflate(input, output, fixedLiteralTable, fixedDistanceTable); + break; + case 2: + var bitLengths = []; + var numLiteralCodes = readBits(input, 5) + 257; + var numDistanceCodes = readBits(input, 5) + 1; + var numCodes = numLiteralCodes + numDistanceCodes; + var numLengthCodes = readBits(input, 4) + 4; + for (var i = 0; i < 19; ++i) { + bitLengths[codeLengthOrder[i]] = i < numLengthCodes ? readBits(sbytes, sstream, 3) : 0; + } + var codeLengthTable = makeHuffmanTable(bitLengths); + bitLengths = []; + var i = 0; + var prev = 0; + while (i < numCodes) { + var j = 1; + var sym = readCode(input, codeLengthTable); + switch (sym) { + case 16: + j = readBits(input, 2) + 3; + sym = prev; + break; + case 17: + j = readBits(input, 3) + 3; + sym = 0; + break; + case 18: + j = readBits(input, 7) + 11; + sym = 0; + break; + default: + prev = sym; + } + while (j--) { + bitLengths[i++] = sym; + } + } + var distanceTable = makeHuffmanTable(bitLengths.splice(numLiteralCodes, numDistanceCodes)); + var literalTable = makeHuffmanTable(bitLengths); + inflate(input, output, literalTable, distanceTable); + break; + default: + fail('unknown block type', 'inflate'); + } + } + function readBits(input, size) { + var buffer = input.bitBuffer; + var bufflen = input.bitLength; + while (size > bufflen) { + buffer |= input.readUnsignedByte() << bufflen; + bufflen += 8; + } + input.bitBuffer = buffer >>> size; + input.bitLength = bufflen - size; + return buffer & (1 << size) - 1; + } + function inflate(input, output, literalTable, distanceTable) { + var sym; + while ((sym = readCode(input, literalTable)) !== 256) { + if (sym < 256) { + output.writeUnsignedByte(sym); + } else { + sym -= 257; + var len = lengthCodes[sym] + readBits(input, lengthExtraBits[sym]); + sym = readCode(input, distanceTable); + var distance = distanceCodes[sym] + readBits(input, distanceExtraBits[sym]); + output.writeBytes(output, output.position - distance, len); + } + } + } + function readCode(input, codeTable) { + var buffer = input.bitBuffer; + var bitlen = input.bitLength; + var maxBits = codeTable.maxBits; + while (maxBits > bitlen) { + buffer |= input.readUnsignedByte() << bitlen; + bitlen += 8; + } + var code = codeTable.codes[buffer & (1 << maxBits) - 1]; + var len = code >> 16; + if (!len) { + throwCompressedDataError(); + } + input.bitBuffer = buffer >>> len; + input.bitLength = bitlen - len; + return code & 65535; + } + function adler32(data, start, end) { + var a = 1; + var b = 0; + for (var i = start; i < end; ++i) { + a = (a + (data[i] & 255)) % 65521; + b = (b + a) % 65521; + } + return b << 16 | a; + } + BAp.compress = function (algorithm) { + this.position = 0; + var output = new ByteArray(); + switch (algorithm) { + case 'zlib': + output.writeUnsignedByte(120); + output.writeUnsignedByte(156); + case 'deflate': + output.le = true; + var len = this.length; + output.ensureCapacity(len + Math.ceil(len / 65535) * 5 + 4); + while (len > 65535) { + output.writeUnsignedByte(0); + output.writeUnsignedShort(65535); + output.writeUnsignedShort(0); + output.writeBytes(this, this.position, 65535); + this.position += 65535; + len -= 65535; + } + output.writeUnsignedByte(0); + output.writeUnsignedShort(len); + output.writeUnsignedShort(~len & 65535); + output.writeBytes(this, this.position, len); + if (algorithm === 'zlib') { + output.writeUnsignedInt(adler32(this.uint8v, 0, this.length)); + } + break; + default: + return; + } + this.ensureCapacity(output.uint8v.length); + this.uint8v.set(output.uint8v); + this.length = output.length; + this.position = 0; + }; + BAp.uncompress = function (algorithm) { + var output = new ByteArray(); + switch (algorithm) { + case 'zlib': + var header = this.readUnsignedShort(); + if ((header & 3840) !== 2048 || header % 31 !== 0 || header & 32) { + throwCompressedDataError(); + } + case 'deflate': + var le = this.le; + this.le = true; + while (this.position < this.length - 6) { + inflateBlock(this, output); + } + this.le = le; + break; + default: + return; + } + this.ensureCapacity(output.uint8v.length); + this.uint8v.set(output.uint8v); + this.length = output.length; + this.position = 0; + }; + return ByteArrayClass; + }(); (function (exports) { var ArgumentParser = function () { var Argument = function () { function argument(shortName, longName, type, options) { this.shortName = shortName; this.longName = longName; this.type = type; options = options || {}; @@ -304,16 +751,223 @@ }(); exports.Option = Option; exports.OptionSet = OptionSet; exports.ArgumentParser = ArgumentParser; }(typeof exports === 'undefined' ? options = {} : exports)); var Option = options.Option; var OptionSet = options.OptionSet; var coreOptions = new OptionSet('Core Options'); +var Timeline = function () { + var barColor = 'rgba(255,255,255, 0.075)'; + var backgroundColor = 'rgb(61, 61, 61)'; + var backgroundColorInfo = 'rgba(0,0,0, 0.85)'; + var fpsLineColor = 'rgb(255,64,0)'; + var textColor = '#ccc'; + function timeline(canvas) { + this.depth = 0; + this.start = 0; + this.index = 0; + this.marks = new CircularBuffer(Int32Array); + this.times = new CircularBuffer(Float64Array); + this.frameRate = 12; + this.maxFrameTime = 1000 * 2 / this.frameRate; + this.refreshFrequency = 10; + this.refreshCounter = 0; + this.count = 0; + this.kinds = createEmptyObject(); + this.kindCount = 0; + this.canvas = canvas; + this.context = canvas.getContext('2d', { + original: true + }); + this.fillStyles = [ + 'rgb(85, 152, 213)', + '#bfd8a7', + '#d906d7' + ]; + window.addEventListener('resize', this.resizeHandler.bind(this), false); + this.resizeHandler(); + } + timeline.prototype.setFrameRate = function setFrameRate(frameRate) { + this.frameRate = frameRate; + this.maxFrameTime = 1000 * 2 / frameRate; + }; + timeline.prototype.refreshEvery = function refreshEvery(freq) { + this.refreshFrequency = freq; + this.refreshCounter = 0; + }; + var ENTER = 3203334144 | 0; + var LEAVE = 3735879680 | 0; + timeline.prototype.registerKind = function getKind(name, fillStyle) { + if (this.kinds[name] === undefined) { + this.fillStyles[this.kindCount] = fillStyle; + this.kinds[name] = this.kindCount++; + } else { + this.fillStyles[this.kinds[name]] = fillStyle; + } + }; + timeline.prototype.getKind = function getKind(name) { + if (this.kinds[name] === undefined) { + this.kinds[name] = this.kindCount++; + if (this.kindCount > this.fillStyles.length) { + this.fillStyles.push(randomStyle()); + } + } + return this.kinds[name]; + }; + timeline.prototype.enter = function enter(name) { + this.depth++; + this.marks.write(ENTER | this.getKind(name)); + this.times.write(performance.now()); + }; + timeline.prototype.leave = function leave(name) { + this.marks.write(LEAVE | this.getKind(name)); + this.times.write(performance.now()); + this.depth--; + if (this.depth === 0) { + this.count++; + if (++this.refreshCounter == this.refreshFrequency) { + this.refreshCounter = 0; + this.paint(); + } + } + }; + timeline.prototype.gatherFrames = function gatherFrames(maxFrames) { + var stack = []; + var frames = []; + var times = this.times; + maxFrames++; + this.marks.forEachInReverse(function (mark, i) { + var time = times.get(i); + if ((mark & 4294901760) === ENTER) { + var node = stack.pop(); + node.startTime = time; + if (!stack.length) { + if (frames.length && !frames[0].total) { + frames[0].total = frames[0].startTime - time; + } + frames.unshift(node); + } else { + var top = stack.top(); + if (!top.children) { + top.children = [ + node + ]; + } else { + top.children.push(node); + } + } + } else if ((mark & 4294901760) === LEAVE) { + if (frames.length > maxFrames) { + return true; + } + stack.push({ + kind: mark & 65535, + endTime: time + }); + } + }); + return frames; + }; + timeline.prototype.resizeHandler = function resizeHandler(event) { + var parent = this.canvas.parentElement; + this.cw = parent.offsetWidth; + this.ch = parent.offsetHeight - 1; + var devicePixelRatio = window.devicePixelRatio || 1; + var backingStoreRatio = this.context.webkitBackingStorePixelRatio || this.context.mozBackingStorePixelRatio || this.context.msBackingStorePixelRatio || this.context.oBackingStorePixelRatio || this.context.backingStorePixelRatio || 1; + if (devicePixelRatio !== backingStoreRatio) { + var ratio = devicePixelRatio / backingStoreRatio; + this.canvas.width = this.cw * ratio; + this.canvas.height = this.ch * ratio; + this.canvas.style.width = this.cw + 'px'; + this.canvas.style.height = this.ch + 'px'; + this.context.scale(ratio, ratio); + } else { + this.canvas.width = this.cw; + this.canvas.height = this.ch; + } + this.context.font = '10px Consolas, "Liberation Mono", Courier, monospace'; + }; + timeline.prototype.paint = function paint() { + var w = 10; + var gap = 1; + var maxFrames = this.cw / (w + gap) | 0; + var frames = this.gatherFrames(maxFrames); + var context = this.context; + var maxFrameTime = this.maxFrameTime; + var fillStyles = this.fillStyles; + context.clearRect(0, 0, this.cw, this.ch); + var maxFrameRate = 0; + var maxFrameRateCount = 0; + var avgFrameRate = 0; + var avgFrameRateCount = 0; + var offsetW; + context.save(); + context.translate(0, this.ch); + context.scale(1, -this.ch / maxFrameTime); + for (var i = 0; i < frames.length - 1; i++) { + var frame = frames[i]; + maxFrameRate += frame.endTime - frame.startTime; + maxFrameRateCount++; + if (frame.total) { + avgFrameRate += frame.total; + avgFrameRateCount++; + } + offsetW = i * (w + gap); + context.fillStyle = barColor; + context.fillRect(offsetW, 0, w, frames[i + 1].startTime - frame.startTime); + drawNode(frame, frame.startTime); + } + function drawNode(node, frameStartTime) { + var nodeTime = node.endTime - node.startTime; + var offsetH = node.startTime - frameStartTime; + context.fillStyle = fillStyles[node.kind]; + context.fillRect(offsetW, offsetH, w, nodeTime); + if (node.children) { + var children = node.children; + for (var i = 0, n = children.length; i < n; i++) { + drawNode(children[i], frameStartTime); + } + } + } + var lineH = 1000 / this.frameRate; + context.beginPath(); + context.lineWidth = 0.5; + context.moveTo(0, lineH); + context.lineTo(this.cw, lineH); + context.strokeStyle = fpsLineColor; + context.stroke(); + context.restore(); + context.fillStyle = backgroundColorInfo; + context.fillRect(0, 0, this.cw, 20); + var textOffset; + var sFrameCount = this.count; + var sMaxFrameRate = Math.round(1000 * maxFrameRateCount / maxFrameRate); + var sAvgFrameRate = Math.round(1000 * avgFrameRateCount / avgFrameRate); + var space = 5; + textOffset = 5; + context.fillStyle = textColor; + context.fillText(sFrameCount, textOffset, 13); + textOffset += context.measureText(sFrameCount).width + space; + context.fillText(sMaxFrameRate, textOffset, 13); + textOffset += context.measureText(sMaxFrameRate).width + space; + context.fillText(sAvgFrameRate, textOffset, 13); + var basicOffset = textOffset + context.measureText(sAvgFrameRate).width + space; + textOffset = this.cw; + for (var k in this.kinds) { + context.fillStyle = this.fillStyles[this.getKind(k)]; + textOffset -= context.measureText(k).width + space; + if (textOffset > basicOffset) { + this.context.fillText(k, textOffset, 13); + } + } + }; + return timeline; + }(); var create = Object.create; var defineProperty = Object.defineProperty; var keys = Object.keys; var isArray = Array.isArray; var fromCharCode = String.fromCharCode; var logE = Math.log; var max = Math.max; var min = Math.min; @@ -335,42 +989,22 @@ function scriptProperties(namespace, pro }, {}); } function cloneObject(obj) { var clone = Object.create(null); for (var prop in obj) clone[prop] = obj[prop]; return clone; } -function sortByDepth(a, b) { - var levelA = a._level; - var levelB = b._level; - if (a._parent !== b._parent && a._index > -1 && b._index > -1) { - while (a._level > levelB) { - a = a._parent; - } - while (b._level > levelA) { - b = b._parent; - } - while (a._level > 1) { - if (a._parent === b._parent) { - break; - } - a = a._parent; - b = b._parent; - } - } - if (a === b) { - return levelA - levelB; - } - return a._index - b._index; -} function sortNumeric(a, b) { return a - b; } +function sortByZindex(a, b) { + return a._zindex - b._zindex; +} function rgbaObjToStr(color) { return 'rgba(' + color.red + ',' + color.green + ',' + color.blue + ',' + color.alpha / 255 + ')'; } function rgbIntAlphaToStr(color, alpha) { color |= 0; if (alpha >= 1) { var colorStr = color.toString(16); while (colorStr.length < 6) { @@ -444,168 +1078,346 @@ function randomStyle() { '#ff1300', '#1f1f21', '#bdbec2', '#ff3a2d' ]; } return randomStyleCache[nextStyle++ % randomStyleCache.length]; } -var Promise = function PromiseClosure() { - function isPromise(obj) { - return typeof obj === 'object' && obj !== null && typeof obj.then === 'function'; - } - function defaultOnFulfilled(value) { - return value; - } - function defaultOnRejected(reason) { - throw reason; - } - function propagateFulfilled(subject, value) { - subject.subpromisesValue = value; - var subpromises = subject.subpromises; - if (!subpromises) { - return; - } - for (var i = 0; i < subpromises.length; i++) { - subpromises[i].fulfill(value); - } - delete subject.subpromises; - } - function propagateRejected(subject, reason) { - subject.subpromisesReason = reason; - var subpromises = subject.subpromises; - if (!subpromises) { - if (!true) { - console.warn(reason); - } - return; - } - for (var i = 0; i < subpromises.length; i++) { - subpromises[i].reject(reason); - } - delete subject.subpromises; - } - function performCall(callback, arg, subject) { - try { - var value = callback(arg); - if (isPromise(value)) { - value.then(function Promise_queueCall_onFulfilled(value) { - propagateFulfilled(subject, value); - }, function Promise_queueCall_onRejected(reason) { - propagateRejected(subject, reason); - }); - return; - } - propagateFulfilled(subject, value); - } catch (ex) { - propagateRejected(subject, ex); - } - } - var queue = []; - function processQueue() { - while (queue.length > 0) { - var task = queue[0]; - if (task.directCallback) { - task.callback.call(task.subject, task.arg); - } else { - performCall(task.callback, task.arg, task.subject); - } - queue.shift(); - } - } - function queueCall(callback, arg, subject, directCallback) { - if (queue.length === 0) { - setTimeout(processQueue, 0); - } - queue.push({ - callback: callback, - arg: arg, - subject: subject, - directCallback: directCallback - }); - } - function Promise(onFulfilled, onRejected) { - this.state = 'pending'; - this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : defaultOnFulfilled; - this.onRejected = typeof onRejected === 'function' ? onRejected : defaultOnRejected; - } - Promise.prototype = { - fulfill: function Promise_resolve(value) { - if (this.state !== 'pending') { - return; - } - this.state = 'fulfilled'; - this.value = value; - queueCall(this.onFulfilled, value, this, false); - }, - reject: function Promise_reject(reason) { - if (this.state !== 'pending') { - return; - } - this.state = 'rejected'; - this.reason = reason; - queueCall(this.onRejected, reason, this, false); - }, - then: function Promise_then(onFulfilled, onRejected) { - var promise = new Promise(onFulfilled, onRejected); - if ('subpromisesValue' in this) { - queueCall(promise.fulfill, this.subpromisesValue, promise, true); - } else if ('subpromisesReason' in this) { - queueCall(promise.reject, this.subpromisesReason, promise, true); - } else { - var subpromises = this.subpromises || (this.subpromises = []); - subpromises.push(promise); +(function PromiseClosure() { + var global = Function('return this')(); + if (global.Promise) { + if (typeof global.Promise.all !== 'function') { + global.Promise.all = function (iterable) { + var count = 0, results = [], resolve, reject; + var promise = new global.Promise(function (resolve_, reject_) { + resolve = resolve_; + reject = reject_; + }); + iterable.forEach(function (p, i) { + count++; + p.then(function (result) { + results[i] = result; + count--; + if (count === 0) { + resolve(results); + } + }, reject); + }); + if (count === 0) { + resolve(results); } return promise; - }, - get resolved() { - return this.state === 'fulfilled'; - }, - resolve: function (value) { - this.fulfill(value); - } - }; - Promise.when = function Promise_when() { - var promise = new Promise(); - if (arguments.length === 0) { - promise.resolve(); - return promise; - } - var promises = slice.call(arguments, 0); - var result = []; - var i = 1; - function fulfill(value) { - result.push(value); - if (i < promises.length) { - promises[i++].then(fulfill, reject); - } else { - promise.resolve(result); - } - return value; - } - function reject(reason) { - promise.reject(reason); - } - promises[0].then(fulfill, reject); - return promise; - }; - return Promise; - }(); -var QuadTree = function (x, y, width, height, level) { + }; + } + if (typeof global.Promise.resolve !== 'function') { + global.Promise.resolve = function (x) { + return new global.Promise(function (resolve) { + resolve(x); + }); + }; + } + return; + } + function getDeferred(C) { + if (typeof C !== 'function') { + throw new TypeError('Invalid deferred constructor'); + } + var resolver = createDeferredConstructionFunctions(); + var promise = new C(resolver); + var resolve = resolver.resolve; + if (typeof resolve !== 'function') { + throw new TypeError('Invalid resolve construction function'); + } + var reject = resolver.reject; + if (typeof reject !== 'function') { + throw new TypeError('Invalid reject construction function'); + } + return { + promise: promise, + resolve: resolve, + reject: reject + }; + } + function updateDeferredFromPotentialThenable(x, deferred) { + if (typeof x !== 'object' || x === null) { + return false; + } + try { + var then = x.then; + if (typeof then !== 'function') { + return false; + } + var thenCallResult = then.call(x, deferred.resolve, deferred.reject); + } catch (e) { + var reject = deferred.reject; + reject(e); + } + return true; + } + function isPromise(x) { + return typeof x === 'object' && x !== null && typeof x.promiseStatus !== 'undefined'; + } + function rejectPromise(promise, reason) { + if (promise.promiseStatus !== 'unresolved') { + return; + } + var reactions = promise.rejectReactions; + promise.result = reason; + promise.resolveReactions = undefined; + promise.rejectReactions = undefined; + promise.promiseStatus = 'has-rejection'; + triggerPromiseReactions(reactions, reason); + } + function resolvePromise(promise, resolution) { + if (promise.promiseStatus !== 'unresolved') { + return; + } + var reactions = promise.resolveReactions; + promise.result = resolution; + promise.resolveReactions = undefined; + promise.rejectReactions = undefined; + promise.promiseStatus = 'has-resolution'; + triggerPromiseReactions(reactions, resolution); + } + function triggerPromiseReactions(reactions, argument) { + for (var i = 0; i < reactions.length; i++) { + queueMicrotask({ + reaction: reactions[i], + argument: argument + }); + } + } + function queueMicrotask(task) { + if (microtasksQueue.length === 0) { + setTimeout(handleMicrotasksQueue, 0); + } + microtasksQueue.push(task); + } + function executePromiseReaction(reaction, argument) { + var deferred = reaction.deferred; + var handler = reaction.handler; + var handlerResult, updateResult; + try { + handlerResult = handler(argument); + } catch (e) { + var reject = deferred.reject; + return reject(e); + } + if (handlerResult === deferred.promise) { + var reject = deferred.reject; + return reject(new TypeError('Self resolution')); + } + try { + updateResult = updateDeferredFromPotentialThenable(handlerResult, deferred); + if (!updateResult) { + var resolve = deferred.resolve; + return resolve(handlerResult); + } + } catch (e) { + var reject = deferred.reject; + return reject(e); + } + } + var microtasksQueue = []; + function handleMicrotasksQueue() { + while (microtasksQueue.length > 0) { + var task = microtasksQueue[0]; + try { + executePromiseReaction(task.reaction, task.argument); + } catch (e) { + if (typeof Promise.onerror === 'function') { + Promise.onerror(e); + } + } + microtasksQueue.shift(); + } + } + function throwerFunction(e) { + throw e; + } + function identityFunction(x) { + return x; + } + function createRejectPromiseFunction(promise) { + return function (reason) { + rejectPromise(promise, reason); + }; + } + function createResolvePromiseFunction(promise) { + return function (resolution) { + resolvePromise(promise, resolution); + }; + } + function createDeferredConstructionFunctions() { + var fn = function (resolve, reject) { + fn.resolve = resolve; + fn.reject = reject; + }; + return fn; + } + function createPromiseResolutionHandlerFunctions(promise, fulfillmentHandler, rejectionHandler) { + return function (x) { + if (x === promise) { + return rejectionHandler(new TypeError('Self resolution')); + } + var cstr = promise.promiseConstructor; + if (isPromise(x)) { + var xConstructor = x.promiseConstructor; + if (xConstructor === cstr) { + return x.then(fulfillmentHandler, rejectionHandler); + } + } + var deferred = getDeferred(cstr); + var updateResult = updateDeferredFromPotentialThenable(x, deferred); + if (updateResult) { + var deferredPromise = deferred.promise; + return deferredPromise.then(fulfillmentHandler, rejectionHandler); + } + return fulfillmentHandler(x); + }; + } + function createPromiseAllCountdownFunction(index, values, deferred, countdownHolder) { + return function (x) { + values[index] = x; + countdownHolder.countdown--; + if (countdownHolder.countdown === 0) { + deferred.resolve(values); + } + }; + } + function Promise(resolver) { + if (typeof resolver !== 'function') { + throw new TypeError('resolver is not a function'); + } + var promise = this; + if (typeof promise !== 'object') { + throw new TypeError('Promise to initialize is not an object'); + } + promise.promiseStatus = 'unresolved'; + promise.resolveReactions = []; + promise.rejectReactions = []; + promise.result = undefined; + var resolve = createResolvePromiseFunction(promise); + var reject = createRejectPromiseFunction(promise); + try { + var result = resolver(resolve, reject); + } catch (e) { + rejectPromise(promise, e); + } + promise.promiseConstructor = Promise; + return promise; + } + Promise.all = function (iterable) { + var deferred = getDeferred(this); + var values = []; + var countdownHolder = { + countdown: 0 + }; + var index = 0; + iterable.forEach(function (nextValue) { + var nextPromise = this.cast(nextValue); + var fn = createPromiseAllCountdownFunction(index, values, deferred, countdownHolder); + nextPromise.then(fn, deferred.reject); + index++; + countdownHolder.countdown++; + }, this); + if (index === 0) { + deferred.resolve(values); + } + return deferred.promise; + }; + Promise.cast = function (x) { + if (isPromise(x)) { + return x; + } + var deferred = getDeferred(this); + deferred.resolve(x); + return deferred.promise; + }; + Promise.reject = function (r) { + var deferred = getDeferred(this); + var rejectResult = deferred.reject(r); + return deferred.promise; + }; + Promise.resolve = function (x) { + var deferred = getDeferred(this); + var rejectResult = deferred.resolve(x); + return deferred.promise; + }; + Promise.prototype = { + 'catch': function (onRejected) { + this.then(undefined, onRejected); + }, + then: function (onFulfilled, onRejected) { + var promise = this; + if (!isPromise(promise)) { + throw new TypeError('this is not a Promises'); + } + var cstr = promise.promiseConstructor; + var deferred = getDeferred(cstr); + var rejectionHandler = typeof onRejected === 'function' ? onRejected : throwerFunction; + var fulfillmentHandler = typeof onFulfilled === 'function' ? onFulfilled : identityFunction; + var resolutionHandler = createPromiseResolutionHandlerFunctions(promise, fulfillmentHandler, rejectionHandler); + var resolveReaction = { + deferred: deferred, + handler: resolutionHandler + }; + var rejectReaction = { + deferred: deferred, + handler: rejectionHandler + }; + switch (promise.promiseStatus) { + case 'unresolved': + promise.resolveReactions.push(resolveReaction); + promise.rejectReactions.push(rejectReaction); + break; + case 'has-resolution': + var resolution = promise.result; + queueMicrotask({ + reaction: resolveReaction, + argument: resolution + }); + break; + case 'has-rejection': + var rejection = promise.result; + queueMicrotask({ + reaction: rejectReaction, + argument: rejection + }); + break; + } + return deferred.promise; + } + }; + global.Promise = Promise; +}()); +var QuadTree = function (x, y, width, height, parent) { this.x = x | 0; this.y = y | 0; this.width = width | 0; this.height = height | 0; - this.level = level | 0; - this.stuckObjects = []; - this.objects = []; + if (parent) { + this.root = parent.root; + this.parent = parent; + this.level = parent.level + 1; + } else { + this.root = this; + this.parent = null; + this.level = 0; + } + this.reset(); +}; +QuadTree.prototype.reset = function () { + this.stuckObjects = null; + this.objects = null; this.nodes = []; }; -QuadTree.prototype._findIndex = function (xMin, yMin, xMax, yMax) { +QuadTree.prototype._findIndex = function (xMin, xMax, yMin, yMax) { var midX = this.x + (this.width / 2 | 0); var midY = this.y + (this.height / 2 | 0); var top = yMin < midY && yMax < midY; var bottom = yMin > midY; if (xMin < midX && xMax < midX) { if (top) { return 1; } else if (bottom) { @@ -618,81 +1430,218 @@ QuadTree.prototype._findIndex = function return 3; } } return -1; }; QuadTree.prototype.insert = function (obj) { var nodes = this.nodes; if (nodes.length) { - var index = this._findIndex(obj.xMin, obj.yMin, obj.xMax, obj.yMax); + var index = this._findIndex(obj.xMin, obj.xMax, obj.yMin, obj.yMax); if (index > -1) { nodes[index].insert(obj); } else { - this.stuckObjects.push(obj); - obj._qtree = this; - } - return; - } - var objects = this.objects; - objects.push(obj); - if (objects.length > 4 && this.level < 10) { - this._subdivide(); - while (objects.length) { - this.insert(objects.shift()); + obj.prev = null; + if (this.stuckObjects) { + obj.next = this.stuckObjects; + this.stuckObjects.prev = obj; + } else { + obj.next = null; + } + this.stuckObjects = obj; + obj.parent = this; } return; } - obj._qtree = this; -}; -QuadTree.prototype.delete = function (obj) { - if (obj._qtree !== this) { - return; - } - var index = this.objects.indexOf(obj); - if (index > -1) { - this.objects.splice(index, 1); + var numChildren = 1; + var item = this.objects; + if (!item) { + obj.prev = null; + obj.next = null; + this.objects = obj; } else { - index = this.stuckObjects.indexOf(obj); - this.stuckObjects.splice(index, 1); - } - obj._qtree = null; -}; -QuadTree.prototype._stack = []; -QuadTree.prototype._out = []; -QuadTree.prototype.retrieve = function (xMin, yMin, xMax, yMax) { - var stack = this._stack; - var out = this._out; - out.length = 0; + while (item.next) { + numChildren++; + item = item.next; + } + obj.prev = item; + obj.next = null; + item.next = obj; + } + if (numChildren > 4 && this.level < 10) { + this._subdivide(); + item = this.objects; + while (item) { + var next = item.next; + this.insert(item); + item = next; + } + this.objects = null; + return; + } + obj.parent = this; +}; +QuadTree.prototype.update = function (obj) { + var node = obj.parent; + if (node) { + if (obj.xMin >= node.x && obj.xMax <= node.x + node.width && obj.yMin >= node.y && obj.yMax <= node.y + node.height) { + if (node.nodes.length) { + var index = this._findIndex(obj.xMin, obj.xMax, obj.yMin, obj.yMax); + if (index > -1) { + node.remove(obj); + node = this.nodes[index]; + node.insert(obj); + } + } else { + node.remove(obj); + node.insert(obj); + } + return; + } + node.remove(obj); + } + this.root.insert(obj); +}; +QuadTree.prototype.remove = function (obj) { + var prev = obj.prev; + var next = obj.next; + if (prev) { + prev.next = next; + obj.prev = null; + } else { + var node = obj.parent; + if (node.objects === obj) { + node.objects = next; + } else if (node.stuckObjects === obj) { + node.stuckObjects = next; + } + } + if (next) { + next.prev = prev; + obj.next = null; + } + obj.parent = null; +}; +QuadTree.prototype.retrieve = function (xMin, xMax, yMin, yMax) { + var stack = []; + var out = []; var node = this; do { if (node.nodes.length) { - var index = node._findIndex(xMin, yMin, xMax, yMax); + var index = node._findIndex(xMin, xMax, yMin, yMax); if (index > -1) { stack.push(node.nodes[index]); } else { stack.push.apply(stack, node.nodes); } } - out.push.apply(out, node.stuckObjects); - out.push.apply(out, node.objects); + var item = node.objects; + for (var i = 0; i < 2; i++) { + while (item) { + if (!(item.xMin > xMax || item.xMax < xMin || item.yMin > yMax || item.yMax < yMin)) { + out.push(item); + } + item = item.next; + } + item = node.stuckObjects; + } node = stack.pop(); } while (node); return out; }; QuadTree.prototype._subdivide = function () { var halfWidth = this.width / 2 | 0; var halfHeight = this.height / 2 | 0; var midX = this.x + halfWidth; var midY = this.y + halfHeight; - var level = this.level + 1; - this.nodes[0] = new QuadTree(midX, this.y, halfWidth, halfHeight, level); - this.nodes[1] = new QuadTree(this.x, this.y, halfWidth, halfHeight, level); - this.nodes[2] = new QuadTree(this.x, midY, halfWidth, halfHeight, level); - this.nodes[3] = new QuadTree(midX, midY, halfWidth, halfHeight, level); + this.nodes[0] = new QuadTree(midX, this.y, halfWidth, halfHeight, this); + this.nodes[1] = new QuadTree(this.x, this.y, halfWidth, halfHeight, this); + this.nodes[2] = new QuadTree(this.x, midY, halfWidth, halfHeight, this); + this.nodes[3] = new QuadTree(midX, midY, halfWidth, halfHeight, this); +}; +var RegionCluster = function () { + this.regions = []; +}; +RegionCluster.prototype.reset = function () { + this.regions.length = 0; +}; +RegionCluster.prototype.insert = function (region) { + var regions = this.regions; + if (regions.length < 3) { + regions.push({ + xMin: region.xMin, + xMax: region.xMax, + yMin: region.yMin, + yMax: region.yMax + }); + return; + } + var a = region; + var b = regions[0]; + var c = regions[1]; + var d = regions[2]; + var ab = (max(a.xMax, b.xMax) - min(a.xMin, b.xMin)) * (max(a.yMax, b.yMax) - min(a.yMin, b.yMin)); + var rb = regions[0]; + var ac = (max(a.xMax, c.xMax) - min(a.xMin, c.xMin)) * (max(a.yMax, c.yMax) - min(a.yMin, c.yMin)); + var ad = (max(a.xMax, d.xMax) - min(a.xMin, d.xMin)) * (max(a.yMax, d.yMax) - min(a.yMin, d.yMin)); + if (ac < ab) { + ab = ac; + rb = c; + } + if (ad < ab) { + ab = ad; + rb = d; + } + var bc = (max(b.xMax, c.xMax) - min(b.xMin, c.xMin)) * (max(b.yMax, c.yMax) - min(b.yMin, c.yMin)); + var bd = (max(b.xMax, d.xMax) - min(b.xMin, d.xMin)) * (max(b.yMax, d.yMax) - min(b.yMin, d.yMin)); + var cd = (max(c.xMax, d.xMax) - min(c.xMin, d.xMin)) * (max(c.yMax, d.yMax) - min(c.yMin, d.yMin)); + if (ab < bc && ab < bd && ab < cd) { + if (a.xMin < rb.xMin) { + rb.xMin = a.xMin; + } + if (a.xMax > rb.xMax) { + rb.xMax = a.xMax; + } + if (a.yMin < rb.yMin) { + rb.yMin = a.yMin; + } + if (a.yMax > rb.yMax) { + rb.yMax = a.yMax; + } + return; + } + rb = regions[0]; + var rc = regions[1]; + if (bd < bc) { + bc = bd; + rc = regions[2]; + } + if (cd < bc) { + rb = regions[1]; + rc = regions[2]; + } + if (rc.xMin < rb.xMin) { + rb.xMin = rc.xMin; + } + if (rc.xMax > rb.xMax) { + rb.xMax = rc.xMax; + } + if (rc.yMin < rb.yMin) { + rb.yMin = rc.yMin; + } + if (rc.yMax > rb.yMax) { + rb.yMax = rc.yMax; + } + rc.xMin = a.xMin; + rc.xMax = a.xMax; + rc.yMin = a.yMin; + rc.yMax = a.yMax; +}; +RegionCluster.prototype.retrieve = function () { + return this.regions; }; var EXTERNAL_INTERFACE_FEATURE = 1; var CLIPBOARD_FEATURE = 2; var SHAREDOBJECT_FEATURE = 3; var VIDEO_FEATURE = 4; var SOUND_FEATURE = 5; var NETCONNECTION_FEATURE = 6; if (!this.performance) { @@ -2297,16 +3246,19 @@ function ShapePath(fillStyle, lineStyle, this.buffers.push(this.morphData.buffer); transferables.push(this.morphData.buffer); } } else { this.buffers = null; } } ShapePath.prototype = { + get isEmpty() { + return this.commands.length === 0; + }, moveTo: function (x, y) { if (this.commands[this.commands.length - 1] === SHAPE_MOVE_TO) { this.data[this.data.length - 2] = x; this.data[this.data.length - 1] = y; return; } this.commands.push(SHAPE_MOVE_TO); this.data.push(x, y); @@ -2458,16 +3410,18 @@ ShapePath.prototype = { colorTransform.setAlpha(ctx); ctx.lineWidth = Math.max(lineStyle.width / 20, 1); ctx.lineCap = lineStyle.lineCap; ctx.lineJoin = lineStyle.lineJoin; ctx.miterLimit = lineStyle.miterLimit; ctx.stroke(); ctx.restore(); } + } else { + ctx.fill(); } ctx.closePath(); }, isPointInPath: function (x, y) { if (!(this.fillStyle || this.lineStyle)) { return false; } var bounds = this.strokeBounds || this.bounds || this._calculateBounds(); @@ -3139,32 +4093,32 @@ function extendBoundsByY(bounds, y) { bounds.yMin = y; } else if (y > bounds.yMax) { bounds.yMax = y; } } function morph(start, end, ratio) { return start + (end - start) * ratio; } -function finishShapePath(path, dictionary) { +function finishShapePath(path, dictionaryResolved) { if (path.fullyInitialized) { return path; } if (!(path instanceof ShapePath)) { var untypedPath = path; path = new ShapePath(path.fillStyle, path.lineStyle, 0, 0, path.isMorph); path.commands = new Uint8Array(untypedPath.buffers[0]); path.data = new Int32Array(untypedPath.buffers[1]); if (untypedPath.isMorph) { path.morphData = new Int32Array(untypedPath.buffers[2]); } path.buffers = null; } - path.fillStyle && initStyle(path.fillStyle, dictionary); - path.lineStyle && initStyle(path.lineStyle, dictionary); + path.fillStyle && initStyle(path.fillStyle, dictionaryResolved); + path.lineStyle && initStyle(path.lineStyle, dictionaryResolved); path.fullyInitialized = true; return path; } var inWorker = typeof window === 'undefined'; var factoryCtx = !inWorker ? document.createElement('canvas').getContext('2d') : null; function buildLinearGradientFactory(colorStops) { var defaultGradient = factoryCtx.createLinearGradient(-1, 0, 1, 0); for (var i = 0; i < colorStops.length; i++) { @@ -3214,17 +4168,17 @@ function buildBitmapPatternFactory(img, ctx.drawImage(img, 0, 0); cachedTransform = ctx.createPattern(canvas, repeat); cachedTransformKey = key; return cachedTransform; }; fn.defaultFillStyle = defaultPattern; return fn; } -function initStyle(style, dictionary) { +function initStyle(style, dictionaryResolved) { if (style.type === undefined) { return; } switch (style.type) { case GRAPHICS_FILL_SOLID: break; case GRAPHICS_FILL_LINEAR_GRADIENT: case GRAPHICS_FILL_RADIAL_GRADIENT: @@ -3246,19 +4200,19 @@ function initStyle(style, dictionary) { gradientConstructor = buildRadialGradientFactory((style.focalPoint | 0) / 20, colorStops); } style.style = gradientConstructor; break; case GRAPHICS_FILL_REPEATING_BITMAP: case GRAPHICS_FILL_CLIPPED_BITMAP: case GRAPHICS_FILL_NONSMOOTHED_REPEATING_BITMAP: case GRAPHICS_FILL_NONSMOOTHED_CLIPPED_BITMAP: - var bitmap = dictionary[style.bitmapId]; + var bitmap = dictionaryResolved[style.bitmapId]; var repeat = style.type === GRAPHICS_FILL_REPEATING_BITMAP || style.type === GRAPHICS_FILL_NONSMOOTHED_REPEATING_BITMAP; - style.style = buildBitmapPatternFactory(bitmap.value.props.img, repeat ? 'repeat' : 'no-repeat'); + style.style = buildBitmapPatternFactory(bitmap.props.img, repeat ? 'repeat' : 'no-repeat'); break; default: fail('invalid fill style', 'shape'); } } var SOUND_SIZE_8_BIT = 0; var SOUND_SIZE_16_BIT = 1; var SOUND_TYPE_MONO = 0; @@ -4037,47 +4991,53 @@ SWF.embed = function (file, doc, contain } stage._color = bgcolor; ctx.fillStyle = rgbaObjToStr(bgcolor); ctx.fillRect(0, 0, canvas.width, canvas.height); var root = loader._content; root._dispatchEvent('added', undefined, true); root._dispatchEvent('addedToStage'); container.appendChild(canvas); + stage._domContainer = container; if (options.onStageInitialized) { options.onStageInitialized(stage); } renderStage(stage, ctx, options); }); if (options.onComplete) { loaderInfo._addEventListener('complete', function () { options.onComplete(); }); } loader._load(typeof file === 'string' ? new flash.net.URLRequest(file) : file); return loader; }; var rendererOptions = coreOptions.register(new OptionSet('Renderer Options')); var traceRenderer = rendererOptions.register(new Option('tr', 'traceRenderer', 'number', 0, 'trace renderer execution')); -var disablePreVisitor = rendererOptions.register(new Option('dpv', 'disablePreVisitor', 'boolean', false, 'disable pre visitor')); var disableRenderVisitor = rendererOptions.register(new Option('drv', 'disableRenderVisitor', 'boolean', false, 'disable render visitor')); var disableMouseVisitor = rendererOptions.register(new Option('dmv', 'disableMouseVisitor', 'boolean', false, 'disable mouse visitor')); var showRedrawRegions = rendererOptions.register(new Option('rr', 'showRedrawRegions', 'boolean', false, 'show redraw regions')); var renderAsWireframe = rendererOptions.register(new Option('raw', 'renderAsWireframe', 'boolean', false, 'render as wireframe')); var showQuadTree = rendererOptions.register(new Option('qt', 'showQuadTree', 'boolean', false, 'show quad tree')); var turboMode = rendererOptions.register(new Option('', 'turbo', 'boolean', false, 'turbo mode')); var forceHidpi = rendererOptions.register(new Option('', 'forceHidpi', 'boolean', false, 'force hidpi')); +var skipFrameDraw = rendererOptions.register(new Option('', 'skipFrameDraw', 'boolean', true, 'skip frame when not on time')); +var hud = rendererOptions.register(new Option('', 'hud', 'boolean', false, 'show hud mode')); var enableConstructChildren = rendererOptions.register(new Option('', 'constructChildren', 'boolean', true, 'Construct Children')); var enableEnterFrame = rendererOptions.register(new Option('', 'enterFrame', 'boolean', true, 'Enter Frame')); var enableAdvanceFrame = rendererOptions.register(new Option('', 'advanceFrame', 'boolean', true, 'Advance Frame')); if (typeof FirefoxCom !== 'undefined') { turboMode.value = FirefoxCom.requestSync('getBoolPref', { pref: 'shumway.turboMode', def: false }); + hud.value = FirefoxCom.requestSync('getBoolPref', { + pref: 'shumway.hud', + def: false + }); forceHidpi.value = FirefoxCom.requestSync('getBoolPref', { pref: 'shumway.force_hidpi', def: false }); } var CanvasCache = { cache: [], getCanvas: function getCanvas(protoCanvas) { @@ -4179,17 +5139,17 @@ RenderVisitor.prototype = { root._invalidateTransform(); } }, childrenStart: function (parent) { if (this.depth === 0) { var ctx = this.ctx; ctx.save(); if (this.invalidPath && !this.refreshStage && !renderAsWireframe.value) { - this.invalidPath.draw(ctx); + this.invalidPath.draw(ctx, false, 0, null); ctx.clip(); } var bgcolor = this.root._color; if (bgcolor) { if (bgcolor.alpha < 255) { ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); } if (bgcolor.alpha > 0) { @@ -4239,33 +5199,34 @@ RenderVisitor.prototype = { var clippingMask = parentHasClippingMask === true; if (child._cxform) { context.colorTransform = parentColorTransform.applyCXForm(child._cxform); } if (!clippingMask) { while (this.clipDepth && this.clipDepth.length > 0 && child._depth > this.clipDepth[0].clipDepth) { var clipDepthInfo = this.clipDepth.shift(); this.clipEnd(clipDepthInfo); + context.parentCtxs.shift(); ctx = this.ctx = clipDepthInfo.ctx; } + if (this.clipDepth && this.clipDepth.length > 0 && child._depth <= this.clipDepth[0].clipDepth) { + ctx = this.ctx = this.clipDepth[0].maskee.ctx; + } if (child._clipDepth) { context.isClippingMask = clippingMask = true; var clipDepthInfo = this.clipStart(child); if (!this.clipDepth) { this.clipDepth = [ clipDepthInfo ]; } else { this.clipDepth.unshift(clipDepthInfo); } + context.parentCtxs.unshift(ctx); ctx = this.ctx = clipDepthInfo.mask.ctx; - } else { - if (this.clipDepth && this.clipDepth.length > 0 && child._depth <= this.clipDepth[0].clipDepth) { - ctx = this.ctx = this.clipDepth[0].maskee.ctx; - } } } if (clippingMask && child._isContainer) { ctx.save(); renderDisplayObject(child, ctx, context); for (var i = 0, n = child._children.length; i < n; i++) { var child1 = child._children[i]; if (!child1) { @@ -4282,44 +5243,46 @@ RenderVisitor.prototype = { return; } ctx.save(); ctx.globalCompositeOperation = getBlendModeName(child._blendMode); if (child._mask) { var clipInfo = this.clipStart(child); var mask = clipInfo.mask; var maskee = clipInfo.maskee; + context.parentCtxs.push(ctx); var savedClipDepth = this.clipDepth; this.clipDepth = null; this.ctx = mask.ctx; this.visit(child._mask, visitContainer, new RenderingContext(this.refreshStage)); this.ctx = ctx; this.clipDepth = savedClipDepth; renderDisplayObject(child, maskee.ctx, context); if (child._isContainer) { this.ctx = maskee.ctx; visitContainer(child, this, context); this.ctx = ctx; } + context.parentCtxs.pop(); this.clipEnd(clipInfo); } else { renderDisplayObject(child, ctx, context); if (child._isContainer) { visitContainer(child, this, context); } } ctx.restore(); if (clippingMask) { ctx.fill(); } context.isClippingMask = parentHasClippingMask; context.colorTransform = parentColorTransform; }, clipStart: function (child) { - var m = child._parent._getConcatenatedTransform(true); + var m = child._parent._getConcatenatedTransform(null, true); var tx = m.tx / 20; var ty = m.ty / 20; var mask = CanvasCache.getCanvas(this.ctx.canvas); mask.ctx.setTransform(m.a, m.b, m.c, m.d, tx, ty); var maskee = CanvasCache.getCanvas(this.ctx.canvas); maskee.ctx.setTransform(m.a, m.b, m.c, m.d, tx, ty); var clipInfo = { ctx: this.ctx, @@ -4460,16 +5423,17 @@ RenderingColorTransform.prototype = { return this.transform.join('|'); } }; function RenderingContext(refreshStage, invalidPath) { this.refreshStage = refreshStage === true; this.invalidPath = invalidPath; this.isClippingMask = false; this.colorTransform = new RenderingColorTransform(); + this.parentCtxs = []; } function renderDisplayObject(child, ctx, context) { var m = child._currentTransform; if (m) { if (m.a * m.d == m.b * m.c) { ctx.closePath(); ctx.rect(0, 0, 0, 0); ctx.clip(); @@ -4488,21 +5452,25 @@ function renderDisplayObject(child, ctx, var graphics = child._graphics; if (graphics._bitmap) { ctx.save(); ctx.translate(child._bbox.xMin / 20, child._bbox.yMin / 20); context.colorTransform.setAlpha(ctx, true); ctx.drawImage(graphics._bitmap, 0, 0); ctx.restore(); } else { - graphics.draw(ctx, context.isClippingMask, child.ratio, context.colorTransform); + var ratio = child.ratio; + if (ratio === undefined) { + ratio = 0; + } + graphics.draw(ctx, context.isClippingMask, ratio, context.colorTransform); } } if (child.draw) { - child.draw(ctx, child.ratio, context.colorTransform); + child.draw(ctx, child.ratio, context.colorTransform, context.parentCtxs); } } else { if (!child._invalid && !context.refreshStage) { return; } if (child.getBounds) { var b = child.getBounds(null); if (b && b.xMax - b.xMin > 0 && b.yMax - b.yMin > 0) { @@ -4549,29 +5517,50 @@ function sampleEnd() { return; } samplesLeftPlusOne--; if (samplesLeftPlusOne === 1) { console.profileEnd('Sample'); } } var timeline; +var hudTimeline; function timelineEnter(name) { timeline && timeline.enter(name); + hudTimeline && hudTimeline.enter(name); } function timelineLeave(name) { timeline && timeline.leave(name); + hudTimeline && hudTimeline.leave(name); } function timelineWrapBroadcastMessage(domain, message) { timelineEnter(message); domain.broadcastMessage(message); timelineLeave(message); } +function initializeHUD(stage, parentCanvas) { + var canvas = document.createElement('canvas'); + var canvasContainer = document.createElement('div'); + canvasContainer.appendChild(canvas); + canvasContainer.style.position = 'absolute'; + canvasContainer.style.top = '0px'; + canvasContainer.style.left = '0px'; + canvasContainer.style.width = '100%'; + canvasContainer.style.height = '150px'; + canvasContainer.style.backgroundColor = 'rgba(0, 0, 0, 0.4)'; + parentCanvas.parentElement.appendChild(canvasContainer); + hudTimeline = new Timeline(canvas); + hudTimeline.setFrameRate(stage._frameRate); + hudTimeline.refreshEvery(10); +} function renderStage(stage, ctx, events) { var frameWidth, frameHeight; + if (!timeline && hud.value) { + initializeHUD(stage, ctx.canvas); + } function updateRenderTransform() { frameWidth = ctx.canvas.width; frameHeight = ctx.canvas.height; var scaleX = frameWidth / stage._stageWidth * 20; var scaleY = frameHeight / stage._stageHeight * 20; switch (stage._scaleMode) { case 'exactFit': break; @@ -4672,32 +5661,19 @@ function renderStage(stage, ctx, events) }); }; } console.timeEnd('Initialize Renderer'); console.timeEnd('Total'); var firstRun = true; var frameCount = 0; var frameFPSAverage = new metrics.Average(120); - (function draw() { - var now = performance.now(); - var renderFrame = now >= nextRenderAt; - if (renderFrame && events.onBeforeFrame) { - var e = { - cancel: false - }; - events.onBeforeFrame(e); - renderFrame = !e.cancel; - } - if (renderFrame && renderDummyBalls) { - frameTime = now; - nextRenderAt = frameTime + maxDelay; - renderDummyBalls(); - requestAnimationFrame(draw); - return; + function drawFrame(renderFrame, frameRequested) { + if (!skipFrameDraw.value) { + frameRequested = true; } sampleStart(); var refreshStage = false; if (stage._invalid) { updateRenderTransform(); stage._invalid = false; refreshStage = true; } @@ -4706,104 +5682,142 @@ function renderStage(stage, ctx, events) stage._mouseMoved = false; mouseMoved = stage._mouseOver; } else { stage._handleMouseButtons(); } if (renderFrame || refreshStage || mouseMoved) { FrameCounter.clear(); var frameStartTime = performance.now(); + timelineEnter('frame'); traceRenderer.value && appendToFrameTerminal('Begin Frame #' + frameCount++, 'purple'); var domain = avm2.systemDomain; if (renderFrame) { - frameTime = now; - maxDelay = 1000 / stage._frameRate; - if (!turboMode.value) { - while (nextRenderAt < now) { - nextRenderAt += maxDelay; - } - } - timelineEnter('EVENTS'); + timelineEnter('events'); if (firstRun) { firstRun = false; } else { enableAdvanceFrame.value && timelineWrapBroadcastMessage(domain, 'advanceFrame'); enableEnterFrame.value && timelineWrapBroadcastMessage(domain, 'enterFrame'); enableConstructChildren.value && timelineWrapBroadcastMessage(domain, 'constructChildren'); } timelineWrapBroadcastMessage(domain, 'frameConstructed'); timelineWrapBroadcastMessage(domain, 'executeFrame'); timelineWrapBroadcastMessage(domain, 'exitFrame'); - timelineLeave('EVENTS'); + timelineLeave('events'); } if (stage._deferRenderEvent) { stage._deferRenderEvent = false; domain.broadcastMessage('render', 'render'); } - if (isCanvasVisible(ctx.canvas) && (refreshStage || renderFrame)) { + if (isCanvasVisible(ctx.canvas) && (refreshStage || renderFrame) && frameRequested) { var invalidPath = null; - if (!disablePreVisitor.value) { - traceRenderer.value && frameWriter.enter('> Pre Visitor'); - timelineEnter('PRE'); - invalidPath = stage._processInvalidRegions(true); - timelineLeave('PRE'); - traceRenderer.value && frameWriter.leave('< Pre Visitor'); - } else { - stage._processInvalidRegions(false); - } - if (!disableRenderVisitor.value) { - timelineEnter('RENDER'); - traceRenderer.value && frameWriter.enter('> Render Visitor'); + traceRenderer.value && frameWriter.enter('> Invalidation'); + timelineEnter('invalidate'); + invalidPath = stage._processInvalidations(refreshStage); + timelineLeave('invalidate'); + traceRenderer.value && frameWriter.leave('< Invalidation'); + if (!disableRenderVisitor.value && !invalidPath.isEmpty) { + timelineEnter('render'); + traceRenderer.value && frameWriter.enter('> Rendering'); new RenderVisitor(stage, ctx, invalidPath, refreshStage).start(); - traceRenderer.value && frameWriter.leave('< Render Visitor'); - timelineLeave('RENDER'); + traceRenderer.value && frameWriter.leave('< Rendering'); + timelineLeave('render'); } if (showQuadTree.value) { ctx.strokeStyle = 'green'; renderQuadTree(ctx, stage._qtree); } if (invalidPath && !refreshStage && showRedrawRegions.value) { ctx.strokeStyle = 'red'; invalidPath.draw(ctx); ctx.stroke(); } } if (mouseMoved && !disableMouseVisitor.value) { - renderFrame && timelineEnter('MOUSE'); - traceRenderer.value && frameWriter.enter('> Mouse Visitor'); + renderFrame && timelineEnter('mouse'); + traceRenderer.value && frameWriter.enter('> Mouse Handling'); stage._handleMouse(); - traceRenderer.value && frameWriter.leave('< Mouse Visitor'); - renderFrame && timelineLeave('MOUSE'); + traceRenderer.value && frameWriter.leave('< Mouse Handling'); + renderFrame && timelineLeave('mouse'); ctx.canvas.style.cursor = stage._cursor; } - if (renderFrame && events.onAfterFrame) { - events.onAfterFrame(); - } if (traceRenderer.value) { frameWriter.enter('> Frame Counters'); for (var name in FrameCounter.counts) { frameWriter.writeLn(name + ': ' + FrameCounter.counts[name]); } frameWriter.leave('< Frame Counters'); var frameElapsedTime = performance.now() - frameStartTime; var frameFPS = 1000 / frameElapsedTime; frameFPSAverage.push(frameFPS); traceRenderer.value && appendToFrameTerminal('End Frame Time: ' + frameElapsedTime.toFixed(2) + ' (' + frameFPS.toFixed(2) + ' fps, ' + frameFPSAverage.average().toFixed(2) + ' average fps)', 'purple'); } + timelineLeave('frame'); } else { traceRenderer.value && appendToFrameTerminal('Skip Frame', 'black'); } sampleEnd(); + } + var frameRequested = true; + var skipNextFrameDraw = false; + (function draw() { + var now = performance.now(); + var renderFrame = true; + if (events.onBeforeFrame) { + var e = { + cancel: false + }; + events.onBeforeFrame(e); + renderFrame = !e.cancel; + } + frameTime = now; + if (renderFrame && renderDummyBalls) { + renderDummyBalls(); + return; + } + drawFrame(renderFrame, frameRequested && !skipNextFrameDraw); + frameRequested = false; + maxDelay = 1000 / stage._frameRate; + if (!turboMode.value) { + nextRenderAt += maxDelay; + var wasLate = false; + while (nextRenderAt < now) { + wasLate = true; + nextRenderAt += maxDelay; + } + if (wasLate && !skipNextFrameDraw) { + skipNextFrameDraw = true; + traceRenderer.value && appendToFrameTerminal('Skip Frame Draw', 'red'); + } else { + skipNextFrameDraw = false; + } + } else { + nextRenderAt = now; + } + if (renderFrame && events.onAfterFrame) { + events.onAfterFrame(); + } if (renderingTerminated) { if (events.onTerminated) { events.onTerminated(); } return; } - requestAnimationFrame(draw); + setTimeout(draw, Math.max(0, nextRenderAt - performance.now())); + }()); + (function frame() { + if (renderingTerminated) { + return; + } + if (stage._invalid || stage._mouseMoved) { + drawFrame(false, true); + } + frameRequested = true; + requestAnimationFrame(frame); }()); } var tagHandler = function (global) { function defineShape($bytes, $stream, $, swfVersion, tagCode) { $ || ($ = {}); $.id = readUi16($bytes, $stream); var $0 = $.bbox = {}; bbox($bytes, $stream, $0, swfVersion, tagCode); @@ -6459,17 +7473,17 @@ BodyParser.prototype = { finalBlock = progressInfo.bytesLoaded >= progressInfo.bytesTotal; } var readStartTime = performance.now(); readTags(swf, stream, swfVersion, finalBlock, options.onprogress); swf.parseTime += performance.now() - readStartTime; var read = stream.pos; buffer.removeHead(read); this.totalRead += read; - if (this.totalRead >= this.length && options.oncomplete) { + if (options.oncomplete && swf.tags[swf.tags.length - 1].finalTag) { options.oncomplete(swf); } } }; SWF.parseAsync = function swf_parseAsync(options) { var buffer = new HeadTailBuffer(); var pipe = { push: function (data, progressInfo) { @@ -6566,16 +7580,17 @@ SWF.parse = function (buffer, options) { options = {}; var pipe = SWF.parseAsync(options); var bytes = new Uint8Array(buffer); var progressInfo = { bytesLoaded: bytes.length, bytesTotal: bytes.length }; pipe.push(bytes, progressInfo); + pipe.close(); }; var $RELEASE = false; var isWorker = typeof window === 'undefined'; if (isWorker && !true) { importScripts.apply(null, [ '../../lib/DataView.js/DataView.js', '../flash/util.js', 'config.js', @@ -8970,23 +9985,39 @@ function popManyInto(src, count, dst) { extendBuiltin(Ap, 'pushUnique', function (v) { for (var i = 0, j = this.length; i < j; i++) { if (this[i] === v) { return; } } this.push(v); }); - extendBuiltin(Ap, 'unique', function () { - var unique = []; - for (var i = 0; i < this.length; i++) { - unique.pushUnique(this[i]); - } - return unique; - }); + var uniquesMap; + if (typeof Map !== 'undefined' && (uniquesMap = new Map()).clear) { + extendBuiltin(Ap, 'unique', function () { + var unique = []; + for (var i = 0; i < this.length; i++) { + if (uniquesMap.has(this[i])) { + continue; + } + unique.push(this[i]); + uniquesMap.set(this[i], true); + } + uniquesMap.clear(); + return unique; + }); + } else { + extendBuiltin(Ap, 'unique', function () { + var unique = []; + for (var i = 0; i < this.length; i++) { + unique.pushUnique(this[i]); + } + return unique; + }); + } extendBuiltin(Ap, 'replace', function (x, y) { if (x === y) { return 0; } var count = 0; for (var i = 0; i < this.length; i++) { if (this[i] === x) { this[i] = y; @@ -9503,17 +10534,17 @@ function base64ArrayBuffer(arrayBuffer) return base64; } var PURPLE = '\x1b[94m'; var YELLOW = '\x1b[93m'; var GREEN = '\x1b[92m'; var RED = '\x1b[91m'; var ENDC = '\x1b[0m'; var IndentingWriter = function () { - var consoleOutFn = console.info.bind(console); + var consoleOutFn = inBrowser ? console.info.bind(console) : print; function indentingWriter(suppressOutput, outFn) { this.tab = ' '; this.padding = ''; this.suppressOutput = suppressOutput; this.out = outFn || consoleOutFn; } indentingWriter.prototype.writeLn = function writeLn(str) { if (!this.suppressOutput) { @@ -10310,34 +11341,47 @@ var Errors = { InvalidEnumError: { code: 2008, message: 'Parameter %1 must be one of the accepted values.' }, ArgumentError: { code: 2015, message: 'Invalid BitmapData.' }, + CompressedDataError: { + code: 2058, + message: 'There was an error decompressing the data.' + }, + SocketConnectError: { + code: 2011, + message: 'Socket connection failed to %1:%2.' + }, CantAddSelfError: { code: 2024, message: 'An object cannot be added as a child of itself.' }, NotAChildError: { code: 2025, message: 'The supplied DisplayObject must be a child of the caller.' + }, + ExternalInterfaceNotAvailableError: { + code: 2067, + message: 'The ExternalInterface is not available in this container. ExternalInterface requires Internet Explorer ActiveX, Firefox, Mozilla 1.7.5 and greater, or other browsers that support NPRuntime.' } }; function getErrorMessage(index) { if (!debuggerMode.value) { return 'Error #' + index; } for (var k in Errors) { if (Errors[k].code == index) { return 'Error #' + index + ': ' + Errors[k].message; } } + return 'Error #' + index + ': (unknown)'; } function formatErrorMessage(error) { var message = error.message; Array.prototype.slice.call(arguments, 1).forEach(function (x, i) { message = message.replace('%' + (i + 1), x); }); return 'Error #' + error.code + ': ' + message; } @@ -12688,16 +13732,17 @@ var ScriptInfo = function scriptInfo() { } }; return scriptInfo; }(); var AbcFile = function () { function abcFile(bytes, name) { Timer.start('Parse ABC'); this.name = name; + this.env = {}; var n, i; var stream = new AbcStream(bytes); checkMagic(stream); Timer.start('Parse constantPool'); this.constantPool = new ConstantPool(stream, name); Timer.stop(); Timer.start('Parse Method Infos'); this.methods = []; @@ -16478,18 +17523,16 @@ var Verifier = function () { return Type.fromName(trait.methodInfo.returnType, domain).instanceType(); } else if (trait.isClass()) { return Type.from(trait.classInfo, domain); } else if (trait.isMethod()) { return Type.from(trait.methodInfo, domain); } } else if (obj.isDirectlyReadable() && mn instanceof Multiname) { ti().propertyQName = Multiname.getPublicQualifiedName(mn.name); - } else if (obj === Type.Object && mn instanceof Multiname) { - ti().propertyQName = Multiname.getPublicQualifiedName(mn.name); } if (isNumericMultiname(mn)) { if (obj.isIndexedReadable()) { ti().isIndexedReadable = true; if (obj.isVector()) { return obj.parameter; } } else if (obj.isDirectlyReadable()) { @@ -16772,16 +17815,18 @@ var Verifier = function () { break; case 87: push(Type.from(new Activation(this.methodInfo))); break; case 88: push(Type.Any); break; case 89: + popMultiname(); + pop(); push(Type.XMLList); break; case 90: push(Type.Any); break; case 93: push(findProperty(popMultiname(), true)); break; @@ -18530,17 +19575,17 @@ var Verifier = function () { }; Node.prototype.toString = function (brief) { if (brief) { return nameOf(this); } var inputs = []; this.visitInputs(function (input) { inputs.push(nameOf(input)); - }, true); + }); var str = nameOf(this) + ' = ' + this.nodeName.toUpperCase(); if (this.toStringDetails) { str += ' ' + this.toStringDetails(); } if (inputs.length) { str += ' ' + inputs.join(', '); } return str; @@ -20736,17 +21781,17 @@ var createName = function createName(nam multiname = buildMultiname(bc.index); object = pop(); push(getSuper(savedScope(), object, multiname, bc.ti)); break; case 5: value = pop(); multiname = buildMultiname(bc.index); object = pop(); - push(setSuper(savedScope(), object, multiname, value, bc.ti)); + setSuper(savedScope(), object, multiname, value, bc.ti); break; case 241: case 240: break; case 64: push(callPure(createFunctionCallee, null, [ constant(methods[bc.index]), topScope(), @@ -23235,17 +24280,17 @@ function executeScript(script) { var abc = script.abc; true; var global = new Global(script); if (abc.applicationDomain.allowNatives) { global[Multiname.getPublicQualifiedName('unsafeJSNative')] = getNative; } script.executing = true; var scope = new Scope(null, script.global); - createFunction(script.init, scope).call(script.global); + createFunction(script.init, scope).call(script.global, false); script.executed = true; } function ensureScriptIsExecuted(script, reason) { if (!script.executed && !script.executing) { if (traceExecution.value >= 2) { print('Executing Script For: ' + reason); } executeScript(script); @@ -24054,17 +25099,17 @@ var Class = function () { if (!buildClass) { unexpected('No native for ' + ci.native.cls); } if (!baseClass) { scope = new Scope(scope, Class); } } var classScope = new Scope(scope, null); - var instanceConstructor = createFunction(ii.init, classScope); + var instanceConstructor = createFunction(ii.init, classScope, false); var cls; if (isNativeClass) { cls = buildClass(domain, classScope, instanceConstructor, baseClass); } else { cls = new Class(className, instanceConstructor); } cls.className = className; cls.classInfo = classInfo; @@ -25306,31 +26351,40 @@ function asCallSuper(scope, namespaces, var method = openMethods[resolved]; var result = method.apply(this, args); traceCallExecution.value > 0 && callWriter.leave('return ' + toSafeString(result)); return result; } function asSetSuper(scope, namespaces, name, flags, value) { if (traceCallExecution.value) { var receiver = this.class ? this.class.className + ' ' : ''; - callWriter.enter('set super ' + receiver + name + '(' + toSafeArrayString(args) + ') #' + callCounter.count(name)); + callWriter.enter('set super ' + receiver + name + '(' + toSafeString(value) + ') #' + callCounter.count(name)); } var baseClass = scope.object.baseClass; var resolved = baseClass.traitsPrototype.resolveMultinameProperty(namespaces, name, flags); - baseClass.traitsPrototype[VM_OPEN_SET_METHOD_PREFIX + resolved].call(this, value); + if (this[VM_SLOTS].byQN[resolved]) { + this.asSetProperty(namespaces, name, flags, value); + } else { + baseClass.traitsPrototype[VM_OPEN_SET_METHOD_PREFIX + resolved].call(this, value); + } traceCallExecution.value > 0 && callWriter.leave(''); } function asGetSuper(scope, namespaces, name, flags) { if (traceCallExecution.value) { var receiver = this.class ? this.class.className + ' ' : ''; callWriter.enter('get super ' + receiver + name + ' #' + callCounter.count(name)); } var baseClass = scope.object.baseClass; var resolved = baseClass.traitsPrototype.resolveMultinameProperty(namespaces, name, flags); - var result = baseClass.traitsPrototype[VM_OPEN_GET_METHOD_PREFIX + resolved].call(this); + var result; + if (this[VM_SLOTS].byQN[resolved]) { + result = this.asGetProperty(namespaces, name, flags); + } else { + result = baseClass.traitsPrototype[VM_OPEN_GET_METHOD_PREFIX + resolved].call(this); + } traceCallExecution.value > 0 && callWriter.leave('return ' + toSafeString(result)); return result; } function construct(constructor, args) { if (constructor.classInfo) { var qn = constructor.classInfo.instanceInfo.name.qualifiedName; if (qn === Multiname.String) { return String.apply(null, args); @@ -26198,17 +27252,17 @@ function getTraitFunction(trait, scope, warning('Calling undefined native method: ' + trait.kindName() + ' ' + mi.holder.name + '::' + Multiname.getQualifiedName(mi.name)); }; }(mi); } } else { if (traceExecution.value >= 2) { print('Creating Function For Trait: ' + trait.holder + ' ' + trait); } - fn = createFunction(mi, scope); + fn = createFunction(mi, scope, false); true; } if (traceExecution.value >= 3) { print('Made Function: ' + Multiname.getQualifiedName(mi.name)); } return fn; } function makeQualifiedNameTraitMap(traits) { @@ -26243,17 +27297,17 @@ function createClass(classInfo, baseClas domain.onMessage.notify1('classCreated', cls); if (cls.instanceConstructor && cls !== Class) { cls.verify(); } if (baseClass && (Multiname.getQualifiedName(baseClass.classInfo.instanceInfo.name.name) === 'Proxy' || baseClass.isProxy)) { installProxyClassWrapper(cls); cls.isProxy = true; } - createFunction(classInfo.init, scope).call(cls); + createFunction(classInfo.init, scope, false).call(cls); if (sealConstTraits) { this.sealConstantTraits(cls, ci.traits); } return cls; } function sealConstantTraits(object, traits) { for (var i = 0, j = traits.length; i < j; i++) { var trait = traits[i]; @@ -26483,16 +27537,23 @@ VM_METHOD_OVERRIDES['com.midasplayer.deb console.log(msg); }; VM_METHOD_OVERRIDES['com.midasplayer.engine.comm.DebugGameComm::getGameData'] = function () { return '<gamedata randomseed="554884453" version="1">\n<musicOn>true</musicOn>\n<soundOn>true</soundOn>\n<isShortGame>false</isShortGame>\n<booster_1>0</booster_1>\n<booster_2>0</booster_2>\n<booster_3>0</booster_3>\n<booster_4>0</booster_4>\n<booster_5>0</booster_5>\n<bestScore>0</bestScore>\n<bestChain>0</bestChain>\n<bestLevel>0</bestLevel>\n<bestCrushed>0</bestCrushed>\n<bestMixed>0</bestMixed>\n<text id="outro.crushed">Candy crushed</text>\n<text id="outro.bestever">best ever</text>\n<text id="outro.trophy.two">scored {0} in one game</text>\n<text id="outro.combo_color_color">All Clear Created</text>\n<text id="outro.trophy.one">crushed {0} candy in one game</text>\n<text id="outro.score">Score</text>\n<text id="outro.opengame">Please register to play the full game</text>\n<text id="outro.chain">Longest chain</text>\n<text id="outro.time">Game ends in {0} seconds</text>\n<text id="outro.combo_color_line">Super Stripes Created</text>\n<text id="game.nomoves">No more moves!</text>\n<text id="outro.combo_wrapper_line">Mega-Candy Created</text>\n<text id="intro.time">Game starts in {0} seconds</text>\n<text id="outro.now">now</text>\n<text id="outro.level">Level reached</text>\n<text id="outro.title">Game Over</text>\n<text id="intro.info1">Match 3 Candy of the same colour to crush them. Matching 4 or 5 in different formations generates special sweets that are extra tasty.</text>\n<text id="intro.info2">You can also combine the special sweets for additional effects by switching them with each other. Try these combinations for a taste you will not forget: </text>\n<text id="outro.combo_color_wrapper">Double Colour Bombs Created</text>\n<text id="outro.trophy.three">made {0} combined candy in one game</text>\n<text id="intro.title">Play like this:</text>\n</gamedata>'; }; VM_METHOD_OVERRIDES['com.antkarlov.Preloader::com.antkarlov:Preloader.isUrl'] = function () { return true; }; +VM_METHOD_OVERRIDES['static com.demonsters.debugger.MonsterDebugger::initialize'] = function () { +}; +VM_METHOD_OVERRIDES['com.spilgames.api.core.tracking.TrackConfig::getTrackers'] = function () { + return []; +}; +VM_METHOD_OVERRIDES['com.spilgames.api.components.TextFields.AutoFitTextFieldEx::com.spilgames.api.components.TextFields:AutoFitTextFieldEx.updateProperties'] = VM_METHOD_OVERRIDES['com.spilgames.api.components.TextFields.AutoFitTextFieldEx::com.spilgames.api.components.TextFields:AutoFitTextFieldEx.updateTextSize'] = function () { +}; function asCheckVectorSetNumericProperty(i, length, fixed) { if (i < 0 || i > length || i === length && fixed || !isNumeric(i)) { throwError('RangeError', Errors.OutOfRangeError, i, length); } } function asCheckVectorGetNumericProperty(i, length) { if (i < 0 || i >= length || !isNumeric(i)) { throwError('RangeError', Errors.OutOfRangeError, i, length); @@ -27688,17 +28749,17 @@ var isXMLType, isXMLName, XMLParser; if (!isWs || isWhitespacePreserved()) { sink.text(resolveEntities(text), isWs); } } i = j; } } this.parseFromString = function (s, mimeType) { - var currentElement = new XML('element'); + var currentElement = new XML('element', '', '', ''); var elementsStack = []; parseXml(s, { beginElement: function (name, attrs, scope, isEmpty) { var parent = currentElement; elementsStack.push(parent); currentElement = createNode('element', name.namespace, name.localName, name.prefix); for (var i = 0; i < attrs.length; ++i) { var attr = createNode('attribute', attrs[i].name.namespace, attrs[i].name.localName, attrs[i].name.prefix); @@ -28460,17 +29521,17 @@ var isXMLType, isXMLName, XMLParser; }, insertChildAfter: function insertChildAfter(child1, child2) { notImplemented('XML.insertChildAfter'); }, insertChildBefore: function insertChildBefore(child1, child2) { notImplemented('XML.insertChildBefore'); }, localName: function localName() { - notImplemented('XML.localName'); + return this.name.localName; }, name: function name() { return this.name; }, _namespace: function _namespace(prefix, argc) { somewhatImplemented('XML._namespace()'); return this.name.uri; }, @@ -30657,232 +31718,36 @@ var natives = function () { return c; } function constant(x) { return function () { return x; }; } function ByteArrayClass(runtime, scope, instanceConstructor, baseClass) { - var INITIAL_SIZE = 128; - var defaultObjectEncoding = 3; - function ByteArray(bytes) { - if (bytes instanceof ByteArray) { - return bytes; - } - var initData = bytes || this.symbol && this.symbol.data; - if (initData) { - this.a = new ArrayBuffer(initData.length); - this.length = initData.length; - new Uint8Array(this.a).set(initData); - } else { - this.a = new ArrayBuffer(INITIAL_SIZE); - this.length = 0; - } - this.position = 0; - this.cacheViews(); - this.nativele = new Int8Array(new Int32Array([]).buffer)[0] === 1; - this.le = this.nativele; - this.objectEncoding = defaultObjectEncoding; - } - function throwEOFError() { - runtime.throwErrorFromVM('flash.errors.EOFError', 'End of file was encountered.'); - } - function throwRangeError() { - var error = Errors.ParamRangeError; - runtime.throwErrorFromVM('RangeError', getErrorMessage(error.code), error.code); - } - function checkRange(x, min, max) { - if (x !== clamp(x, min, max)) { - throwRangeError(); - } - } - function get(b, m, size) { - if (b.position + size > b.length) { - throwEOFError(); - } - var v = b.view[m](b.position, b.le); - b.position += size; - return v; - } - function set(b, m, size, v) { - var len = b.position + size; - b.ensureCapacity(len); - b.view[m](b.position, v, b.le); - b.position = len; - if (len > b.length) { - b.length = len; - } - } - var c = new Class('ByteArray', ByteArray, C(ByteArray)); + var BA = function () { + ByteArray.call(this); + }; + var BAp = BA.prototype = Object.create(ByteArray.prototype); + var c = new Class('ByteArray', BA, C(BA)); c.extendBuiltin(baseClass); - var BAp = ByteArray.prototype; BAp.asGetNumericProperty = function (i) { if (i >= this.length) { return undefined; } return this.uint8v[i]; }; BAp.asSetNumericProperty = function (i, v) { var len = i + 1; this.ensureCapacity(len); this.uint8v[i] = v; if (len > this.length) { this.length = len; } }; - BAp.cacheViews = function cacheViews() { - var a = this.a; - this.int8v = new Int8Array(a); - this.uint8v = new Uint8Array(a); - this.view = new DataView(a); - }; - BAp.getBytes = function getBytes() { - return new Uint8Array(this.a, 0, this.length); - }; - BAp.ensureCapacity = function ensureCapacity(size) { - var origa = this.a; - if (origa.byteLength < size) { - var newSize = origa.byteLength; - while (newSize < size) { - newSize *= 2; - } - var copya = new ArrayBuffer(newSize); - var origv = this.int8v; - this.a = copya; - this.cacheViews(); - this.int8v.set(origv); - } - }; - BAp.clear = function clear() { - this.length = 0; - this.position = 0; - }; - BAp.readBoolean = function readBoolean() { - if (this.position + 1 > this.length) { - throwEOFError(); - } - return this.int8v[this.position++] !== 0; - }; - BAp.readByte = function readByte() { - if (this.position + 1 > this.length) { - throwEOFError(); - } - return this.int8v[this.position++]; - }; - BAp.readUnsignedByte = function readUnsignedByte() { - if (this.position + 1 > this.length) { - throwEOFError(); - } - return this.uint8v[this.position++]; - }; - BAp.readBytes = function readBytes(bytes, offset, length) { - var pos = this.position; - if (pos + length > this.length) { - throwEOFError(); - } - if (bytes.length < offset + length) { - bytes.ensureCapacity(offset + length); - bytes.length = offset + length; - } - bytes.int8v.set(new Int8Array(this.a, pos, length), offset); - this.position += length; - }; - BAp.writeBoolean = function writeBoolean(v) { - var len = this.position + 1; - this.ensureCapacity(len); - this.int8v[this.position++] = v ? 1 : 0; - if (len > this.length) { - this.length = len; - } - }; - BAp.writeByte = function writeByte(v) { - var len = this.position + 1; - this.ensureCapacity(len); - this.int8v[this.position++] = v; - if (len > this.length) { - this.length = len; - } - }; - BAp.writeUnsignedByte = function writeUnsignedByte(v) { - var len = this.position + 1; - this.ensureCapacity(len); - this.uint8v[this.position++] = v; - if (len > this.length) { - this.length = len; - } - }; - BAp.writeRawBytes = function writeRawBytes(bytes) { - var len = this.position + bytes.length; - this.ensureCapacity(len); - this.int8v.set(bytes, this.position); - this.position = len; - if (len > this.length) { - this.length = len; - } - }; - BAp.readRawBytes = function readRawBytes() { - return new Int8Array(this.a, 0, this.length); - }; - BAp.writeBytes = function writeBytes(bytes, offset, length) { - if (arguments.length < 2) { - offset = 0; - } - if (arguments.length < 3) { - length = 0; - } - checkRange(offset, 0, bytes.length); - checkRange(offset + length, 0, bytes.length); - if (length === 0) { - length = bytes.length - offset; - } - this.writeRawBytes(new Int8Array(bytes.a, offset, length)); - }; - BAp.readDouble = function readDouble() { - return get(this, 'getFloat64', 8); - }; - BAp.readFloat = function readFloat() { - return get(this, 'getFloat32', 4); - }; - BAp.readInt = function readInt() { - return get(this, 'getInt32', 4); - }; - BAp.readShort = function readShort() { - return get(this, 'getInt16', 2); - }; - BAp.readUnsignedInt = function readUnsignedInt() { - return get(this, 'getUint32', 4); - }; - BAp.readUnsignedShort = function readUnsignedShort() { - return get(this, 'getUint16', 2); - }; - BAp.readObject = function readObject() { - return AMFUtils.encodings[this.objectEncoding].read(this); - }; - BAp.writeDouble = function writeDouble(v) { - set(this, 'setFloat64', 8, v); - }; - BAp.writeFloat = function writeFloat(v) { - set(this, 'setFloat32', 4, v); - }; - BAp.writeInt = function writeInt(v) { - set(this, 'setInt32', 4, v); - }; - BAp.writeShort = function writeShort(v) { - set(this, 'setInt16', 2, v); - }; - BAp.writeUnsignedInt = function writeUnsignedInt(v) { - set(this, 'setUint32', 4, v); - }; - BAp.writeUnsignedShort = function writeUnsignedShort(v) { - set(this, 'setUint16', 2, v); - }; - BAp.writeObject = function readObject(v) { - return AMFUtils.encodings[this.objectEncoding].write(this, v); - }; BAp.readUTF = function readUTF() { return this.readUTFBytes(this.readShort()); }; BAp.readUTFBytes = function readUTFBytes(length) { var pos = this.position; if (pos + length > this.length) { throwEOFError(); } @@ -30940,52 +31805,58 @@ var natives = function () { objectEncoding: { get: function () { return this.objectEncoding; }, set: function (v) { this.objectEncoding = v; } }, - readBytes: BAp.readBytes, writeBytes: BAp.writeBytes, writeBoolean: BAp.writeBoolean, writeByte: BAp.writeByte, writeShort: BAp.writeShort, writeInt: BAp.writeInt, writeUnsignedInt: BAp.writeUnsignedInt, writeFloat: BAp.writeFloat, writeDouble: BAp.writeDouble, writeMultiByte: BAp.writeMultiByte, + writeObject: function writeObject(v) { + return AMFUtils.encodings[this.objectEncoding].write(this, v); + }, writeUTF: BAp.writeUTF,