Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | /* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
michael@0 | 2 | /* vim: set ft=javascript ts=2 et sw=2 tw=80: */ |
michael@0 | 3 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 6 | |
michael@0 | 7 | "use strict"; |
michael@0 | 8 | |
michael@0 | 9 | const Cu = Components.utils; |
michael@0 | 10 | const Ci = Components.interfaces; |
michael@0 | 11 | const Cc = Components.classes; |
michael@0 | 12 | |
michael@0 | 13 | Cu.import("resource://gre/modules/Services.jsm"); |
michael@0 | 14 | Cu.import("resource://gre/modules/Task.jsm"); |
michael@0 | 15 | Cu.import("resource://gre/modules/devtools/Loader.jsm"); |
michael@0 | 16 | Cu.import("resource://gre/modules/devtools/Console.jsm"); |
michael@0 | 17 | |
michael@0 | 18 | const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {}); |
michael@0 | 19 | const {InplaceEditor, editableItem} = devtools.require("devtools/shared/inplace-editor"); |
michael@0 | 20 | const {parseDeclarations} = devtools.require("devtools/styleinspector/css-parsing-utils"); |
michael@0 | 21 | |
michael@0 | 22 | const NUMERIC = /^-?[\d\.]+$/; |
michael@0 | 23 | const LONG_TEXT_ROTATE_LIMIT = 3; |
michael@0 | 24 | |
michael@0 | 25 | /** |
michael@0 | 26 | * An instance of EditingSession tracks changes that have been made during the |
michael@0 | 27 | * modification of box model values. All of these changes can be reverted by |
michael@0 | 28 | * calling revert. |
michael@0 | 29 | * |
michael@0 | 30 | * @param doc A DOM document that can be used to test style rules. |
michael@0 | 31 | * @param rules An array of the style rules defined for the node being edited. |
michael@0 | 32 | * These should be in order of priority, least important first. |
michael@0 | 33 | */ |
michael@0 | 34 | function EditingSession(doc, rules) { |
michael@0 | 35 | this._doc = doc; |
michael@0 | 36 | this._rules = rules; |
michael@0 | 37 | this._modifications = new Map(); |
michael@0 | 38 | } |
michael@0 | 39 | |
michael@0 | 40 | EditingSession.prototype = { |
michael@0 | 41 | /** |
michael@0 | 42 | * Gets the value of a single property from the CSS rule. |
michael@0 | 43 | * |
michael@0 | 44 | * @param rule The CSS rule |
michael@0 | 45 | * @param property The name of the property |
michael@0 | 46 | */ |
michael@0 | 47 | getPropertyFromRule: function(rule, property) { |
michael@0 | 48 | let dummyStyle = this._element.style; |
michael@0 | 49 | |
michael@0 | 50 | dummyStyle.cssText = rule.cssText; |
michael@0 | 51 | return dummyStyle.getPropertyValue(property); |
michael@0 | 52 | }, |
michael@0 | 53 | |
michael@0 | 54 | /** |
michael@0 | 55 | * Returns the current value for a property as a string or the empty string if |
michael@0 | 56 | * no style rules affect the property. |
michael@0 | 57 | * |
michael@0 | 58 | * @param property The name of the property as a string |
michael@0 | 59 | */ |
michael@0 | 60 | getProperty: function(property) { |
michael@0 | 61 | // Create a hidden element for getPropertyFromRule to use |
michael@0 | 62 | let div = this._doc.createElement("div"); |
michael@0 | 63 | div.setAttribute("style", "display: none"); |
michael@0 | 64 | this._doc.body.appendChild(div); |
michael@0 | 65 | this._element = this._doc.createElement("p"); |
michael@0 | 66 | div.appendChild(this._element); |
michael@0 | 67 | |
michael@0 | 68 | // As the rules are in order of priority we can just iterate until we find |
michael@0 | 69 | // the first that defines a value for the property and return that. |
michael@0 | 70 | for (let rule of this._rules) { |
michael@0 | 71 | let value = this.getPropertyFromRule(rule, property); |
michael@0 | 72 | if (value !== "") { |
michael@0 | 73 | div.remove(); |
michael@0 | 74 | return value; |
michael@0 | 75 | } |
michael@0 | 76 | } |
michael@0 | 77 | div.remove(); |
michael@0 | 78 | return ""; |
michael@0 | 79 | }, |
michael@0 | 80 | |
michael@0 | 81 | /** |
michael@0 | 82 | * Sets a number of properties on the node. Returns a promise that will be |
michael@0 | 83 | * resolved when the modifications are complete. |
michael@0 | 84 | * |
michael@0 | 85 | * @param properties An array of properties, each is an object with name and |
michael@0 | 86 | * value properties. If the value is "" then the property |
michael@0 | 87 | * is removed. |
michael@0 | 88 | */ |
michael@0 | 89 | setProperties: function(properties) { |
michael@0 | 90 | let modifications = this._rules[0].startModifyingProperties(); |
michael@0 | 91 | |
michael@0 | 92 | for (let property of properties) { |
michael@0 | 93 | if (!this._modifications.has(property.name)) |
michael@0 | 94 | this._modifications.set(property.name, this.getPropertyFromRule(this._rules[0], property.name)); |
michael@0 | 95 | |
michael@0 | 96 | if (property.value == "") |
michael@0 | 97 | modifications.removeProperty(property.name); |
michael@0 | 98 | else |
michael@0 | 99 | modifications.setProperty(property.name, property.value, ""); |
michael@0 | 100 | } |
michael@0 | 101 | |
michael@0 | 102 | return modifications.apply().then(null, console.error); |
michael@0 | 103 | }, |
michael@0 | 104 | |
michael@0 | 105 | /** |
michael@0 | 106 | * Reverts all of the property changes made by this instance. Returns a |
michael@0 | 107 | * promise that will be resolved when complete. |
michael@0 | 108 | */ |
michael@0 | 109 | revert: function() { |
michael@0 | 110 | let modifications = this._rules[0].startModifyingProperties(); |
michael@0 | 111 | |
michael@0 | 112 | for (let [property, value] of this._modifications) { |
michael@0 | 113 | if (value != "") |
michael@0 | 114 | modifications.setProperty(property, value, ""); |
michael@0 | 115 | else |
michael@0 | 116 | modifications.removeProperty(property); |
michael@0 | 117 | } |
michael@0 | 118 | |
michael@0 | 119 | return modifications.apply().then(null, console.error); |
michael@0 | 120 | } |
michael@0 | 121 | }; |
michael@0 | 122 | |
michael@0 | 123 | function LayoutView(aInspector, aWindow) |
michael@0 | 124 | { |
michael@0 | 125 | this.inspector = aInspector; |
michael@0 | 126 | |
michael@0 | 127 | // <browser> is not always available (for Chrome targets for example) |
michael@0 | 128 | if (this.inspector.target.tab) { |
michael@0 | 129 | this.browser = aInspector.target.tab.linkedBrowser; |
michael@0 | 130 | } |
michael@0 | 131 | |
michael@0 | 132 | this.doc = aWindow.document; |
michael@0 | 133 | this.sizeLabel = this.doc.querySelector(".size > span"); |
michael@0 | 134 | this.sizeHeadingLabel = this.doc.getElementById("element-size"); |
michael@0 | 135 | |
michael@0 | 136 | this.init(); |
michael@0 | 137 | } |
michael@0 | 138 | |
michael@0 | 139 | LayoutView.prototype = { |
michael@0 | 140 | init: function LV_init() { |
michael@0 | 141 | this.update = this.update.bind(this); |
michael@0 | 142 | this.onNewNode = this.onNewNode.bind(this); |
michael@0 | 143 | this.onNewSelection = this.onNewSelection.bind(this); |
michael@0 | 144 | this.inspector.selection.on("new-node-front", this.onNewSelection); |
michael@0 | 145 | this.inspector.sidebar.on("layoutview-selected", this.onNewNode); |
michael@0 | 146 | |
michael@0 | 147 | // Store for the different dimensions of the node. |
michael@0 | 148 | // 'selector' refers to the element that holds the value in view.xhtml; |
michael@0 | 149 | // 'property' is what we are measuring; |
michael@0 | 150 | // 'value' is the computed dimension, computed in update(). |
michael@0 | 151 | this.map = { |
michael@0 | 152 | position: {selector: "#element-position", |
michael@0 | 153 | property: "position", |
michael@0 | 154 | value: undefined}, |
michael@0 | 155 | marginTop: {selector: ".margin.top > span", |
michael@0 | 156 | property: "margin-top", |
michael@0 | 157 | value: undefined}, |
michael@0 | 158 | marginBottom: {selector: ".margin.bottom > span", |
michael@0 | 159 | property: "margin-bottom", |
michael@0 | 160 | value: undefined}, |
michael@0 | 161 | // margin-left is a shorthand for some internal properties, |
michael@0 | 162 | // margin-left-ltr-source and margin-left-rtl-source for example. The |
michael@0 | 163 | // real margin value we want is in margin-left-value |
michael@0 | 164 | marginLeft: {selector: ".margin.left > span", |
michael@0 | 165 | property: "margin-left", |
michael@0 | 166 | realProperty: "margin-left-value", |
michael@0 | 167 | value: undefined}, |
michael@0 | 168 | // margin-right behaves the same as margin-left |
michael@0 | 169 | marginRight: {selector: ".margin.right > span", |
michael@0 | 170 | property: "margin-right", |
michael@0 | 171 | realProperty: "margin-right-value", |
michael@0 | 172 | value: undefined}, |
michael@0 | 173 | paddingTop: {selector: ".padding.top > span", |
michael@0 | 174 | property: "padding-top", |
michael@0 | 175 | value: undefined}, |
michael@0 | 176 | paddingBottom: {selector: ".padding.bottom > span", |
michael@0 | 177 | property: "padding-bottom", |
michael@0 | 178 | value: undefined}, |
michael@0 | 179 | // padding-left behaves the same as margin-left |
michael@0 | 180 | paddingLeft: {selector: ".padding.left > span", |
michael@0 | 181 | property: "padding-left", |
michael@0 | 182 | realProperty: "padding-left-value", |
michael@0 | 183 | value: undefined}, |
michael@0 | 184 | // padding-right behaves the same as margin-left |
michael@0 | 185 | paddingRight: {selector: ".padding.right > span", |
michael@0 | 186 | property: "padding-right", |
michael@0 | 187 | realProperty: "padding-right-value", |
michael@0 | 188 | value: undefined}, |
michael@0 | 189 | borderTop: {selector: ".border.top > span", |
michael@0 | 190 | property: "border-top-width", |
michael@0 | 191 | value: undefined}, |
michael@0 | 192 | borderBottom: {selector: ".border.bottom > span", |
michael@0 | 193 | property: "border-bottom-width", |
michael@0 | 194 | value: undefined}, |
michael@0 | 195 | borderLeft: {selector: ".border.left > span", |
michael@0 | 196 | property: "border-left-width", |
michael@0 | 197 | value: undefined}, |
michael@0 | 198 | borderRight: {selector: ".border.right > span", |
michael@0 | 199 | property: "border-right-width", |
michael@0 | 200 | value: undefined}, |
michael@0 | 201 | }; |
michael@0 | 202 | |
michael@0 | 203 | // Make each element the dimensions editable |
michael@0 | 204 | for (let i in this.map) { |
michael@0 | 205 | if (i == "position") |
michael@0 | 206 | continue; |
michael@0 | 207 | |
michael@0 | 208 | let dimension = this.map[i]; |
michael@0 | 209 | editableItem({ element: this.doc.querySelector(dimension.selector) }, (element, event) => { |
michael@0 | 210 | this.initEditor(element, event, dimension); |
michael@0 | 211 | }); |
michael@0 | 212 | } |
michael@0 | 213 | |
michael@0 | 214 | this.onNewNode(); |
michael@0 | 215 | }, |
michael@0 | 216 | |
michael@0 | 217 | /** |
michael@0 | 218 | * Called when the user clicks on one of the editable values in the layoutview |
michael@0 | 219 | */ |
michael@0 | 220 | initEditor: function LV_initEditor(element, event, dimension) { |
michael@0 | 221 | let { property, realProperty } = dimension; |
michael@0 | 222 | if (!realProperty) |
michael@0 | 223 | realProperty = property; |
michael@0 | 224 | let session = new EditingSession(document, this.elementRules); |
michael@0 | 225 | let initialValue = session.getProperty(realProperty); |
michael@0 | 226 | |
michael@0 | 227 | let editor = new InplaceEditor({ |
michael@0 | 228 | element: element, |
michael@0 | 229 | initial: initialValue, |
michael@0 | 230 | |
michael@0 | 231 | start: (editor) => { |
michael@0 | 232 | editor.elt.parentNode.classList.add("editing"); |
michael@0 | 233 | }, |
michael@0 | 234 | |
michael@0 | 235 | change: (value) => { |
michael@0 | 236 | if (NUMERIC.test(value)) |
michael@0 | 237 | value += "px"; |
michael@0 | 238 | let properties = [ |
michael@0 | 239 | { name: property, value: value } |
michael@0 | 240 | ] |
michael@0 | 241 | |
michael@0 | 242 | if (property.substring(0, 7) == "border-") { |
michael@0 | 243 | let bprop = property.substring(0, property.length - 5) + "style"; |
michael@0 | 244 | let style = session.getProperty(bprop); |
michael@0 | 245 | if (!style || style == "none" || style == "hidden") |
michael@0 | 246 | properties.push({ name: bprop, value: "solid" }); |
michael@0 | 247 | } |
michael@0 | 248 | |
michael@0 | 249 | session.setProperties(properties); |
michael@0 | 250 | }, |
michael@0 | 251 | |
michael@0 | 252 | done: (value, commit) => { |
michael@0 | 253 | editor.elt.parentNode.classList.remove("editing"); |
michael@0 | 254 | if (!commit) |
michael@0 | 255 | session.revert(); |
michael@0 | 256 | } |
michael@0 | 257 | }, event); |
michael@0 | 258 | }, |
michael@0 | 259 | |
michael@0 | 260 | /** |
michael@0 | 261 | * Is the layoutview visible in the sidebar? |
michael@0 | 262 | */ |
michael@0 | 263 | isActive: function LV_isActive() { |
michael@0 | 264 | return this.inspector.sidebar.getCurrentTabID() == "layoutview"; |
michael@0 | 265 | }, |
michael@0 | 266 | |
michael@0 | 267 | /** |
michael@0 | 268 | * Destroy the nodes. Remove listeners. |
michael@0 | 269 | */ |
michael@0 | 270 | destroy: function LV_destroy() { |
michael@0 | 271 | this.inspector.sidebar.off("layoutview-selected", this.onNewNode); |
michael@0 | 272 | this.inspector.selection.off("new-node-front", this.onNewSelection); |
michael@0 | 273 | if (this.browser) { |
michael@0 | 274 | this.browser.removeEventListener("MozAfterPaint", this.update, true); |
michael@0 | 275 | } |
michael@0 | 276 | this.sizeHeadingLabel = null; |
michael@0 | 277 | this.sizeLabel = null; |
michael@0 | 278 | this.inspector = null; |
michael@0 | 279 | this.doc = null; |
michael@0 | 280 | }, |
michael@0 | 281 | |
michael@0 | 282 | /** |
michael@0 | 283 | * Selection 'new-node-front' event handler. |
michael@0 | 284 | */ |
michael@0 | 285 | onNewSelection: function() { |
michael@0 | 286 | let done = this.inspector.updating("layoutview"); |
michael@0 | 287 | this.onNewNode().then(done, (err) => { console.error(err); done() }); |
michael@0 | 288 | }, |
michael@0 | 289 | |
michael@0 | 290 | onNewNode: function LV_onNewNode() { |
michael@0 | 291 | if (this.isActive() && |
michael@0 | 292 | this.inspector.selection.isConnected() && |
michael@0 | 293 | this.inspector.selection.isElementNode()) { |
michael@0 | 294 | this.undim(); |
michael@0 | 295 | } else { |
michael@0 | 296 | this.dim(); |
michael@0 | 297 | } |
michael@0 | 298 | return this.update(); |
michael@0 | 299 | }, |
michael@0 | 300 | |
michael@0 | 301 | /** |
michael@0 | 302 | * Hide the layout boxes. No node are selected. |
michael@0 | 303 | */ |
michael@0 | 304 | dim: function LV_dim() { |
michael@0 | 305 | if (this.browser) { |
michael@0 | 306 | this.browser.removeEventListener("MozAfterPaint", this.update, true); |
michael@0 | 307 | } |
michael@0 | 308 | this.trackingPaint = false; |
michael@0 | 309 | this.doc.body.classList.add("dim"); |
michael@0 | 310 | this.dimmed = true; |
michael@0 | 311 | }, |
michael@0 | 312 | |
michael@0 | 313 | /** |
michael@0 | 314 | * Show the layout boxes. A node is selected. |
michael@0 | 315 | */ |
michael@0 | 316 | undim: function LV_undim() { |
michael@0 | 317 | if (!this.trackingPaint) { |
michael@0 | 318 | if (this.browser) { |
michael@0 | 319 | this.browser.addEventListener("MozAfterPaint", this.update, true); |
michael@0 | 320 | } |
michael@0 | 321 | this.trackingPaint = true; |
michael@0 | 322 | } |
michael@0 | 323 | this.doc.body.classList.remove("dim"); |
michael@0 | 324 | this.dimmed = false; |
michael@0 | 325 | }, |
michael@0 | 326 | |
michael@0 | 327 | /** |
michael@0 | 328 | * Compute the dimensions of the node and update the values in |
michael@0 | 329 | * the layoutview/view.xhtml document. Returns a promise that will be resolved |
michael@0 | 330 | * when complete. |
michael@0 | 331 | */ |
michael@0 | 332 | update: function LV_update() { |
michael@0 | 333 | let lastRequest = Task.spawn((function*() { |
michael@0 | 334 | if (!this.isActive() || |
michael@0 | 335 | !this.inspector.selection.isConnected() || |
michael@0 | 336 | !this.inspector.selection.isElementNode()) { |
michael@0 | 337 | return; |
michael@0 | 338 | } |
michael@0 | 339 | |
michael@0 | 340 | let node = this.inspector.selection.nodeFront; |
michael@0 | 341 | let layout = yield this.inspector.pageStyle.getLayout(node, { |
michael@0 | 342 | autoMargins: !this.dimmed |
michael@0 | 343 | }); |
michael@0 | 344 | let styleEntries = yield this.inspector.pageStyle.getApplied(node, {}); |
michael@0 | 345 | |
michael@0 | 346 | // If a subsequent request has been made, wait for that one instead. |
michael@0 | 347 | if (this._lastRequest != lastRequest) { |
michael@0 | 348 | return this._lastRequest; |
michael@0 | 349 | } |
michael@0 | 350 | |
michael@0 | 351 | this._lastRequest = null; |
michael@0 | 352 | let width = layout.width; |
michael@0 | 353 | let height = layout.height; |
michael@0 | 354 | let newLabel = width + "x" + height; |
michael@0 | 355 | if (this.sizeHeadingLabel.textContent != newLabel) { |
michael@0 | 356 | this.sizeHeadingLabel.textContent = newLabel; |
michael@0 | 357 | } |
michael@0 | 358 | |
michael@0 | 359 | // If the view is dimmed, no need to do anything more. |
michael@0 | 360 | if (this.dimmed) { |
michael@0 | 361 | this.inspector.emit("layoutview-updated"); |
michael@0 | 362 | return null; |
michael@0 | 363 | } |
michael@0 | 364 | |
michael@0 | 365 | for (let i in this.map) { |
michael@0 | 366 | let property = this.map[i].property; |
michael@0 | 367 | if (!(property in layout)) { |
michael@0 | 368 | // Depending on the actor version, some properties |
michael@0 | 369 | // might be missing. |
michael@0 | 370 | continue; |
michael@0 | 371 | } |
michael@0 | 372 | let parsedValue = parseInt(layout[property]); |
michael@0 | 373 | if (Number.isNaN(parsedValue)) { |
michael@0 | 374 | // Not a number. We use the raw string. |
michael@0 | 375 | // Useful for "position" for example. |
michael@0 | 376 | this.map[i].value = layout[property]; |
michael@0 | 377 | } else { |
michael@0 | 378 | this.map[i].value = parsedValue; |
michael@0 | 379 | } |
michael@0 | 380 | } |
michael@0 | 381 | |
michael@0 | 382 | let margins = layout.autoMargins; |
michael@0 | 383 | if ("top" in margins) this.map.marginTop.value = "auto"; |
michael@0 | 384 | if ("right" in margins) this.map.marginRight.value = "auto"; |
michael@0 | 385 | if ("bottom" in margins) this.map.marginBottom.value = "auto"; |
michael@0 | 386 | if ("left" in margins) this.map.marginLeft.value = "auto"; |
michael@0 | 387 | |
michael@0 | 388 | for (let i in this.map) { |
michael@0 | 389 | let selector = this.map[i].selector; |
michael@0 | 390 | let span = this.doc.querySelector(selector); |
michael@0 | 391 | if (span.textContent.length > 0 && |
michael@0 | 392 | span.textContent == this.map[i].value) { |
michael@0 | 393 | continue; |
michael@0 | 394 | } |
michael@0 | 395 | span.textContent = this.map[i].value; |
michael@0 | 396 | this.manageOverflowingText(span); |
michael@0 | 397 | } |
michael@0 | 398 | |
michael@0 | 399 | width -= this.map.borderLeft.value + this.map.borderRight.value + |
michael@0 | 400 | this.map.paddingLeft.value + this.map.paddingRight.value; |
michael@0 | 401 | |
michael@0 | 402 | height -= this.map.borderTop.value + this.map.borderBottom.value + |
michael@0 | 403 | this.map.paddingTop.value + this.map.paddingBottom.value; |
michael@0 | 404 | |
michael@0 | 405 | let newValue = width + "x" + height; |
michael@0 | 406 | if (this.sizeLabel.textContent != newValue) { |
michael@0 | 407 | this.sizeLabel.textContent = newValue; |
michael@0 | 408 | } |
michael@0 | 409 | |
michael@0 | 410 | this.elementRules = [e.rule for (e of styleEntries)]; |
michael@0 | 411 | |
michael@0 | 412 | this.inspector.emit("layoutview-updated"); |
michael@0 | 413 | }).bind(this)).then(null, console.error); |
michael@0 | 414 | |
michael@0 | 415 | return this._lastRequest = lastRequest; |
michael@0 | 416 | }, |
michael@0 | 417 | |
michael@0 | 418 | showBoxModel: function(options={}) { |
michael@0 | 419 | let toolbox = this.inspector.toolbox; |
michael@0 | 420 | let nodeFront = this.inspector.selection.nodeFront; |
michael@0 | 421 | |
michael@0 | 422 | toolbox.highlighterUtils.highlightNodeFront(nodeFront, options); |
michael@0 | 423 | }, |
michael@0 | 424 | |
michael@0 | 425 | hideBoxModel: function() { |
michael@0 | 426 | let toolbox = this.inspector.toolbox; |
michael@0 | 427 | |
michael@0 | 428 | toolbox.highlighterUtils.unhighlight(); |
michael@0 | 429 | }, |
michael@0 | 430 | |
michael@0 | 431 | manageOverflowingText: function(span) { |
michael@0 | 432 | let classList = span.parentNode.classList; |
michael@0 | 433 | |
michael@0 | 434 | if (classList.contains("left") || classList.contains("right")) { |
michael@0 | 435 | let force = span.textContent.length > LONG_TEXT_ROTATE_LIMIT; |
michael@0 | 436 | classList.toggle("rotate", force); |
michael@0 | 437 | } |
michael@0 | 438 | } |
michael@0 | 439 | }; |
michael@0 | 440 | |
michael@0 | 441 | let elts; |
michael@0 | 442 | let tooltip; |
michael@0 | 443 | |
michael@0 | 444 | let onmouseover = function(e) { |
michael@0 | 445 | let region = e.target.getAttribute("data-box"); |
michael@0 | 446 | |
michael@0 | 447 | tooltip.textContent = e.target.getAttribute("tooltip"); |
michael@0 | 448 | this.layoutview.showBoxModel({region: region}); |
michael@0 | 449 | |
michael@0 | 450 | return false; |
michael@0 | 451 | }.bind(window); |
michael@0 | 452 | |
michael@0 | 453 | let onmouseout = function(e) { |
michael@0 | 454 | tooltip.textContent = ""; |
michael@0 | 455 | this.layoutview.hideBoxModel(); |
michael@0 | 456 | |
michael@0 | 457 | return false; |
michael@0 | 458 | }.bind(window); |
michael@0 | 459 | |
michael@0 | 460 | window.setPanel = function(panel) { |
michael@0 | 461 | this.layoutview = new LayoutView(panel, window); |
michael@0 | 462 | |
michael@0 | 463 | // Tooltip mechanism |
michael@0 | 464 | elts = document.querySelectorAll("*[tooltip]"); |
michael@0 | 465 | tooltip = document.querySelector(".tooltip"); |
michael@0 | 466 | for (let i = 0; i < elts.length; i++) { |
michael@0 | 467 | let elt = elts[i]; |
michael@0 | 468 | elt.addEventListener("mouseover", onmouseover, true); |
michael@0 | 469 | elt.addEventListener("mouseout", onmouseout, true); |
michael@0 | 470 | } |
michael@0 | 471 | |
michael@0 | 472 | // Mark document as RTL or LTR: |
michael@0 | 473 | let chromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"]. |
michael@0 | 474 | getService(Ci.nsIXULChromeRegistry); |
michael@0 | 475 | let dir = chromeReg.isLocaleRTL("global"); |
michael@0 | 476 | document.body.setAttribute("dir", dir ? "rtl" : "ltr"); |
michael@0 | 477 | |
michael@0 | 478 | window.parent.postMessage("layoutview-ready", "*"); |
michael@0 | 479 | }; |
michael@0 | 480 | |
michael@0 | 481 | window.onunload = function() { |
michael@0 | 482 | this.layoutview.destroy(); |
michael@0 | 483 | if (elts) { |
michael@0 | 484 | for (let i = 0; i < elts.length; i++) { |
michael@0 | 485 | let elt = elts[i]; |
michael@0 | 486 | elt.removeEventListener("mouseover", onmouseover, true); |
michael@0 | 487 | elt.removeEventListener("mouseout", onmouseout, true); |
michael@0 | 488 | } |
michael@0 | 489 | } |
michael@0 | 490 | }; |