Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | var widgets = require('sdk/widget'); |
michael@0 | 6 | var pageMod = require('sdk/page-mod'); |
michael@0 | 7 | var data = require('sdk/self').data; |
michael@0 | 8 | var panels = require('sdk/panel'); |
michael@0 | 9 | var simpleStorage = require('sdk/simple-storage'); |
michael@0 | 10 | var notifications = require("sdk/notifications"); |
michael@0 | 11 | |
michael@0 | 12 | /* |
michael@0 | 13 | Global variables |
michael@0 | 14 | * Boolean to indicate whether the add-on is switched on or not |
michael@0 | 15 | * Array for all workers associated with the 'selector' page mod |
michael@0 | 16 | * Array for all workers associated with the 'matcher' page mod |
michael@0 | 17 | */ |
michael@0 | 18 | var annotatorIsOn = false; |
michael@0 | 19 | var selectors = []; |
michael@0 | 20 | var matchers = []; |
michael@0 | 21 | |
michael@0 | 22 | if (!simpleStorage.storage.annotations) |
michael@0 | 23 | simpleStorage.storage.annotations = []; |
michael@0 | 24 | |
michael@0 | 25 | /* |
michael@0 | 26 | Update the matchers: call this whenever the set of annotations changes |
michael@0 | 27 | */ |
michael@0 | 28 | function updateMatchers() { |
michael@0 | 29 | matchers.forEach(function (matcher) { |
michael@0 | 30 | matcher.postMessage(simpleStorage.storage.annotations); |
michael@0 | 31 | }); |
michael@0 | 32 | } |
michael@0 | 33 | |
michael@0 | 34 | /* |
michael@0 | 35 | Constructor for an Annotation object |
michael@0 | 36 | */ |
michael@0 | 37 | function Annotation(annotationText, anchor) { |
michael@0 | 38 | this.annotationText = annotationText; |
michael@0 | 39 | this.url = anchor[0]; |
michael@0 | 40 | this.ancestorId = anchor[1]; |
michael@0 | 41 | this.anchorText = anchor[2]; |
michael@0 | 42 | } |
michael@0 | 43 | |
michael@0 | 44 | /* |
michael@0 | 45 | Function to deal with a new annotation. |
michael@0 | 46 | Create a new annotation object, store it, and |
michael@0 | 47 | notify all the annotators of the change. |
michael@0 | 48 | */ |
michael@0 | 49 | function handleNewAnnotation(annotationText, anchor) { |
michael@0 | 50 | var newAnnotation = new Annotation(annotationText, anchor); |
michael@0 | 51 | simpleStorage.storage.annotations.push(newAnnotation); |
michael@0 | 52 | updateMatchers(); |
michael@0 | 53 | } |
michael@0 | 54 | |
michael@0 | 55 | /* |
michael@0 | 56 | Function to tell the selector page mod that the add-on has become (in)active |
michael@0 | 57 | */ |
michael@0 | 58 | function activateSelectors() { |
michael@0 | 59 | selectors.forEach( |
michael@0 | 60 | function (selector) { |
michael@0 | 61 | selector.postMessage(annotatorIsOn); |
michael@0 | 62 | }); |
michael@0 | 63 | } |
michael@0 | 64 | |
michael@0 | 65 | /* |
michael@0 | 66 | Toggle activation: update the on/off state and notify the selectors. |
michael@0 | 67 | */ |
michael@0 | 68 | function toggleActivation() { |
michael@0 | 69 | annotatorIsOn = !annotatorIsOn; |
michael@0 | 70 | activateSelectors(); |
michael@0 | 71 | return annotatorIsOn; |
michael@0 | 72 | } |
michael@0 | 73 | |
michael@0 | 74 | function detachWorker(worker, workerArray) { |
michael@0 | 75 | var index = workerArray.indexOf(worker); |
michael@0 | 76 | if(index != -1) { |
michael@0 | 77 | workerArray.splice(index, 1); |
michael@0 | 78 | } |
michael@0 | 79 | } |
michael@0 | 80 | |
michael@0 | 81 | exports.main = function() { |
michael@0 | 82 | |
michael@0 | 83 | /* |
michael@0 | 84 | The widget provides a mechanism to switch the selector on or off, and to |
michael@0 | 85 | view the list of annotations. |
michael@0 | 86 | |
michael@0 | 87 | The selector is switched on/off with a left-click, and the list of annotations |
michael@0 | 88 | is displayed on a right-click. |
michael@0 | 89 | */ |
michael@0 | 90 | var widget = widgets.Widget({ |
michael@0 | 91 | id: 'toggle-switch', |
michael@0 | 92 | label: 'Annotator', |
michael@0 | 93 | contentURL: data.url('widget/pencil-off.png'), |
michael@0 | 94 | contentScriptWhen: 'ready', |
michael@0 | 95 | contentScriptFile: data.url('widget/widget.js') |
michael@0 | 96 | }); |
michael@0 | 97 | |
michael@0 | 98 | widget.port.on('left-click', function() { |
michael@0 | 99 | console.log('activate/deactivate'); |
michael@0 | 100 | widget.contentURL = toggleActivation() ? |
michael@0 | 101 | data.url('widget/pencil-on.png') : |
michael@0 | 102 | data.url('widget/pencil-off.png'); |
michael@0 | 103 | }); |
michael@0 | 104 | |
michael@0 | 105 | widget.port.on('right-click', function() { |
michael@0 | 106 | console.log('show annotation list'); |
michael@0 | 107 | annotationList.show(); |
michael@0 | 108 | }); |
michael@0 | 109 | |
michael@0 | 110 | /* |
michael@0 | 111 | The selector page-mod enables the user to select page elements to annotate. |
michael@0 | 112 | |
michael@0 | 113 | It is attached to all pages but only operates if the add-on is active. |
michael@0 | 114 | |
michael@0 | 115 | The content script highlights any page elements which can be annotated. If the |
michael@0 | 116 | user clicks a highlighted element it sends a message to the add-on containing |
michael@0 | 117 | information about the element clicked, which is called the anchor of the |
michael@0 | 118 | annotation. |
michael@0 | 119 | |
michael@0 | 120 | When we receive this message we assign the anchor to the annotationEditor and |
michael@0 | 121 | display it. |
michael@0 | 122 | */ |
michael@0 | 123 | var selector = pageMod.PageMod({ |
michael@0 | 124 | include: ['*'], |
michael@0 | 125 | contentScriptWhen: 'ready', |
michael@0 | 126 | contentScriptFile: [data.url('jquery-1.4.2.min.js'), |
michael@0 | 127 | data.url('selector.js')], |
michael@0 | 128 | onAttach: function(worker) { |
michael@0 | 129 | worker.postMessage(annotatorIsOn); |
michael@0 | 130 | selectors.push(worker); |
michael@0 | 131 | worker.port.on('show', function(data) { |
michael@0 | 132 | annotationEditor.annotationAnchor = data; |
michael@0 | 133 | annotationEditor.show(); |
michael@0 | 134 | }); |
michael@0 | 135 | worker.on('detach', function () { |
michael@0 | 136 | detachWorker(this, selectors); |
michael@0 | 137 | }); |
michael@0 | 138 | } |
michael@0 | 139 | }); |
michael@0 | 140 | |
michael@0 | 141 | /* |
michael@0 | 142 | The annotationEditor panel is the UI component used for creating |
michael@0 | 143 | new annotations. It contains a text area for the user to |
michael@0 | 144 | enter the annotation. |
michael@0 | 145 | |
michael@0 | 146 | When we are ready to display the editor we assign its 'anchor' property |
michael@0 | 147 | and call its show() method. |
michael@0 | 148 | |
michael@0 | 149 | Its content script sends the content of the text area to the add-on |
michael@0 | 150 | when the user presses the return key. |
michael@0 | 151 | |
michael@0 | 152 | When we receives this message we create a new annotation using the anchor |
michael@0 | 153 | and the text the user entered, store it, and hide the panel. |
michael@0 | 154 | */ |
michael@0 | 155 | var annotationEditor = panels.Panel({ |
michael@0 | 156 | width: 220, |
michael@0 | 157 | height: 220, |
michael@0 | 158 | contentURL: data.url('editor/annotation-editor.html'), |
michael@0 | 159 | contentScriptFile: data.url('editor/annotation-editor.js'), |
michael@0 | 160 | onMessage: function(annotationText) { |
michael@0 | 161 | if (annotationText) |
michael@0 | 162 | handleNewAnnotation(annotationText, this.annotationAnchor); |
michael@0 | 163 | annotationEditor.hide(); |
michael@0 | 164 | }, |
michael@0 | 165 | onShow: function() { |
michael@0 | 166 | this.postMessage('focus'); |
michael@0 | 167 | } |
michael@0 | 168 | }); |
michael@0 | 169 | |
michael@0 | 170 | /* |
michael@0 | 171 | The annotationList panel is the UI component that lists all the annotations |
michael@0 | 172 | the user has entered. |
michael@0 | 173 | |
michael@0 | 174 | On its 'show' event we pass it the array of annotations. |
michael@0 | 175 | |
michael@0 | 176 | The content script creates the HTML elements for the annotations, and |
michael@0 | 177 | intercepts clicks on the links, passing them back to the add-on to open them |
michael@0 | 178 | in the browser. |
michael@0 | 179 | */ |
michael@0 | 180 | var annotationList = panels.Panel({ |
michael@0 | 181 | width: 420, |
michael@0 | 182 | height: 200, |
michael@0 | 183 | contentURL: data.url('list/annotation-list.html'), |
michael@0 | 184 | contentScriptFile: [data.url('jquery-1.4.2.min.js'), |
michael@0 | 185 | data.url('list/annotation-list.js')], |
michael@0 | 186 | contentScriptWhen: 'ready', |
michael@0 | 187 | onShow: function() { |
michael@0 | 188 | this.postMessage(simpleStorage.storage.annotations); |
michael@0 | 189 | }, |
michael@0 | 190 | onMessage: function(message) { |
michael@0 | 191 | require('sdk/tabs').open(message); |
michael@0 | 192 | } |
michael@0 | 193 | }); |
michael@0 | 194 | |
michael@0 | 195 | /* |
michael@0 | 196 | We listen for the OverQuota event from simple-storage. |
michael@0 | 197 | If it fires we just notify the user and delete the most |
michael@0 | 198 | recent annotations until we are back in quota. |
michael@0 | 199 | */ |
michael@0 | 200 | simpleStorage.on("OverQuota", function () { |
michael@0 | 201 | notifications.notify({ |
michael@0 | 202 | title: 'Storage space exceeded', |
michael@0 | 203 | text: 'Removing recent annotations'}); |
michael@0 | 204 | while (simpleStorage.quotaUsage > 1) |
michael@0 | 205 | simpleStorage.storage.annotations.pop(); |
michael@0 | 206 | }); |
michael@0 | 207 | |
michael@0 | 208 | /* |
michael@0 | 209 | The matcher page-mod locates anchors on web pages and prepares for the |
michael@0 | 210 | annotation to be displayed. |
michael@0 | 211 | |
michael@0 | 212 | It is attached to all pages, and when it is attached we pass it the complete |
michael@0 | 213 | list of annotations. It looks for anchors in its page. If it finds one it |
michael@0 | 214 | highlights the anchor and binds mouseenter/mouseout events to 'show' and 'hide' |
michael@0 | 215 | messages to the add-on. |
michael@0 | 216 | |
michael@0 | 217 | When the add-on receives the 'show' message it assigns the annotation text to |
michael@0 | 218 | the annotation panel and shows it. |
michael@0 | 219 | |
michael@0 | 220 | Note that the matcher is active whether or not the add-on is active: |
michael@0 | 221 | 'inactive' only means that the user can't create new add-ons, they can still |
michael@0 | 222 | see old ones. |
michael@0 | 223 | */ |
michael@0 | 224 | var matcher = pageMod.PageMod({ |
michael@0 | 225 | include: ['*'], |
michael@0 | 226 | contentScriptWhen: 'ready', |
michael@0 | 227 | contentScriptFile: [data.url('jquery-1.4.2.min.js'), |
michael@0 | 228 | data.url('matcher.js')], |
michael@0 | 229 | onAttach: function(worker) { |
michael@0 | 230 | if(simpleStorage.storage.annotations) { |
michael@0 | 231 | worker.postMessage(simpleStorage.storage.annotations); |
michael@0 | 232 | } |
michael@0 | 233 | worker.port.on('show', function(data) { |
michael@0 | 234 | annotation.content = data; |
michael@0 | 235 | annotation.show(); |
michael@0 | 236 | }); |
michael@0 | 237 | worker.port.on('hide', function() { |
michael@0 | 238 | annotation.content = null; |
michael@0 | 239 | annotation.hide(); |
michael@0 | 240 | }); |
michael@0 | 241 | worker.on('detach', function () { |
michael@0 | 242 | detachWorker(this, matchers); |
michael@0 | 243 | }); |
michael@0 | 244 | matchers.push(worker); |
michael@0 | 245 | } |
michael@0 | 246 | }); |
michael@0 | 247 | |
michael@0 | 248 | /* |
michael@0 | 249 | The annotation panel is the UI component that displays an annotation. |
michael@0 | 250 | |
michael@0 | 251 | When we are ready to show it we assign its 'content' attribute to contain |
michael@0 | 252 | the annotation text, and that gets sent to the content process in onShow(). |
michael@0 | 253 | */ |
michael@0 | 254 | var annotation = panels.Panel({ |
michael@0 | 255 | width: 200, |
michael@0 | 256 | height: 180, |
michael@0 | 257 | contentURL: data.url('annotation/annotation.html'), |
michael@0 | 258 | contentScriptFile: [data.url('jquery-1.4.2.min.js'), |
michael@0 | 259 | data.url('annotation/annotation.js')], |
michael@0 | 260 | contentScriptWhen: 'ready', |
michael@0 | 261 | onShow: function() { |
michael@0 | 262 | this.postMessage(this.content); |
michael@0 | 263 | } |
michael@0 | 264 | }); |
michael@0 | 265 | |
michael@0 | 266 | } |