├── .gitignore ├── .jpmignore ├── .jshintrc ├── .travis.yml ├── README.md ├── chrome.manifest ├── chrome ├── locale │ ├── de │ │ ├── notification.properties │ │ ├── popup-panel.properties │ │ └── toolbox.properties │ ├── en-US │ │ ├── notification.properties │ │ ├── popup-panel.properties │ │ └── toolbox.properties │ └── zh-CN │ │ ├── notification.properties │ │ ├── popup-panel.properties │ │ └── toolbox.properties └── skin │ └── classic │ └── shared │ ├── logo_16x16.png │ ├── logo_32x32.png │ ├── logo_64x64.png │ ├── resizer.png │ └── ua.css ├── data ├── config.js ├── img │ ├── close.svg │ └── slider.png ├── layer-form.js ├── layer-list.js ├── layer-store.js ├── lib │ ├── bootstrap │ │ └── css │ │ │ └── bootstrap.css │ ├── react │ │ └── react.js │ └── requirejs │ │ └── require.js ├── notification-config.js ├── notification-content.js ├── notification.html ├── notification.js ├── popup-frame-script.js ├── popup-panel.js ├── popup.css ├── popup.html └── popup.js ├── docs └── images │ ├── empty-panel.png │ ├── firebug-integration.png │ ├── layer-form-page.png │ ├── layer-form-panel.png │ ├── popup-panel.png │ └── start-button.png ├── index.js ├── lib ├── first-run.js ├── main.js ├── pixel-perfect-actor.js ├── pixel-perfect-front.js ├── pixel-perfect-popup.js ├── pixel-perfect-store.js ├── pixel-perfect-toolbox-overlay.js ├── start-button.js └── style-editor-overlay.js ├── license.txt ├── package.json └── test ├── common.js ├── frame-script.js ├── test-add-layer.js ├── test-layer-invert.js ├── test-layer-lock.js ├── test-layer-opacity.js ├── test-layer-position.js ├── test-layer-scale.js ├── test-layer-visibility.js ├── test-remove-layer.js ├── test-start-button.js └── test-toggle-popup.js /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Junk that could exist anywhere: 3 | .DS_Store 4 | *.swp 5 | *.tmp 6 | .*.gz 7 | *.patch 8 | *~ 9 | 10 | # Temporary files created by Eclipse 11 | .tmp* 12 | 13 | # Editor junk 14 | *.project 15 | /.pydevproject 16 | /.settings/ 17 | /.settings.xml 18 | /.settings.xml.old 19 | /.idea/ 20 | *.iws 21 | *.ids 22 | *.iml 23 | *.ipr 24 | 25 | # Build Files 26 | /build/ 27 | /release/ 28 | *.graphml 29 | *.xpi 30 | 31 | # Files from NPM 32 | /node_modules/ 33 | 34 | # Extensions 35 | /firebug@software.joehewitt.com 36 | 37 | # Bash 38 | *.sh 39 | *.bat 40 | -------------------------------------------------------------------------------- /.jpmignore: -------------------------------------------------------------------------------- 1 | # Doc Files 2 | /docs/ 3 | 4 | # Test Files 5 | /test/ 6 | 7 | # GIT 8 | /.git/ 9 | 10 | # Other files 11 | .gitignore 12 | .jpmignore 13 | .jshintrc 14 | .project 15 | 16 | # Existing packages 17 | *.xpi 18 | 19 | # Travis 20 | .travis.yml 21 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esnext": "true", 3 | "predef": [ "require", "exports", "module" ], 4 | "curly": "true" 5 | } 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "0.10" 5 | 6 | before_install: 7 | - "export DISPLAY=:99.0" 8 | - "sh -e /etc/init.d/xvfb start" 9 | - "/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16 -extension RANDR" 10 | 11 | before_script: 12 | - npm install jpm -g 13 | - npm install mozilla-download -g 14 | - cd .. 15 | - mozilla-download --branch fx-team --product firefox $TRAVIS_BUILD_DIR/../ 16 | - cd $TRAVIS_BUILD_DIR 17 | 18 | script: 19 | - export JPM_FIREFOX_BINARY=$TRAVIS_BUILD_DIR/../firefox/firefox 20 | - jpm test -v 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Pixel Perfect [](https://travis-ci.org/firebug/pixel-perfect) 2 | ============= 3 | 4 | [](https://gitter.im/firebug/pixel-perfect?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 5 | 6 | Make your web development Pixel Perfect. 7 | 8 | * Home Page: https://github.com/firebug/pixel-perfect/wiki 9 | * Developer Guide: https://github.com/firebug/pixel-perfect/wiki/Developer-Guide 10 | 11 | Pixel Perfect is Firefox extension built on top of native developer tools in Firefox. 12 | 13 | License 14 | ------- 15 | PixelPerfect is free and open source software distributed under the 16 | [BSD License](https://github.com/firebug/pixel-perfect/blob/master/license.txt). 17 | 18 | Hacking on Pixel Perfect 2 19 | -------------------------- 20 | See Pixel Perfect [Developer Guide](https://github.com/firebug/pixel-perfect/wiki/Developer-Guide) 21 | 22 | Further Resources 23 | ----------------- 24 | * Add-on SDK: https://developer.mozilla.org/en-US/Add-ons/SDK 25 | * DevTools API: https://developer.mozilla.org/en-US/docs/Tools/DevToolsAPI 26 | * Coding Style: https://github.com/mozilla/addon-sdk/wiki/Coding-style-guide 27 | * DevTools Extension Examples: https://github.com/mozilla/addon-sdk/tree/devtools/examples 28 | * DevTools/Hacking: https://wiki.mozilla.org/DevTools/Hacking 29 | * Firefox Developer Edition: https://developer.mozilla.org/en-US/Firefox/Developer_Edition 30 | * Firebug.next wiki: https://getfirebug.com/wiki/index.php/Firebug.next 31 | -------------------------------------------------------------------------------- /chrome.manifest: -------------------------------------------------------------------------------- 1 | content pixelperfect chrome/content/ 2 | skin pixelperfect classic/1.0 chrome/skin/classic/shared/ 3 | 4 | locale pixelperfect en-US chrome/locale/en-US/ 5 | locale pixelperfect zh-CN chrome/locale/zh-CN/ 6 | locale pixelperfect de chrome/locale/de/ 7 | -------------------------------------------------------------------------------- /chrome/locale/de/notification.properties: -------------------------------------------------------------------------------- 1 | pixelPerfect.firstRun.welcome=Willkommen bei Pixel Perfect! 2 | pixelPerfect.firstRun.description=Pixel Perfect wurde ursprünglich als Erweiterung für Firebug eingeführt. Diese neue Version basiert direkt auf den nativen Firefox Entwickler-Tools und kommt mit verbesserter Benutzerfreundlichkeit und verbesserten Funktionen daher. Es kann nun mit oder ohne Firebug verwendet werden. Unter anderem werden Remote-Geräte wie Mobiltelefone unterstützt. Viel Spaß! 3 | pixelPerfect.firstRun.start=Starte Pixel Perfect 4 | -------------------------------------------------------------------------------- /chrome/locale/de/popup-panel.properties: -------------------------------------------------------------------------------- 1 | pixelPerfect.label.opacity=Deckkraft 2 | pixelPerfect.label.x=X-Achse 3 | pixelPerfect.label.y=Y-Achse 4 | pixelPerfect.label.scale=Größe 5 | pixelPerfect.label.lock=Sperren 6 | pixelPerfect.label.invert=Invertieren 7 | pixelPerfect.label.addLayer=Ebene hinzufügen 8 | pixelPerfect.help.desc=Pixel Perfect erlaubt es, (HTML)-Seitenebenen zu erstellen. Beginnen Sie mit dem hinzufügen einer neuen Ebene, indem Sie auf die untenstehende Schaltfläche klicken. 9 | pixelPerfect.help.more=Mehr erfahren 10 | -------------------------------------------------------------------------------- /chrome/locale/de/toolbox.properties: -------------------------------------------------------------------------------- 1 | pixelPerfect.title=Pixel Perfect 2 | pixelPerfect.startButton.title=Pixel Perfect 3 | pixelPerfect.startButton.tip=Machen Sie ihre Webentwicklung Pixel Perfect 4 | pixelPerfect.menu.about.label=Über Pixel Perfect… 5 | pixelPerfect.menu.about.tip=Informationen über Pixel Perfect 6 | pixelPerfect.menu.visitHomePage.label=Webseite besuchen… 7 | pixelPerfect.menu.visitHomePage.tip=Lernen Sie mehr über Pixel Perfect 8 | pixelPerfect.menu.reportIssue.label=Problem melden… 9 | pixelPerfect.menu.reportIssue.tip=Haben Sie einen Fehler gefunden oder brauchen Sie eine neue Funktion? Lassen Sie es uns wissen! 10 | pixelPerfect.menu.group.label=Diskussionsgruppe… 11 | pixelPerfect.menu.group.tip=Öffnen Sie die Diskussionsgruppenseite (mit Firebug geteilt) 12 | -------------------------------------------------------------------------------- /chrome/locale/en-US/notification.properties: -------------------------------------------------------------------------------- 1 | # LOCALIZATION NOTE (pixelPerfect.firstRun.welcome, pixelPerfect.firstRun.description, 2 | # pixelPerfect.firstRun.start): A message used in a notification dialog 3 | # that is displayed when Pixel Perfect is installed 4 | pixelPerfect.firstRun.welcome=Welcome to Pixel Perfect! 5 | pixelPerfect.firstRun.description=Pixel Perfect was originally introduced as an extension for Firebug. This new version is built on top of the native Firefox developer tools with improved user experience and features. It can now be used with or without Firebug. Among other things, remote devices such as mobile phones are also supported. Enjoy! 6 | pixelPerfect.firstRun.start=Start Pixel Perfect 7 | -------------------------------------------------------------------------------- /chrome/locale/en-US/popup-panel.properties: -------------------------------------------------------------------------------- 1 | # LOCALIZATION NOTE (pixelPerfect.label.opacity, pixelPerfect.label.x, 2 | # pixelPerfect.label.y, pixelPerfect.label.scale, 3 | # pixelPerfect.label.lock, pixelPerfect.label.addLayer): 4 | # Label for Pixel Perfect popup panel form 5 | pixelPerfect.label.opacity=Opacity 6 | pixelPerfect.label.x=X 7 | pixelPerfect.label.y=Y 8 | pixelPerfect.label.scale=Scale 9 | pixelPerfect.label.lock=Lock 10 | pixelPerfect.label.invert=Invert 11 | pixelPerfect.label.addLayer=Add Layer 12 | 13 | # LOCALIZATION NOTE (pixelPerfect.help.desc): description displayed 14 | # in Pixel Perfect panel when there are no layers. 15 | pixelPerfect.help.desc=Pixel Perfect allows creating (HTML) page layers. Start with adding a new layer by clicking on the button below. 16 | 17 | # LOCALIZATION NOTE (pixelPerfect.help.more): A label used for 'More' 18 | # link that is displayed in Pixel Perfect panel when there are no layers. 19 | pixelPerfect.help.more=Learn More 20 | -------------------------------------------------------------------------------- /chrome/locale/en-US/toolbox.properties: -------------------------------------------------------------------------------- 1 | # LOCALIZATION NOTE (pixelPerfect.title): Pixel Perfect extension title 2 | pixelPerfect.title=Pixel Perfect 3 | 4 | # LOCALIZATION NOTE (pixelPerfect.button.title, pixelPerfect.button.tip): 5 | # Label and a tooltip for Pixel Perfect start button available in the 6 | # Firefox toolbar and Style Editor panel. This button opens Pixel Perfect 7 | # popup panel. 8 | pixelPerfect.startButton.title=Pixel Perfect 9 | pixelPerfect.startButton.tip=Make your web development Pixel Perfect 10 | 11 | # LOCALIZATION NOTE (pixelPerfect.menu.about.label, pixelPerfect.menu.about.tip): 12 | # Pixel Perfect menu label. The menu is available in pixel perfect start 13 | # button located in Firefox toolbar. 14 | pixelPerfect.menu.about.label=About Pixel Perfect... 15 | pixelPerfect.menu.about.tip=Information about Pixel Perfect 16 | 17 | # LOCALIZATION NOTE (pixelPerfect.menu.visitHomePage.label, pixelPerfect.menu.visitHomePage.tip): 18 | # Pixel Perfect menu label. The menu is available in pixel perfect start 19 | # button located in Firefox toolbar. 20 | pixelPerfect.menu.visitHomePage.label=Visit Home Page... 21 | pixelPerfect.menu.visitHomePage.tip=Learn more about Pixel Perfect 22 | 23 | # LOCALIZATION NOTE (pixelPerfect.menu.reportIssue.label, pixelPerfect.menu.reportIssue.tip): 24 | # Pixel Perfect menu label. The menu is available in pixel perfect start 25 | # button located in Firefox toolbar. 26 | pixelPerfect.menu.reportIssue.label=Report Issue... 27 | pixelPerfect.menu.reportIssue.tip=Did you find a bug or do you need a new feature? Let us know! 28 | 29 | # LOCALIZATION NOTE (pixelPerfect.menu.group.label, pixelPerfect.menu.group.tip): 30 | # Pixel Perfect menu label. The menu is available in pixel perfect start 31 | # button located in Firefox toolbar. 32 | pixelPerfect.menu.group.label=Discussion Group... 33 | pixelPerfect.menu.group.tip=Open the discussion group site (shared with Firebug) 34 | -------------------------------------------------------------------------------- /chrome/locale/zh-CN/notification.properties: -------------------------------------------------------------------------------- 1 | pixelPerfect.firstRun.welcome=欢迎使用 Pixel Perfect! 2 | pixelPerfect.firstRun.description=Pixel Perfect 最初是作为一个适用于 Firebug 的扩展被引入。这个新版本建立于原生的 Firefox 开发者工具并带有改进的用户体验和功能。它可以适用于或不用于 Firebug。除此以外,远程设备(例如移动电话)现在也已支持。尽情享用吧! 3 | pixelPerfect.firstRun.start=启动 Pixel Perfect 4 | -------------------------------------------------------------------------------- /chrome/locale/zh-CN/popup-panel.properties: -------------------------------------------------------------------------------- 1 | pixelPerfect.label.opacity=不透明度 2 | pixelPerfect.label.x=X 3 | pixelPerfect.label.y=Y 4 | pixelPerfect.label.scale=比例尺 5 | pixelPerfect.label.lock=锁定 6 | pixelPerfect.label.invert=反转 7 | pixelPerfect.label.addLayer=添加图层 8 | pixelPerfect.help.desc=Pixel Perfect 带来创建 (HTML) 页面图层的功能。首先,点击下面的按钮添加一个新图层。 9 | pixelPerfect.help.more=详细了解 10 | -------------------------------------------------------------------------------- /chrome/locale/zh-CN/toolbox.properties: -------------------------------------------------------------------------------- 1 | pixelPerfect.title=Pixel Perfect 2 | pixelPerfect.startButton.title=Pixel Perfect 3 | pixelPerfect.startButton.tip=使用 Pixel Perfect 助力您的 Web 开发 4 | pixelPerfect.menu.about.label=关于 Pixel Perfect... 5 | pixelPerfect.menu.about.tip=有关 Pixel Perfect 的信息 6 | pixelPerfect.menu.visitHomePage.label=访问主页... 7 | pixelPerfect.menu.visitHomePage.tip=详细了解 Pixel Perfect 8 | pixelPerfect.menu.reportIssue.label=报告问题... 9 | pixelPerfect.menu.reportIssue.tip=您发现了一个 bug 或者您需要一个新功能?让我们知道! 10 | pixelPerfect.menu.group.label=讨论组... 11 | pixelPerfect.menu.group.tip=打开讨论组网站(与 Firebug 共享) 12 | -------------------------------------------------------------------------------- /chrome/skin/classic/shared/logo_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebug/pixel-perfect/8106cbfecb9a9a59544fa172f6d12cb9c924109c/chrome/skin/classic/shared/logo_16x16.png -------------------------------------------------------------------------------- /chrome/skin/classic/shared/logo_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebug/pixel-perfect/8106cbfecb9a9a59544fa172f6d12cb9c924109c/chrome/skin/classic/shared/logo_32x32.png -------------------------------------------------------------------------------- /chrome/skin/classic/shared/logo_64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebug/pixel-perfect/8106cbfecb9a9a59544fa172f6d12cb9c924109c/chrome/skin/classic/shared/logo_64x64.png -------------------------------------------------------------------------------- /chrome/skin/classic/shared/resizer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebug/pixel-perfect/8106cbfecb9a9a59544fa172f6d12cb9c924109c/chrome/skin/classic/shared/resizer.png -------------------------------------------------------------------------------- /chrome/skin/classic/shared/ua.css: -------------------------------------------------------------------------------- 1 | /* See license.txt for terms of usage */ 2 | 3 | div:-moz-native-anonymous .pixelperfect-layer-box { 4 | position: absolute; 5 | cursor: move; 6 | pointer-events: auto; 7 | line-height: 1px; 8 | } 9 | 10 | div:-moz-native-anonymous .pixelperfect-layer-box[drag] { 11 | outline: 1px dashed rgb(51, 153, 255); 12 | } 13 | 14 | div:-moz-native-anonymous .pixelperfect-layer-box[lock] { 15 | cursor: default; 16 | pointer-events: none; 17 | } 18 | 19 | div:-moz-native-anonymous .pixelperfect-layer-box[invert] img { 20 | filter: invert(100%); 21 | } 22 | 23 | /* The following two styles are fixing issue #67 24 | https://github.com/firebug/pixel-perfect/issues/67 25 | They can both be removed as soon as the platform is fixed: 26 | https://bugzilla.mozilla.org/show_bug.cgi?id=1168113 27 | 28 | This gets yet more complex since there is currently no 29 | CSS way how to avoid fixed position: 30 | https://bugzilla.mozilla.org/show_bug.cgi?id=1230508#c26 31 | The only way is using 'scroll' events and update position 32 | of layers manually. 33 | 34 | The CSS is currently using position: fixed (in addition to 35 | the manual scroll-offset calculation). As soon as 'absolute' 36 | is supported again it should be removed */ 37 | div:-moz-native-anonymous.moz-custom-content-container { 38 | /*position: absolute;*/ 39 | position: fixed; 40 | } 41 | 42 | :-moz-native-anonymous .highlighter-container { 43 | position: fixed; 44 | } 45 | -------------------------------------------------------------------------------- /data/config.js: -------------------------------------------------------------------------------- 1 | /* See license.txt for terms of usage */ 2 | 3 | // RequireJS configuration 4 | require.config({ 5 | baseUrl: ".", 6 | paths: { 7 | "react": "./lib/react/react", 8 | "reps": "../../node_modules/firebug.sdk/lib/reps", 9 | } 10 | }); 11 | 12 | // Load the main panel module 13 | requirejs(["popup"]); 14 | -------------------------------------------------------------------------------- /data/img/close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 133 | -------------------------------------------------------------------------------- /data/img/slider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebug/pixel-perfect/8106cbfecb9a9a59544fa172f6d12cb9c924109c/data/img/slider.png -------------------------------------------------------------------------------- /data/layer-form.js: -------------------------------------------------------------------------------- 1 | /* See license.txt for terms of usage */ 2 | 3 | define(function(require, exports, module) { 4 | 5 | // Dependencies 6 | const React = require("react"); 7 | const { Reps } = require("reps/reps"); 8 | const { LayerStore } = require("layer-store"); 9 | 10 | // Shortcuts 11 | const { SPAN, TABLE, TR, TD, BUTTON, INPUT, DIV, LABEL } = Reps.DOM; 12 | 13 | /** 14 | * @react This template implements a form allowing to see and modify 15 | * Layer properties. Every modification is immediately propagated to 16 | * the storage {@link PixelPerfectStore}. Since the storage object lives 17 | * inside the chrome scope the access is done through a proxy object 18 | * {@link LayerStore} that sends appropriate JSON messages using message 19 | * manager. 20 | */ 21 | var LayerForm = React.createClass({ 22 | getInitialState: function() { 23 | return {}; 24 | }, 25 | 26 | componentWillReceiveProps: function(nextProps) { 27 | this.setState(nextProps.layer); 28 | }, 29 | 30 | render: function() { 31 | var layer = this.props.layer; 32 | return ( 33 | TABLE({className: "form"}, 34 | TR({}, 35 | TD({className: "right"}, 36 | LABEL({className: "pixel-perfect-label", 37 | htmlFor: "pixel-perfect-opacity"}, 38 | Locale.$STR("pixelPerfect.label.opacity") + ":" 39 | ) 40 | ), 41 | TD({}, 42 | INPUT({className: "opacity", type: "range", value: layer.opacity, 43 | id: "pixel-perfect-opacity", 44 | onChange: this.onChange.bind(this, "opacity", "string")}) 45 | ), 46 | TD({}, 47 | INPUT({className: "opacity-value", size: 3, value: layer.opacity, 48 | maxLength: 3, 49 | onChange: this.onChange.bind(this, "opacity", "number")}) 50 | ) 51 | ), 52 | TR({}, 53 | TD({className: "right"}, 54 | LABEL({className: "pixel-perfect-label", 55 | htmlFor: "pixel-perfect-x"}, 56 | Locale.$STR("pixelPerfect.label.x") + ":") 57 | ), 58 | TD({className: "positionCell", colSpan: 2}, 59 | TABLE({className: "position"}, 60 | TR({}, 61 | TD({}, 62 | INPUT({size: 5, value: layer.x, type: "number", 63 | id: "pixel-perfect-x", 64 | onChange: this.onChange.bind(this, "x", "number")}) 65 | ), 66 | TD({className: "right"}, 67 | LABEL({className: "pixel-perfect-label", 68 | htmlFor: "pixel-perfect-y"}, 69 | Locale.$STR("pixelPerfect.label.y") + ":") 70 | ), 71 | TD({}, 72 | INPUT({size: 5, value: layer.y, type: "number", 73 | id: "pixel-perfect-y", 74 | onChange: this.onChange.bind(this, "y", "number")}) 75 | ) 76 | ) 77 | ) 78 | ) 79 | ), 80 | TR({}, 81 | TD({className: "right"}, 82 | LABEL({className: "pixel-perfect-label", 83 | htmlFor: "pixel-perfect-scale"}, 84 | Locale.$STR("pixelPerfect.label.scale") + ":") 85 | ), 86 | TD({colSpan: 2}, 87 | INPUT({size: 3, value: layer.scale, type: "number", step: "0.1", 88 | id: "pixel-perfect-scale", 89 | onChange: this.onChange.bind(this, "scale", "float")}) 90 | ) 91 | ), 92 | TR({}, 93 | TD({className: "right"}, 94 | LABEL({className: "pixel-perfect-label", 95 | htmlFor: "pixel-perfect-lock"}, 96 | Locale.$STR("pixelPerfect.label.lock") + ":") 97 | ), 98 | TD({colSpan: 2}, 99 | INPUT({type: "checkbox", checked: layer.lock, 100 | id: "pixel-perfect-lock", 101 | onChange: this.onChange.bind(this, "lock", "boolean")}) 102 | ) 103 | ), 104 | TR({}, 105 | TD({className: "right"}, 106 | LABEL({className: "pixel-perfect-label", 107 | htmlFor: "pixel-perfect-invert"}, 108 | Locale.$STR("pixelPerfect.label.invert") + ":") 109 | ), 110 | TD({colSpan: 2}, 111 | INPUT({type: "checkbox", checked: layer.invert, 112 | id: "pixel-perfect-invert", 113 | onChange: this.onChange.bind(this, "invert", "boolean")}) 114 | ) 115 | ), 116 | TR({}, 117 | TD({colSpan: 3}, 118 | DIV({className: "url"}, layer.url) 119 | ) 120 | ) 121 | ) 122 | ); 123 | }, 124 | 125 | // Events 126 | 127 | /** 128 | * Handler for changes made through the form's input fields. 129 | * State of the current layer is updated and changed propagated 130 | * to the {@link LayerStore}. 131 | */ 132 | onChange: function(propName, type, event) { 133 | var value; 134 | 135 | switch (type) { 136 | case "boolean": 137 | value = event.target.checked; 138 | break; 139 | case "number": 140 | value = parseInt(event.target.value, 10); 141 | break; 142 | case "float": 143 | var v = event.target.value; 144 | value = (v != "0.") ? parseFloat(v, 10) : v; 145 | break; 146 | case "string": 147 | value = event.target.value; 148 | break; 149 | } 150 | 151 | if (this.state[propName] === value) { 152 | return; 153 | } 154 | 155 | // Make sure the UI is updated. 156 | this.state[propName] = value; 157 | this.setState(this.state); 158 | 159 | var props = {}; 160 | props[propName] = value; 161 | 162 | // Immediately update modified layer inside the store object. 163 | // The {@link LayerStore} object is used as a proxy to the real 164 | // storage object that lives in the chrome scope. 165 | LayerStore.modify(this.props.layer.id, props); 166 | }, 167 | }); 168 | 169 | // Exports from this module 170 | exports.LayerForm = React.createFactory(LayerForm); 171 | }); 172 | -------------------------------------------------------------------------------- /data/layer-list.js: -------------------------------------------------------------------------------- 1 | /* See license.txt for terms of usage */ 2 | 3 | define(function(require, exports, module) { 4 | 5 | // Dependencies 6 | const React = require("react"); 7 | const { Reps } = require("reps/reps"); 8 | const { LayerStore } = require("layer-store"); 9 | 10 | // Shortcuts 11 | const { TABLE, TBODY, TR, TD, INPUT, IMG, THEAD, TH, DIV } = Reps.DOM; 12 | 13 | /** 14 | * @react This template is responsible for displaying list of registered 15 | * layers. The user can append new as well as remove an existing layer 16 | * from/to the list. It's also possible to change order of existing layers 17 | * through drag and drop. 18 | */ 19 | var LayerList = React.createClass({ 20 | getInitialState: function() { 21 | return { 22 | layers: this.props.layers, 23 | selection: this.props.selection 24 | }; 25 | }, 26 | 27 | componentWillReceiveProps: function(nextProps) { 28 | this.setState({ 29 | layers: nextProps.layers, 30 | selection: nextProps.selection 31 | }); 32 | }, 33 | 34 | render: function() { 35 | var rows = []; 36 | var layers = this.state.layers; 37 | 38 | layers.forEach(layer => { 39 | rows.push(LayerRow({ 40 | key: layer.id, 41 | layer: layer, 42 | selected: layer.id == this.props.selection, 43 | selectLayer: this.props.selectLayer.bind(this, layer), 44 | removeLayer: this.props.removeLayer.bind(this, layer), 45 | dragStart: this.dragStart, 46 | dragEnd: this.dragEnd, 47 | })); 48 | }); 49 | 50 | // Add one extra row that displays a button for appending new layers. 51 | rows.push(AddLayerRow({ 52 | addLayer: this.props.addLayer 53 | })); 54 | 55 | return ( 56 | TABLE({className: "layerTable", onDragOver: this.dragOver}, 57 | THEAD({className: "poolRow"}, 58 | TH({width: "20px"}), 59 | TH({width: "96px"}) 60 | ), 61 | TBODY(null, rows) 62 | ) 63 | ); 64 | }, 65 | 66 | // Drag And Drop 67 | 68 | dragStart: function(event) { 69 | // Drag operation can only start by dragging layer image. 70 | var target = event.currentTarget; 71 | if (target.classList.contains("layerImage")) { 72 | this.dragged = event.currentTarget; 73 | event.dataTransfer.effectAllowed = "copyMove"; 74 | 75 | // Firefox requires calling dataTransfer.setData 76 | // for the drag to properly work 77 | event.dataTransfer.setData("text/html", null); 78 | } 79 | }, 80 | 81 | dragOver: function(event) { 82 | // Bail out if no drag in progress at this moment. 83 | if (!this.dragged) { 84 | return; 85 | } 86 | 87 | // Drop operation can only happen on an existing layer image. 88 | var target = event.target; 89 | if (!target.classList.contains("layerImage")) { 90 | return; 91 | } 92 | 93 | event.preventDefault(); 94 | 95 | // Ignore the beginning of the drag operation when the mouse is 96 | // hovering over the clicked layer. 97 | if (target == this.dragged) { 98 | return; 99 | } 100 | 101 | // Check if the mouse is hovering close to the target layer 102 | // image center. If yes, let's perform the drag-move operation 103 | // i.g. move the source layer into a new position in the list. 104 | var y = event.clientY; 105 | var rect = target.getBoundingClientRect(); 106 | var delta = (rect.height / 3); 107 | if (!(y > rect.top + delta && y < rect.bottom - delta)) { 108 | return; 109 | } 110 | 111 | var originalIndex = this.getLayerIndexById(this.dragged.dataset.id); 112 | var targetIndex = this.getLayerIndexById(target.dataset.id); 113 | 114 | // Move the dragging layer into a new position in the layers list 115 | // and ensure UI rendering by setting a new state. 116 | var layers = this.state.layers; 117 | var removed = layers.splice(originalIndex, 1)[0]; 118 | layers.splice(targetIndex, 0, removed); 119 | 120 | // Set new state (dragged image is at new index in the array). 121 | this.setState({layers: layers}); 122 | 123 | LayerStore.move(originalIndex, targetIndex); 124 | }, 125 | 126 | dragEnd: function(event) { 127 | // Nothing special here, the dragged image is already moved 128 | // into a new location during the drag-over event. 129 | }, 130 | 131 | // Helpers 132 | 133 | getLayerIndexById: function(id) { 134 | return this.state.layers.findIndex(layer => { 135 | return (layer.id == id); 136 | }) 137 | } 138 | }); 139 | 140 | /** 141 | * @react This template renders one layer (row) in the list. 142 | * Every layer is rendered as a small thumbnail displaying 143 | * the associated image. The thumbnail also displays a close 144 | * button when hovered by mouse. This button allows to remove 145 | * the layer. 146 | */ 147 | var LayerRow = React.createFactory(React.createClass({ 148 | getInitialState: function() { 149 | return { 150 | layer: {}, 151 | selected: false 152 | }; 153 | }, 154 | 155 | componentWillReceiveProps: function(nextProps) { 156 | this.setState(nextProps); 157 | }, 158 | 159 | render: function() { 160 | var layer = this.props.layer; 161 | var imageUrl = layer.dataUrl; 162 | var selected = this.props.selected ? " selected" : ""; 163 | 164 | return ( 165 | TR({className: "layerRow", onClick: this.props.selectLayer}, 166 | TD({className: "layerCell"}, 167 | INPUT({className: "visibility", type: "checkbox", 168 | checked: layer.visible, onChange: this.onVisibleChange}) 169 | ), 170 | TD({className: "layerCell"}, 171 | DIV({className: "layerImageBox" + selected}, 172 | IMG({className: "layerImage img-thumbnail", src: imageUrl, 173 | key: layer.id, "data-id": layer.id, 174 | onDragStart: this.props.dragStart, 175 | onDragEnd: this.props.dragEnd}), 176 | DIV({className: "closeButton", onClick: this.onRemove}) 177 | ) 178 | ) 179 | ) 180 | ) 181 | }, 182 | 183 | onRemove: function(event) { 184 | // Cancel the event to avoid selection of the to be removed layer. 185 | event.stopPropagation(); 186 | event.preventDefault(); 187 | 188 | // Execute provided callback, it's already bound with 189 | // a layer object associated with this row. 190 | this.props.removeLayer(); 191 | }, 192 | 193 | onVisibleChange: function(event) { 194 | var value = event.target.checked; 195 | 196 | this.state.layer["visible"] = value; 197 | this.setState(this.state); 198 | 199 | var props = { visible: value }; 200 | LayerStore.modify(this.props.layer.id, props); 201 | }, 202 | })); 203 | 204 | /** 205 | * @react This template renders a button for adding a new layer. 206 | */ 207 | var AddLayerRow = React.createFactory(React.createClass({ 208 | render: function() { 209 | return ( 210 | TR({className: "layerRow", onClick: this.props.onSelect}, 211 | TD({className: "layerCell"}), 212 | TD({className: "layerCell"}, 213 | DIV({className: "layerImageBox"}, 214 | DIV({className: "layerImage add img-thumbnail"}, 215 | DIV({onClick: this.props.addLayer}, 216 | Locale.$STR("pixelPerfect.label.addLayer") 217 | ) 218 | ) 219 | ) 220 | ) 221 | ) 222 | ) 223 | }, 224 | })); 225 | 226 | // Exports from this module 227 | exports.LayerList = React.createFactory(LayerList); 228 | }); 229 | -------------------------------------------------------------------------------- /data/layer-store.js: -------------------------------------------------------------------------------- 1 | /* See license.txt for terms of usage */ 2 | 3 | "use strict"; 4 | 5 | define(function(require, exports, module) { 6 | 7 | /** 8 | * This object represents a proxy to the real Store object in the chrome 9 | * scope {@link PixelPerfectStorage}. All API calls are forwarded through 10 | * a message manager (as asynchronous events) to the real store object. 11 | */ 12 | const LayerStore = 13 | /** @lends LayerStore */ 14 | { 15 | /** 16 | * Modify properties of an existing layer. 17 | */ 18 | modify: function(id, props) { 19 | postChromeMessage("modify", [id, props]); 20 | }, 21 | 22 | /** 23 | * Add a new layer. 24 | */ 25 | add: function() { 26 | postChromeMessage("add"); 27 | }, 28 | 29 | /** 30 | * Remove an existing layer. 31 | */ 32 | remove: function(id) { 33 | postChromeMessage("remove", [id]); 34 | }, 35 | 36 | /** 37 | * Change index (order) of an existing layer in the list. Move it 38 | * from the index 'from' to new index 'to'. 39 | */ 40 | move: function(from, to) { 41 | postChromeMessage("move", [from, to]); 42 | }, 43 | }; 44 | 45 | // Exports from this module 46 | exports.LayerStore = LayerStore; 47 | }); 48 | -------------------------------------------------------------------------------- /data/notification-config.js: -------------------------------------------------------------------------------- 1 | /* See license.txt for terms of usage */ 2 | 3 | // RequireJS configuration 4 | require.config({ 5 | baseUrl: ".", 6 | paths: { 7 | "react": "./lib/react/react", 8 | "reps": "../../node_modules/firebug.sdk/lib/reps" 9 | } 10 | }); 11 | 12 | // Load the main panel module 13 | requirejs(["notification"]); 14 | -------------------------------------------------------------------------------- /data/notification-content.js: -------------------------------------------------------------------------------- 1 | /* See license.txt for terms of usage */ 2 | 3 | /** 4 | * Content script for notification panel. It's only purpose is registering 5 | * a listener for 'click' event and sending a message to the chrome scope 6 | * if the user clicks on 'Start Pixel Perfect' link. 7 | */ 8 | window.addEventListener("click", function(event) { 9 | var target = event.target; 10 | if (target.id == "start") { 11 | self.port.emit("start"); 12 | } 13 | }); 14 | 15 | /** 16 | * Receive localized strings from the chrome scope. These strings are 17 | * used in the notification panel UI. 18 | */ 19 | self.port.on("locales", function(locales) { 20 | document.body.setAttribute("data-locales", JSON.stringify(locales)); 21 | }); 22 | 23 | -------------------------------------------------------------------------------- /data/notification.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /data/notification.js: -------------------------------------------------------------------------------- 1 | /* See license.txt for terms of usage */ 2 | 3 | define(function(require, exports, module) { 4 | 5 | // Dependencies 6 | const React = require("react"); 7 | const { Reps } = require("reps/reps"); 8 | 9 | // Shortcuts 10 | const { TABLE, TR, TD, DIV, IMG, SPAN, BR } = Reps.DOM; 11 | 12 | /** 13 | * @react This template implements UI for the first-run notification panel. 14 | * The panel displays basic information about the extension and a link 15 | * that can be used to open Pixel Perfect popup panel 16 | * {@link PixelPerfectPopup}. 17 | */ 18 | var NotificationContent = React.createFactory(React.createClass({ 19 | render: function() { 20 | var style = { 21 | "textAlign": "justify" 22 | } 23 | 24 | return ( 25 | TABLE({className: "defaultContentTable"}, 26 | TR({}, 27 | TD({width: "10px;", style: style}, 28 | IMG({className: "defaultContentImage", 29 | src: "chrome://pixelperfect/skin/logo_32x32.png"}) 30 | ), 31 | TD({className: "defaultContentHeader", style: style}, 32 | SPAN({}, this.props.title) 33 | ) 34 | ), 35 | TR({}, 36 | TD({colSpan: 2, style: style}, 37 | DIV({className: "defaultContentDesc"}, this.props.welcome), 38 | BR(), 39 | DIV({className: "defaultContentDesc"}, this.props.description) 40 | ) 41 | ), 42 | TR({}, 43 | TD({colSpan: 2}, 44 | DIV({className: "layerImage add img-thumbnail"}, 45 | DIV({id: "start"}, this.props.start) 46 | ) 47 | ) 48 | ) 49 | ) 50 | ) 51 | }, 52 | })); 53 | 54 | // Get localized strings from body dataset and render the panel UI. 55 | // The strings are set into the body element from the chrome scope. 56 | // See {@link FirstRun#initialize} method for more details. 57 | var locales = JSON.parse(document.body.dataset.locales); 58 | React.render(NotificationContent(locales), document.body); 59 | }); 60 | -------------------------------------------------------------------------------- /data/popup-frame-script.js: -------------------------------------------------------------------------------- 1 | /* See license.txt for terms of usage */ 2 | 3 | "use strict"; 4 | 5 | (function({ 6 | content, 7 | addMessageListener, 8 | sendAsyncMessage, 9 | removeMessageListener, 10 | addEventListener}) { 11 | 12 | const Cu = Components.utils; 13 | const Cc = Components.classes; 14 | const Ci = Components.interfaces; 15 | 16 | const document = content.document; 17 | const window = content; 18 | 19 | /** 20 | * Listener for message from the inspector panel (chrome scope). 21 | * It's further distributed as DOM event, so it can be handled by 22 | * the page content script. 23 | */ 24 | function messageListener(message) { 25 | const { type, data, origin, bubbles, cancelable } = message.data; 26 | 27 | const event = new window.MessageEvent(type, { 28 | bubbles: bubbles, 29 | cancelable: cancelable, 30 | data: data, 31 | origin: origin, 32 | target: window, 33 | source: window, 34 | }); 35 | 36 | window.dispatchEvent(event); 37 | }; 38 | 39 | addMessageListener("pixelperfect/event/message", messageListener); 40 | 41 | /** 42 | * Send a message back to the parent panel (chrome scope). 43 | */ 44 | function postChromeMessage(type, args, objects) { 45 | let data = { 46 | type: type, 47 | args: args, 48 | }; 49 | 50 | sendAsyncMessage("message", data, objects); 51 | } 52 | 53 | /** 54 | * Export 'postChromeMessage' function to the frame content as soon 55 | * as it's loaded. This function allows sending messages from the 56 | * frame's content directly to the chrome scope. 57 | */ 58 | addEventListener("DOMContentLoaded", function onContentLoaded(event) { 59 | removeEventListener("DOMContentLoaded", onContentLoaded, true); 60 | 61 | Cu.exportFunction(postChromeMessage, window, { 62 | defineAs: "postChromeMessage" 63 | }); 64 | }, true); 65 | 66 | // End of scope 67 | })(this); 68 | -------------------------------------------------------------------------------- /data/popup-panel.js: -------------------------------------------------------------------------------- 1 | /* See license.txt for terms of usage */ 2 | 3 | define(function(require, exports, module) { 4 | 5 | // Dependencies 6 | const React = require("react"); 7 | const { Reps } = require("reps/reps"); 8 | const { LayerList } = require("layer-list"); 9 | const { LayerForm } = require("layer-form"); 10 | const { LayerStore } = require("layer-store"); 11 | 12 | // Shortcuts 13 | const { TABLE, TR, TD, DIV, IMG, SPAN } = Reps.DOM; 14 | 15 | /** 16 | * @react This template implements basic layout for the popup panel. 17 | * There are two components displayed: 18 | * 1. Layer list: list of all registered layers {@link LayerList}. 19 | * 2. Layer form: a form displaying properties of the selected layer 20 | * {@link LayerForm}. 21 | * 22 | * If there are no layers, the popup panel displays default content 23 | * with instructions and one button: 'Add Layer' {@link DefaultContent}. 24 | */ 25 | var PopupPanel = React.createClass({ 26 | getInitialState: function() { 27 | return { 28 | layers: [], 29 | selection: null, 30 | }; 31 | }, 32 | 33 | render: function() { 34 | var layers = this.state.layers; 35 | var selectedLayer = this.getLayer(this.state.selection); 36 | 37 | // If there are no layers, display default content with instructions 38 | // about how to create one. 39 | if (!layers || !layers.length) { 40 | return DefaultContent({ 41 | version: this.state.version, 42 | addLayer: this.addLayer 43 | }); 44 | } 45 | 46 | // Render list of layers and a form component. 47 | return ( 48 | TABLE({className: "popupPanelTable"}, 49 | TR({}, 50 | TD({className: "layerListCell"}, 51 | DIV({className: "layerList"}, 52 | LayerList({ 53 | layers: this.state.layers, 54 | selection: this.state.selection, 55 | selectLayer: this.selectLayer, 56 | addLayer: this.addLayer, 57 | removeLayer: this.removeLayer 58 | }) 59 | ) 60 | ), 61 | TD({className: "layerFormCell"}, 62 | DIV({className: "layerForm"}, 63 | LayerForm({ 64 | layer: selectedLayer 65 | }) 66 | ) 67 | ) 68 | ) 69 | ) 70 | ) 71 | }, 72 | 73 | getLayer: function(id) { 74 | var layers = this.state.layers; 75 | for (var i=0; i