├── share-icon.png ├── data ├── ui │ ├── scripts │ │ ├── jqueryStub.js │ │ ├── AutoCompleteRefresh.html │ │ ├── Select.html │ │ ├── blade │ │ │ ├── array.js │ │ │ ├── fn.js │ │ │ ├── url.js │ │ │ ├── object.js │ │ │ └── Widget.js │ │ ├── osTheme.js │ │ ├── Select.css │ │ ├── shareOptions.js │ │ ├── mediator.js │ │ ├── TextCounter.js │ │ ├── dispatch.js │ │ ├── jquery.textOverflow.js │ │ ├── friendly.js │ │ ├── Select.js │ │ └── isoDate.js │ └── share │ │ ├── i │ │ ├── bug_b.png │ │ ├── face2.png │ │ ├── thumb.jpg │ │ ├── loader.gif │ │ ├── sprite.png │ │ ├── sprite2.png │ │ └── LinkedIn_Logo16px.png │ │ ├── scripts │ │ └── widgets │ │ │ ├── TabButton.html │ │ │ ├── AddAccount.html │ │ │ ├── DebugPanel.html │ │ │ ├── ServicePanel.html │ │ │ ├── DebugPanel.js │ │ │ ├── TabButton.js │ │ │ ├── AddAccount.js │ │ │ ├── jigFuncs.js │ │ │ ├── AccountPanel.html │ │ │ └── ServicePanel.js │ │ ├── style │ │ ├── win.css │ │ ├── linux.css │ │ └── mac.css │ │ └── index.html ├── apps │ ├── google │ │ ├── google.png │ │ ├── google.webapp │ │ └── google.html │ ├── twitter │ │ ├── twitter.png │ │ ├── twitter.webapp │ │ └── twitter.html │ ├── facebook │ │ ├── facebook.png │ │ ├── facebook.webapp │ │ └── facebook.html │ └── common.js ├── skin │ ├── pinstripe │ │ ├── share-button.png │ │ ├── share-button-active.png │ │ ├── share-button-shared.png │ │ ├── share-button-sharing.png │ │ └── share.css │ ├── winstripe │ │ ├── share-button.png │ │ ├── share-button-active.png │ │ ├── share-button-shared.png │ │ ├── share-button-sharing.png │ │ └── share.css │ └── gnomestripe │ │ ├── share-button.png │ │ ├── share-button-active.png │ │ ├── share-button-shared.png │ │ ├── share-button-sharing.png │ │ └── share.css ├── locale │ └── en-US.properties └── servicesapi.js ├── tests ├── apps │ └── basic │ │ ├── basic.html │ │ ├── basic.webapp │ │ └── basic.js ├── page.html ├── corpus │ ├── shorturl_linkrel.html │ ├── shortlink_linkrel.html │ ├── shorturl_link.html │ ├── og_invalid_url.html │ └── opengraph.html ├── browser │ ├── browser_store.js │ ├── share.html │ ├── browser_panelReady.js │ ├── browser_errorNotification_newtab.js │ ├── head.js │ ├── Makefile.in │ └── keyvaluestore_tests.js ├── test-toggle-key.js ├── test-url-validate.js ├── test-button-state.js ├── test-error-notification.js ├── test-click-button.js ├── unit │ ├── head_helpers.js │ ├── test_securefilestore.js │ └── test_keyvaluestore.js ├── test-bookmark-page.js ├── Makefile.in ├── test-location-change.js ├── test-panel-service-panel.js ├── test_utils.js ├── test-email-addresses.js ├── test-panel-focus.js ├── test-share-options.js ├── test-smtp-send.js ├── test-share-state.js └── test-panel-service-prefs.js ├── package.json ├── dependencies.json ├── README.md ├── Makefile ├── extra └── share.html └── lib ├── addonutils.js ├── email └── mime.js └── overlay.js /share-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/fx-share-addon/HEAD/share-icon.png -------------------------------------------------------------------------------- /data/ui/scripts/jqueryStub.js: -------------------------------------------------------------------------------- 1 | //A stub file used for RequireJS optimizer builds. 2 | define({}); 3 | -------------------------------------------------------------------------------- /data/ui/share/i/bug_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/fx-share-addon/HEAD/data/ui/share/i/bug_b.png -------------------------------------------------------------------------------- /data/ui/share/i/face2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/fx-share-addon/HEAD/data/ui/share/i/face2.png -------------------------------------------------------------------------------- /data/ui/share/i/thumb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/fx-share-addon/HEAD/data/ui/share/i/thumb.jpg -------------------------------------------------------------------------------- /data/apps/google/google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/fx-share-addon/HEAD/data/apps/google/google.png -------------------------------------------------------------------------------- /data/ui/share/i/loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/fx-share-addon/HEAD/data/ui/share/i/loader.gif -------------------------------------------------------------------------------- /data/ui/share/i/sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/fx-share-addon/HEAD/data/ui/share/i/sprite.png -------------------------------------------------------------------------------- /data/ui/share/i/sprite2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/fx-share-addon/HEAD/data/ui/share/i/sprite2.png -------------------------------------------------------------------------------- /data/apps/twitter/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/fx-share-addon/HEAD/data/apps/twitter/twitter.png -------------------------------------------------------------------------------- /data/apps/facebook/facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/fx-share-addon/HEAD/data/apps/facebook/facebook.png -------------------------------------------------------------------------------- /data/skin/pinstripe/share-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/fx-share-addon/HEAD/data/skin/pinstripe/share-button.png -------------------------------------------------------------------------------- /data/skin/winstripe/share-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/fx-share-addon/HEAD/data/skin/winstripe/share-button.png -------------------------------------------------------------------------------- /data/ui/share/i/LinkedIn_Logo16px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/fx-share-addon/HEAD/data/ui/share/i/LinkedIn_Logo16px.png -------------------------------------------------------------------------------- /data/skin/gnomestripe/share-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/fx-share-addon/HEAD/data/skin/gnomestripe/share-button.png -------------------------------------------------------------------------------- /data/skin/gnomestripe/share-button-active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/fx-share-addon/HEAD/data/skin/gnomestripe/share-button-active.png -------------------------------------------------------------------------------- /data/skin/gnomestripe/share-button-shared.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/fx-share-addon/HEAD/data/skin/gnomestripe/share-button-shared.png -------------------------------------------------------------------------------- /data/skin/pinstripe/share-button-active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/fx-share-addon/HEAD/data/skin/pinstripe/share-button-active.png -------------------------------------------------------------------------------- /data/skin/pinstripe/share-button-shared.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/fx-share-addon/HEAD/data/skin/pinstripe/share-button-shared.png -------------------------------------------------------------------------------- /data/skin/pinstripe/share-button-sharing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/fx-share-addon/HEAD/data/skin/pinstripe/share-button-sharing.png -------------------------------------------------------------------------------- /data/skin/winstripe/share-button-active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/fx-share-addon/HEAD/data/skin/winstripe/share-button-active.png -------------------------------------------------------------------------------- /data/skin/winstripe/share-button-shared.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/fx-share-addon/HEAD/data/skin/winstripe/share-button-shared.png -------------------------------------------------------------------------------- /data/skin/winstripe/share-button-sharing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/fx-share-addon/HEAD/data/skin/winstripe/share-button-sharing.png -------------------------------------------------------------------------------- /data/skin/gnomestripe/share-button-sharing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/fx-share-addon/HEAD/data/skin/gnomestripe/share-button-sharing.png -------------------------------------------------------------------------------- /data/ui/scripts/AutoCompleteRefresh.html: -------------------------------------------------------------------------------- 1 |
No matches.
-------------------------------------------------------------------------------- /data/locale/en-US.properties: -------------------------------------------------------------------------------- 1 | sharePageCmd.label=Share Page... 2 | sharePageCmd.accesskey=h 3 | sharePageCmd.tooltip=Share the current page via Twitter, Facebook, and more. 4 | -------------------------------------------------------------------------------- /data/ui/share/scripts/widgets/TabButton.html: -------------------------------------------------------------------------------- 1 | {name} 2 | -------------------------------------------------------------------------------- /tests/apps/basic/basic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | This is a simple app for testing. 7 | 8 | 9 | -------------------------------------------------------------------------------- /data/ui/share/scripts/widgets/AddAccount.html: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /tests/page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Just another web page 5 | 6 | 7 | 8 |

This is just another web page

9 |

with a couple of paragraphs

10 | 11 | 12 | -------------------------------------------------------------------------------- /data/ui/scripts/Select.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 8 |
9 |
10 | -------------------------------------------------------------------------------- /tests/apps/basic/basic.webapp: -------------------------------------------------------------------------------- 1 | { 2 | "name": "F1 Test App", 3 | "installs_allowed_from": [ "*" ], 4 | "icons": { 5 | "128": "/does/not/exist/yet" 6 | }, 7 | "services": { 8 | "link.send": { 9 | "path": "basic.html" 10 | }, 11 | "test": { 12 | "path": "basic.html" 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/corpus/shorturl_linkrel.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | link[rel='shorturl'] 9 | 10 | -------------------------------------------------------------------------------- /tests/corpus/shortlink_linkrel.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | link[rel='shortlink'] 9 | 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fx-share-addon", 3 | "fullName": "Firefox Share (alpha)", 4 | "id": "fx-share-addon@mozilla.org", 5 | "description": "Share links fast", 6 | "author": "Mozilla", 7 | "version": "0.2.3", 8 | "dependencies": ["api-utils", "addon-kit", "activities", "oauthorizer"], 9 | "icon": "./share-icon.png", 10 | "main": "./main.js" 11 | } 12 | -------------------------------------------------------------------------------- /data/apps/google/google.webapp: -------------------------------------------------------------------------------- 1 | { 2 | "name":"GMail via Firefox Share", 3 | "developer": { 4 | "name":"Mozilla Labs Demo", 5 | "url":"http://mozillalabs.com" 6 | }, 7 | "description":"Connects your browser's Firefox Share to GMail", 8 | "icons":{ 9 | "32":"/google.png" 10 | }, 11 | "services": { 12 | "link.send": { 13 | "path": "/google.html" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tests/corpus/shorturl_link.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | link id="shorturl" 9 | 10 | -------------------------------------------------------------------------------- /data/apps/twitter/twitter.webapp: -------------------------------------------------------------------------------- 1 | { 2 | "name":"Twitter via Firefox Share", 3 | "developer": { 4 | "name":"Mozilla Labs Demo", 5 | "url":"http://mozillalabs.com" 6 | }, 7 | "description":"Connects your browser's Firefox Share to Twitter", 8 | "icons":{ 9 | "32":"/twitter.png" 10 | }, 11 | "services": { 12 | "link.send": { 13 | "path": "/twitter.html" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /data/apps/facebook/facebook.webapp: -------------------------------------------------------------------------------- 1 | { 2 | "name":"Facebook via Firefox Share", 3 | "developer": { 4 | "name":"Mozilla Labs Demo", 5 | "url":"http://mozillalabs.com" 6 | }, 7 | "description":"Connects your browser's Firefox Share to Facebook", 8 | "icons":{ 9 | "32":"/facebook.png" 10 | }, 11 | "services": { 12 | "link.send": { 13 | "path": "/facebook.html" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tests/corpus/og_invalid_url.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | Open Graph Test Page 10 | 11 | 12 | -------------------------------------------------------------------------------- /tests/browser/browser_store.js: -------------------------------------------------------------------------------- 1 | /* Any copyright is dedicated to the Public Domain. 2 | http://creativecommons.org/publicdomain/zero/1.0/ */ 3 | 4 | function test() { 5 | waitForExplicitFinish(); 6 | openTab(PAGE_URL, function() { 7 | run_next_test(); 8 | }); 9 | } 10 | 11 | Services.scriptloader.loadSubScript(CHROME_PREFIX + "keyvaluestore_tests.js", 12 | this); 13 | 14 | gTests.push(function tearDown() { 15 | gBrowser.removeCurrentTab(); 16 | cleanup(finish); 17 | }); 18 | -------------------------------------------------------------------------------- /tests/browser/share.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Share Panel Mock 5 | 6 | 7 | 8 |

Share Panel Mock

9 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /tests/corpus/opengraph.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | 11 | Open Graph Test Page 12 | 13 | 14 | -------------------------------------------------------------------------------- /dependencies.json: -------------------------------------------------------------------------------- 1 | { 2 | "project": [ 3 | "git", 4 | "https://github.com/mixedpuppy/fx-share-addon.git", 5 | "develop", 6 | "" 7 | ], 8 | "location": "deps", 9 | "projects": [ 10 | [ 11 | "git", 12 | "https://github.com/mozilla/addon-sdk.git", 13 | "master", 14 | "" 15 | ], 16 | [ 17 | "git", 18 | "https://github.com/mozilla/oauthorizer.git", 19 | "develop", 20 | "" 21 | ], 22 | [ 23 | "git", 24 | "https://github.com/mozilla/activities.git", 25 | "develop", 26 | "" 27 | ] 28 | ] 29 | } -------------------------------------------------------------------------------- /data/skin/gnomestripe/share.css: -------------------------------------------------------------------------------- 1 | panel[type="arrow"].link_send .panel-arrowcontent, 2 | #share-browser { 3 | min-height: 124px; 4 | min-width: 426px; 5 | height: 124px; 6 | width: 426px; 7 | } 8 | 9 | #share-button { 10 | list-style-image: url("../gnomestripe/share-button.png"); 11 | width: auto; 12 | height: auto; 13 | padding: 1px; 14 | } 15 | 16 | #share-button[checked] { 17 | list-style-image: url("../gnomestripe/share-button-active.png"); 18 | } 19 | 20 | #share-button[status="start"] { 21 | list-style-image: url("../gnomestripe/share-button-sharing.png"); 22 | } 23 | 24 | #share-button[status="finished"] { 25 | list-style-image: url("../gnomestripe/share-button-shared.png"); 26 | } 27 | -------------------------------------------------------------------------------- /data/skin/winstripe/share.css: -------------------------------------------------------------------------------- 1 | panel[type="arrow"].link_send browser, 2 | #share-browser { 3 | min-height: 124px; 4 | min-width: 426px; 5 | height: 124px; 6 | width: 426px; 7 | } 8 | 9 | #share-button { 10 | list-style-image: url("../winstripe/share-button.png"); 11 | width: auto; 12 | height: auto; 13 | } 14 | 15 | #share-button[checked] { 16 | list-style-image: url("../winstripe/share-button-active.png"); 17 | } 18 | 19 | #share-button[status="start"] { 20 | list-style-image: url("../winstripe/share-button-sharing.png"); 21 | } 22 | 23 | #share-button[status="finished"] { 24 | list-style-image: url("../winstripe/share-button-shared.png"); 25 | } 26 | 27 | #share-popup .panel-inner-arrowcontent { 28 | padding: 0; 29 | } 30 | -------------------------------------------------------------------------------- /data/skin/pinstripe/share.css: -------------------------------------------------------------------------------- 1 | panel[type="arrow"].link_send browser, 2 | #share-browser { 3 | min-height: 124px; 4 | min-width: 426px; 5 | height: 124px; 6 | width: 426px; 7 | border-radius: 6px; 8 | } 9 | panel[type="arrow"].link_send .panel-arrowcontent, 10 | #share-popup .panel-arrowcontent { 11 | padding: 2px; 12 | } 13 | #share-button { 14 | list-style-image: url("../pinstripe/share-button.png"); 15 | width: auto; 16 | height: auto; 17 | padding: 1px; 18 | } 19 | 20 | #share-button[checked] { 21 | list-style-image: url("../pinstripe/share-button-active.png"); 22 | } 23 | 24 | #share-button[status="start"] { 25 | list-style-image: url("../pinstripe/share-button-sharing.png"); 26 | } 27 | 28 | #share-button[status="finished"] { 29 | list-style-image: url("../pinstripe/share-button-shared.png"); 30 | } 31 | -------------------------------------------------------------------------------- /data/ui/share/scripts/widgets/DebugPanel.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |

debug

5 | developer 6 |
7 |
8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 | -------------------------------------------------------------------------------- /tests/browser/browser_panelReady.js: -------------------------------------------------------------------------------- 1 | /* Any copyright is dedicated to the Public Domain. 2 | http://creativecommons.org/publicdomain/zero/1.0/ */ 3 | 4 | function test() { 5 | waitForExplicitFinish(); 6 | 7 | openTab(PAGE_URL, function() { 8 | // First we expect to see a message with the "panelReady" topic 9 | // which we send ourselves below. 10 | relayMessage({topic: "panelReady"}, function(event) { 11 | let message = JSON.parse(event.data); 12 | is(message.topic, "panelReady"); 13 | 14 | // Panel sends a "shareState" message in response to "panelReady" 15 | next(gShareWindow, "message", function(event) { 16 | let message = JSON.parse(event.data); 17 | is(message.topic, "shareState"); 18 | 19 | gBrowser.removeCurrentTab(); 20 | cleanup(finish); 21 | }); 22 | }); 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /data/ui/share/scripts/widgets/ServicePanel.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | loading... 5 |
6 |
7 |
8 | Twitter or Facebook 9 |
10 |
11 |
Sign in to your account to start sharing links with friends on
12 |
13 |
14 |
15 | 16 |
17 |
18 | This application has failed to load. 19 |

20 |
21 |
22 | -------------------------------------------------------------------------------- /tests/test-toggle-key.js: -------------------------------------------------------------------------------- 1 | /* Any copyright is dedicated to the Public Domain. 2 | http://creativecommons.org/publicdomain/zero/1.0/ */ 3 | 4 | /** 5 | * Open and close the Share panel by hitting the F1 key. 6 | */ 7 | 8 | const {getMediator, getTestUrl, getShareButton, createTab, removeCurrentTab, finalize} = require("./test_utils"); 9 | const { keyPress } = require("api-utils/dom/events/keys"); 10 | const { activeBrowserWindow: { document } } = require("window-utils"); 11 | 12 | exports.testKey = function(test) { 13 | test.waitUntilDone(); 14 | let pageUrl = getTestUrl("page.html"); 15 | 16 | finalize(test, function(finish) { 17 | removeCurrentTab(function() { 18 | finish(); 19 | }); 20 | }); 21 | 22 | createTab(pageUrl, function(tab) { 23 | let share = getMediator(); 24 | share.panel.once("show", function() { 25 | keyPress(document.documentElement, "ESCAPE"); 26 | }); 27 | share.panel.once("hide", function() { 28 | test.assert(true, "keypress open/close panels") 29 | test.done(); 30 | }); 31 | 32 | keyPress(document.documentElement, "F1"); 33 | }); 34 | 35 | } 36 | -------------------------------------------------------------------------------- /data/ui/share/style/win.css: -------------------------------------------------------------------------------- 1 | @color: #454545; 2 | @background: white; 3 | 4 | @tabsBackgroundWebKit: #E1E9F6; 5 | @tabsBackground: #E1E9F6; 6 | @tabsBorderRight: 1px solid #BDCFE8; 7 | 8 | @tabButtonBorder: 1px solid #6490C5; 9 | @tabButtonColor: #688FC2; 10 | @tabButtonBackgroundWebKit: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #DEE9F8), color-stop(100%, #B6DCF9)); 11 | @tabButtonBackground: -moz-linear-gradient(center top , #DEE9F8 0%, #B6DCF9 100%); 12 | 13 | @tabContentBackgroundWebKit: white; 14 | @tabContentBackground: white; 15 | 16 | @shareTypeSectionInputBackground: white; 17 | 18 | @accountPanelInputErrorBorder: 1px solid #FC0000; 19 | @accountPanelInputErrorBackground: white; 20 | @accountPanelErrorColor: #FC0000; 21 | 22 | @buttonShareBorder: 1px solid #1C92C2; 23 | @buttonShareColor: #252525; 24 | @buttonShareTextShadow: 0; 25 | @buttonShareBackgroundWebKit: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #EDF7F9), color-stop(100%, #9BDCF5)); 26 | @buttonShareBackground: -moz-linear-gradient(center top , #EDF7F9 0%, #9BDCF5 100%); 27 | 28 | 29 | @inputBorder: 1px solid #577894; 30 | @inputBoxShadow: 0 0 5px #57BCFF; 31 | -------------------------------------------------------------------------------- /data/ui/share/style/linux.css: -------------------------------------------------------------------------------- 1 | @color: #454545; 2 | @background: white; 3 | 4 | @tabsBackgroundWebKit: #E1E9F6; 5 | @tabsBackground: #E1E9F6; 6 | @tabsBorderRight: 1px solid #BDCFE8; 7 | 8 | @tabButtonBorder: 1px solid #6490C5; 9 | @tabButtonColor: #688FC2; 10 | @tabButtonBackgroundWebKit: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #DEE9F8), color-stop(100%, #B6DCF9)); 11 | @tabButtonBackground: -moz-linear-gradient(center top , #DEE9F8 0%, #B6DCF9 100%); 12 | 13 | @tabContentBackgroundWebKit: white; 14 | @tabContentBackground: white; 15 | 16 | @shareTypeSectionInputBackground: white; 17 | 18 | @accountPanelInputErrorBorder: 1px solid #FC0000; 19 | @accountPanelInputErrorBackground: white; 20 | @accountPanelErrorColor: #FC0000; 21 | 22 | @buttonShareBorder: 1px solid #1C92C2; 23 | @buttonShareColor: #252525; 24 | @buttonShareTextShadow: 0; 25 | @buttonShareBackgroundWebKit: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #EDF7F9), color-stop(100%, #9BDCF5)); 26 | @buttonShareBackground: -moz-linear-gradient(center top , #EDF7F9 0%, #9BDCF5 100%); 27 | 28 | 29 | @inputBorder: 1px solid #577894; 30 | @inputBoxShadow: 0 0 5px #57BCFF; 31 | -------------------------------------------------------------------------------- /tests/test-url-validate.js: -------------------------------------------------------------------------------- 1 | /* Any copyright is dedicated to the Public Domain. 2 | http://creativecommons.org/publicdomain/zero/1.0/ */ 3 | 4 | let {validateURL} = require("fx-share-addon/panel"); 5 | 6 | let bad_urls = [ 7 | "/invalid/path", 8 | "file:///invalid/path", 9 | "http://invalid.comhttp://invalid.com", 10 | "http:///invalid/path", 11 | "chrome://browser/content/aboutDialog.xul", 12 | "http://invalid.com:foo/somepath", 13 | "http://www.invalid.orghttp://s3.www.invalid.org/images/small_logo.png" 14 | ]; 15 | 16 | let good_urls = [ 17 | "http://valid/", 18 | "http://valid.com", 19 | "http://valid.com/somepath", 20 | "http://valid.com:80/somepath", 21 | "https://valid.com:80/somepath#foobar?test=1", 22 | "http://s3.www.valid.org/images/small_logo.png", 23 | "ftp://valid/", 24 | "ftps://valid/" 25 | ]; 26 | 27 | exports.runTest = function(test) { 28 | // First test urls that should fail validation. 29 | for (var i=0; i < bad_urls.length; i++) { 30 | test.assertStrictEqual(validateURL(bad_urls[i]), null); 31 | } 32 | 33 | // Test some good urls now. 34 | for (var i=0; i < good_urls.length; i++) { 35 | test.assertStrictEqual(validateURL(good_urls[i]), good_urls[i]); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | The branch you are looking at is an effort towards getting Firefox Share working 2 | with the [Open Web Apps](https://apps.mozillalabs.com/) infrastructure. 3 | 4 | PreRequisite 5 | =============== 6 | 7 | * Firefox 8 | * Python 9 | * Git 10 | * make 11 | 12 | Getting setup 13 | ===================== 14 | 15 | To pull and run fx-share addon: 16 | 17 | git clone https://github.com/mozilla/fx-share-addon 18 | cd fx-share-addon 19 | make pull 20 | make run 21 | 22 | You can build an xpi: 23 | 24 | make xpi 25 | 26 | You can run the tests: 27 | 28 | make test 29 | 30 | 31 | If you want to run (using make run) in a specific profile: 32 | 33 | OWA_PROFILE=/path/to/firefox/profile make run 34 | 35 | Tests cannot be run in a specific profile. 36 | 37 | 38 | Prepare your firefox profile 39 | ----------------------------- 40 | 41 | You probably want a test firefox profile so open up the [Profile Manager](http://kb.mozillazine.org/Profile_manager). 42 | 43 | In the Mac: 44 | 45 | /Applications/Firefox.app/Contents/MacOS/firefox -ProfileManager 46 | 47 | On Windows: 48 | 49 | firefox.exe -P 50 | 51 | In the profile manager, create a profile with the name `fxsharetest`, then exit the profile manager. 52 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | APPNAME = fx-share-addon 2 | PYTHON = python 3 | 4 | ifeq ($(TOPSRCDIR),) 5 | export TOPSRCDIR = $(shell pwd) 6 | endif 7 | profile := 8 | ifneq ($(FX_PROFILE),) 9 | profile := --profiledir="$(FX_PROFILE)" 10 | endif 11 | 12 | deps := $(TOPSRCDIR)/deps 13 | ifneq ($(DEPSDIR),) 14 | deps := $(DEPSDIR) 15 | endif 16 | 17 | binary := 18 | ifneq ($(MOZ_BINARY),) 19 | binary := -b "$(MOZ_BINARY)" 20 | endif 21 | 22 | addon_sdk := $(deps)/addon-sdk/bin 23 | oauthorizer := $(deps)/oauthorizer 24 | activities := $(deps)/activities 25 | 26 | cfx_args := --pkgdir=$(TOPSRCDIR) $(binary) $(profile) --package-path=$(oauthorizer) --package-path=$(activities) --binary-args="-console -purgecaches" 27 | 28 | test_args := 29 | ifneq ($(TEST),) 30 | test_args := -f $(TEST) 31 | endif 32 | 33 | # might be useful for symlink handling... 34 | SLINK = ln -sf 35 | ifneq ($(findstring MINGW,$(shell uname -s)),) 36 | SLINK = cp -r 37 | export NO_SYMLINK = 1 38 | endif 39 | 40 | all: xpi 41 | 42 | xpi: pull 43 | $(addon_sdk)/cfx xpi --no-strip-xpi $(cfx_args) 44 | 45 | pull: 46 | $(PYTHON) build.py 47 | 48 | test: 49 | $(addon_sdk)/cfx test -v $(cfx_args) $(test_args) 50 | 51 | run: 52 | $(addon_sdk)/cfx run $(cfx_args) 53 | 54 | .PHONY: xpi clean pull test run 55 | -------------------------------------------------------------------------------- /tests/test-button-state.js: -------------------------------------------------------------------------------- 1 | /* Any copyright is dedicated to the Public Domain. 2 | http://creativecommons.org/publicdomain/zero/1.0/ */ 3 | 4 | const {Cc, Ci, Cm, Cu, components} = require("chrome"); 5 | 6 | let tmp = {}; 7 | Cu.import("resource://gre/modules/Services.jsm", tmp); 8 | Cu.import("resource://gre/modules/PlacesUtils.jsm", tmp); 9 | let {Services, PlacesUtils} = tmp; 10 | 11 | let {createSharePanel, getTestUrl, createTab, removeCurrentTab, finalize} = require("./test_utils"); 12 | 13 | exports.testButtonState = function(test) { 14 | test.waitUntilDone(); 15 | let pageUrl = getTestUrl("page.html"); 16 | 17 | finalize(test, function(finish) { 18 | removeCurrentTab(function() { 19 | finish(); 20 | }); 21 | }); 22 | 23 | createTab(pageUrl, function(tab) { 24 | let sharePanel = createSharePanel(); 25 | //sharePanel.show(); 26 | // the panel callback doesn't seem to happen immediately... 27 | test.waitUntil(function() {return sharePanel.anchor.getAttribute("checked");} 28 | ).then(function() { 29 | sharePanel.panel.hide(); // XX - not currently exposed on sharePanel 30 | test.waitUntil(function() {return !sharePanel.anchor.getAttribute("checked")} 31 | ).then(function() { 32 | test.done(); 33 | }); 34 | }); 35 | }); 36 | } 37 | -------------------------------------------------------------------------------- /data/apps/twitter/twitter.html: -------------------------------------------------------------------------------- 1 | 23 | 24 | 25 | 26 | Twitter via F1 OWA 27 | 28 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /data/apps/facebook/facebook.html: -------------------------------------------------------------------------------- 1 | 23 | 24 | 25 | 26 | Facebook via F1 OWA 27 | 28 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /data/ui/share/scripts/widgets/DebugPanel.js: -------------------------------------------------------------------------------- 1 | /* ***** BEGIN LICENSE BLOCK ***** 2 | * Version: MPL 1.1 3 | * 4 | * The contents of this file are subject to the Mozilla Public License Version 5 | * 1.1 (the "License"); you may not use this file except in compliance with 6 | * the License. You may obtain a copy of the License at 7 | * http://www.mozilla.org/MPL/ 8 | * 9 | * Software distributed under the License is distributed on an "AS IS" basis, 10 | * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | * for the specific language governing rights and limitations under the 12 | * License. 13 | * 14 | * The Original Code is Raindrop. 15 | * 16 | * The Initial Developer of the Original Code is 17 | * Mozilla Messaging, Inc.. 18 | * Portions created by the Initial Developer are Copyright (C) 2009 19 | * the Initial Developer. All Rights Reserved. 20 | * 21 | * Contributor(s): 22 | * */ 23 | 24 | /*jslint indent: 2 */ 25 | /*global define: false */ 26 | "use strict"; 27 | 28 | define([ 'blade/object', 'blade/Widget', 'jquery', 'text!./DebugPanel.html'], 29 | function (object, Widget, $, template) { 30 | 31 | //Define the widget. 32 | return object(Widget, null, function (parent) { 33 | return { 34 | template: template 35 | }; 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /data/ui/scripts/blade/array.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license blade/array Copyright (c) 2010, The Dojo Foundation All Rights Reserved. 3 | * Available via the MIT, GPL or new BSD license. 4 | * see: http://github.com/jrburke/blade for details 5 | */ 6 | /*jslint nomen: false, plusplus: false */ 7 | /*global define: false */ 8 | 9 | 'use strict'; 10 | 11 | define([], function () { 12 | var ostring = Object.prototype.toString, 13 | ap = Array.prototype, 14 | aps = ap.slice, 15 | 16 | array = { 17 | /** 18 | * Determines if the input a function. 19 | * @param {Object} it whatever you want to test to see if it is a function. 20 | * @returns Boolean 21 | */ 22 | is: function (it) { 23 | return ostring.call(it) === "[object Array]"; 24 | }, 25 | 26 | /** 27 | * Converts an array-like thing into a real array 28 | * @param{ArrayLike} arrayLike something that looks like an array, 29 | * has a length and can access members via indices. 30 | * @returns {Array} 31 | */ 32 | to: function (arrayLike) { 33 | return [].concat(aps.call(arguments, 0)); 34 | } 35 | }; 36 | 37 | return array; 38 | }); 39 | -------------------------------------------------------------------------------- /tests/test-error-notification.js: -------------------------------------------------------------------------------- 1 | /* Any copyright is dedicated to the Public Domain. 2 | http://creativecommons.org/publicdomain/zero/1.0/ */ 3 | 4 | const {Cc, Ci, Cm, Cu, components} = require("chrome"); 5 | let {getMediator, getTestUrl, createTab, removeCurrentTab, finalize} = require("./test_utils"); 6 | 7 | // test showing the notification box, this could move to owa 8 | exports.testErrorNotification = function(test) { 9 | test.waitUntilDone(); 10 | let pageUrl = getTestUrl("page.html"); 11 | 12 | finalize(test, function(finish) { 13 | removeCurrentTab(function() { 14 | finish(); 15 | }); 16 | }); 17 | 18 | createTab(pageUrl, function(tab) { 19 | let sharePanel = getMediator(); 20 | sharePanel.showErrorNotification({msg: "This is a test error"}); 21 | 22 | let wm = Cc["@mozilla.org/appshell/window-mediator;1"] 23 | .getService(Ci.nsIWindowMediator); 24 | let win = wm.getMostRecentWindow("navigator:browser"); 25 | var gBrowser = win.gBrowser; 26 | 27 | let nBox = gBrowser.getNotificationBox(); 28 | let nId = "openwebapp-error-" + sharePanel.methodName; 29 | let notification = nBox.getNotificationWithValue(nId); 30 | test.assertNotEqual(notification, undefined, "notification-box-showing") 31 | nBox.removeNotification(notification); 32 | 33 | test.done(); 34 | }); 35 | 36 | } 37 | 38 | -------------------------------------------------------------------------------- /tests/test-click-button.js: -------------------------------------------------------------------------------- 1 | const {Cc, Ci, Cm, Cu, components} = require("chrome"); 2 | const {getMediator, getTestUrl, getShareButton, createTab, removeCurrentTab, finalize} = require("./test_utils"); 3 | const events = require("dom/events"); 4 | const { activeBrowserWindow: { document } } = require("window-utils"); 5 | const window = document.window; 6 | 7 | function mouseEvent(element) { 8 | events.emit(element, "click", { 9 | category: "MouseEvents", 10 | settings: [ 11 | true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null 12 | ] 13 | }); 14 | } 15 | 16 | exports.testButton = function(test) { 17 | test.waitUntilDone(); 18 | let pageUrl = getTestUrl("page.html"); 19 | 20 | finalize(test, function(finish) { 21 | removeCurrentTab(function() { 22 | finish(); 23 | }); 24 | }); 25 | 26 | createTab(pageUrl, function(tab) { 27 | let share = getMediator(); 28 | share.panel.once("show", function() { 29 | test.assert(true, "mouse clicks opens panels"); 30 | // close the panel by clicking someplace outside the panel 31 | mouseEvent(share.anchor); 32 | }); 33 | share.panel.once("hide", function() { 34 | test.assert(true, "mouse clicks closes panels"); 35 | test.done(); 36 | }); 37 | 38 | // open the panel by clicking on the urlbar icon 39 | mouseEvent(share.anchor); 40 | }); 41 | } 42 | -------------------------------------------------------------------------------- /data/ui/share/style/mac.css: -------------------------------------------------------------------------------- 1 | @color: #e2e2e2; 2 | @background: #505050; 3 | 4 | @tabsBackgroundWebKit: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #515151), color-stop(100%, #2b2b2b)); 5 | @tabsBackground: -moz-linear-gradient(center top , #515151 0%, #2b2b2b 100%); 6 | @tabsBorderRight: 0; 7 | 8 | @tabButtonBorder: 1px solid #577894; 9 | @tabButtonColor: white; 10 | @tabButtonBackgroundWebKit: #303333; 11 | @tabButtonBackground: #303333; 12 | 13 | @tabContentBackgroundWebKit: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #6f6f6f), color-stop(100%, #444444)); 14 | @tabContentBackground: -moz-linear-gradient(center top , #6f6f6f 0%, #444444 100%); 15 | 16 | @addAccountSelectBackgroundWebKit: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #767676), color-stop(100%, #545454)); 17 | @addAccountSelectBackground: -moz-linear-gradient(center top , #767676 0%, #545454 100%); 18 | 19 | @shareTypeSectionInputBackground: #848484; 20 | 21 | @accountPanelInputErrorBorder: 1px solid #C26464; 22 | @accountPanelInputErrorBackground: #893232; 23 | @accountPanelErrorColor: #C26464; 24 | 25 | @buttonShareBorder: 1px solid #57BCFF; 26 | @buttonShareTextShadow: 0 1px 1px #47AFF8; 27 | @buttonShareColor: #005796; 28 | @buttonShareBackgroundWebKit: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #0097F1), color-stop(100%, #545454)); 29 | @buttonShareBackground: -moz-linear-gradient(center top , #23AEFF 0%, #0097F1 100%); 30 | 31 | @inputBorder: 1px solid #577894; 32 | @inputBoxShadow: 0 0 5px #57BCFF; 33 | -------------------------------------------------------------------------------- /data/ui/share/scripts/widgets/TabButton.js: -------------------------------------------------------------------------------- 1 | /* ***** BEGIN LICENSE BLOCK ***** 2 | * Version: MPL 1.1 3 | * 4 | * The contents of this file are subject to the Mozilla Public License Version 5 | * 1.1 (the "License"); you may not use this file except in compliance with 6 | * the License. You may obtain a copy of the License at 7 | * http://www.mozilla.org/MPL/ 8 | * 9 | * Software distributed under the License is distributed on an "AS IS" basis, 10 | * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | * for the specific language governing rights and limitations under the 12 | * License. 13 | * 14 | * The Original Code is Raindrop. 15 | * 16 | * The Initial Developer of the Original Code is 17 | * Mozilla Messaging, Inc.. 18 | * Portions created by the Initial Developer are Copyright (C) 2009 19 | * the Initial Developer. All Rights Reserved. 20 | * 21 | * Contributor(s): 22 | * */ 23 | 24 | /*jslint indent: 2, plusplus: false, nomen: false */ 25 | /*global define: false, document: false */ 26 | "use strict"; 27 | 28 | define([ 'blade/object', 'blade/fn', 'blade/Widget', 'module', 'Select', 29 | 'jquery', 'text!./TabButton.html'], 30 | function (object, fn, Widget, module, Select, 31 | $, template) { 32 | 33 | var className = module.id.replace(/\//g, '-'); 34 | 35 | 36 | //Define the widget. 37 | return object(Widget, null, function (parent) { 38 | return { 39 | template: template, 40 | className: className 41 | }; 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /tests/unit/head_helpers.js: -------------------------------------------------------------------------------- 1 | const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; 2 | 3 | // Register resource alias. Normally done in ServicesShare.manifest. 4 | function addResourceAlias() { 5 | Cu.import("resource://gre/modules/Services.jsm"); 6 | const resProt = Services.io.getProtocolHandler("resource") 7 | .QueryInterface(Ci.nsIResProtocolHandler); 8 | let uri = Services.io.newURI("resource:///modules/services-share/", null, null); 9 | resProt.setSubstitution("services-share", uri); 10 | } 11 | addResourceAlias(); 12 | 13 | /** 14 | * Runs the next test in the gTests array. gTests should be a array defined in 15 | * each test file. 16 | */ 17 | let gRunningTest = null; 18 | let gTestIndex = 0; // The index of the currently running test. 19 | function run_next_test() 20 | { 21 | function _run_next_test() 22 | { 23 | if (gTestIndex < gTests.length) { 24 | do_test_pending(); 25 | gRunningTest = gTests[gTestIndex++]; 26 | print("TEST-INFO | " + _TEST_FILE + " | Starting " + 27 | gRunningTest.name); 28 | // Exceptions do not kill asynchronous tests, so they'll time out. 29 | try { 30 | gRunningTest(); 31 | } 32 | catch (e) { 33 | do_throw(e); 34 | } 35 | } 36 | } 37 | 38 | // For sane stacks during failures, we execute this code soon, but not now. 39 | do_execute_soon(_run_next_test); 40 | 41 | if (gRunningTest !== null) { 42 | // Close the previous test do_test_pending call. 43 | do_test_finished(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /data/ui/scripts/osTheme.js: -------------------------------------------------------------------------------- 1 | /* ***** BEGIN LICENSE BLOCK ***** 2 | * Version: MPL 1.1 3 | * 4 | * The contents of this file are subject to the Mozilla Public License Version 5 | * 1.1 (the "License"); you may not use this file except in compliance with 6 | * the License. You may obtain a copy of the License at 7 | * http://www.mozilla.org/MPL/ 8 | * 9 | * Software distributed under the License is distributed on an "AS IS" basis, 10 | * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | * for the specific language governing rights and limitations under the 12 | * License. 13 | * 14 | * The Original Code is Raindrop. 15 | * 16 | * The Initial Developer of the Original Code is 17 | * Mozilla Messaging, Inc.. 18 | * Portions created by the Initial Developer are Copyright (C) 2009 19 | * the Initial Developer. All Rights Reserved. 20 | * 21 | * Contributor(s): 22 | * */ 23 | 24 | /*jslint indent: 2 */ 25 | /*global document: false, navigator: false, define: false */ 26 | 'use strict'; 27 | 28 | /** 29 | * Sets up a class on the documentElement that is based on the OS theme. 30 | * this right away, not part of a module, to a flash of unstyled content. 31 | * 32 | * Also returns the name of the OS theme as the module value. 33 | */ 34 | 35 | (function () { 36 | var platform = navigator.platform, 37 | map = { 38 | 'Win': 'win', 39 | 'Mac': 'mac', 40 | 'Linux': 'linux' 41 | }, 42 | theme, prop; 43 | 44 | for (prop in map) { 45 | if (map.hasOwnProperty(prop) && platform.indexOf(prop) !== -1) { 46 | theme = map[prop]; 47 | document.documentElement.className += ' ' + theme; 48 | break; 49 | } 50 | } 51 | 52 | define(function () { 53 | return theme; 54 | }); 55 | }()); -------------------------------------------------------------------------------- /tests/test-bookmark-page.js: -------------------------------------------------------------------------------- 1 | /* Any copyright is dedicated to the Public Domain. 2 | http://creativecommons.org/publicdomain/zero/1.0/ */ 3 | 4 | const {Cc, Ci, Cm, Cu, components} = require("chrome"); 5 | 6 | let tmp = {}; 7 | Cu.import("resource://gre/modules/Services.jsm", tmp); 8 | Cu.import("resource://gre/modules/PlacesUtils.jsm", tmp); 9 | let {Services, PlacesUtils} = tmp; 10 | 11 | let {getMediator, getTestUrl, createTab, removeCurrentTab, finalize} = require("./test_utils"); 12 | 13 | exports.testBookmarkPage = function(test) { 14 | test.waitUntilDone(); 15 | let pageUrl = getTestUrl("page.html"); 16 | 17 | finalize(test, function(finish) { 18 | removeCurrentTab(function() { 19 | finish(); 20 | }); 21 | }); 22 | 23 | createTab(pageUrl, function(tab) { 24 | let bms = Cc["@mozilla.org/browser/nav-bookmarks-service;1"] 25 | .getService(Ci.nsINavBookmarksService); 26 | 27 | let nsiuri = Services.io.newURI(pageUrl, null, null); 28 | 29 | let shareMessage = { 30 | link: pageUrl, 31 | appName: "F1 test suite", 32 | title: 'A test page' 33 | }; 34 | let sharePanel = getMediator(); 35 | let oldPrefVal; 36 | try { 37 | oldPrefVal = Services.prefs.getBoolPref("services.share.bookmarking"); 38 | } catch (ex) { 39 | // oldPrefVal stays undefined. 40 | } 41 | Services.prefs.setBoolPref("services.share.bookmarking", true); 42 | try { 43 | sharePanel.onOWASuccess(shareMessage); 44 | } finally { 45 | if (typeof oldPrefVal !== 'undefined') { 46 | Services.prefs.setBoolPref("services.share.bookmarking", oldPrefVal); 47 | } 48 | } 49 | sharePanel.panel.hide(); 50 | test.assert(bms.isBookmarked(nsiuri)); 51 | let tags = PlacesUtils.tagging.getTagsForURI(nsiuri, {}); 52 | test.assertStrictEqual(tags.length, 1); 53 | test.assertStrictEqual(tags[0], shareMessage.appName); 54 | 55 | test.done(); 56 | }); 57 | } 58 | -------------------------------------------------------------------------------- /data/ui/share/scripts/widgets/AddAccount.js: -------------------------------------------------------------------------------- 1 | /* ***** BEGIN LICENSE BLOCK ***** 2 | * Version: MPL 1.1 3 | * 4 | * The contents of this file are subject to the Mozilla Public License Version 5 | * 1.1 (the "License"); you may not use this file except in compliance with 6 | * the License. You may obtain a copy of the License at 7 | * http://www.mozilla.org/MPL/ 8 | * 9 | * Software distributed under the License is distributed on an "AS IS" basis, 10 | * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | * for the specific language governing rights and limitations under the 12 | * License. 13 | * 14 | * The Original Code is Raindrop. 15 | * 16 | * The Initial Developer of the Original Code is 17 | * Mozilla Messaging, Inc.. 18 | * Portions created by the Initial Developer are Copyright (C) 2009 19 | * the Initial Developer. All Rights Reserved. 20 | * 21 | * Contributor(s): 22 | * */ 23 | 24 | /*jslint indent: 2, plusplus: false, nomen: false */ 25 | /*global define: false, document: false, alert: false */ 26 | "use strict"; 27 | 28 | define([ 'blade/object', 'blade/fn', 'blade/Widget', 'jquery', 29 | 'module', 'dispatch', 'Select', 30 | 'text!./AddAccount.html'], 31 | function (object, fn, Widget, $, 32 | module, dispatch, Select, 33 | template) { 34 | 35 | var className = module.id.replace(/\//g, '-'); 36 | 37 | //Define the widget. 38 | return object(Widget, null, function (parent) { 39 | return { 40 | template: template, 41 | className: className, 42 | 43 | onRender: function () { 44 | 45 | var options = [{name: 'Select type', value: ''}]; 46 | this.owaservices.forEach(function(svc, i) { 47 | options.push({ 48 | name: svc.app.manifest.name, 49 | value: i.toString() 50 | }); 51 | }); 52 | 53 | this.select = new Select({ 54 | name: 'accountType', 55 | options: options 56 | }, $('.add', this.node)[0]); 57 | } 58 | }; 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /data/servicesapi.js: -------------------------------------------------------------------------------- 1 | 2 | var callid = 0; 3 | unsafeWindow.navigator.wrappedJSObject.mozActivities.services.sendEmail = { 4 | call: function(svc, data, callback) { 5 | callid++; 6 | let resultName = "owa.service.sendEmail.call.result."+callid; 7 | self.port.once(resultName, function(result) { 8 | callback(result); 9 | }); 10 | self.port.emit('owa.service.sendEmail.call', { 11 | svc: svc, 12 | data: data, 13 | result: resultName 14 | }); 15 | } 16 | }; 17 | 18 | unsafeWindow.navigator.wrappedJSObject.mozActivities.services.resolveEmailAddresses = { 19 | call: function(data, callback) { 20 | callid++; 21 | let resultName = "owa.service.resolveEmailAddresses.call.result."+callid; 22 | self.port.once(resultName, function(result) { 23 | callback(result); 24 | }); 25 | self.port.emit('owa.service.resolveEmailAddresses.call', { 26 | data: data, 27 | result: resultName 28 | }); 29 | } 30 | }; 31 | 32 | unsafeWindow.navigator.wrappedJSObject.mozActivities.services.formatEmailAddresses = { 33 | call: function(data, callback) { 34 | callid++; 35 | let resultName = "owa.service.formatEmailAddresses.call.result."+callid; 36 | self.port.once(resultName, function(result) { 37 | callback(result); 38 | }); 39 | self.port.emit('owa.service.formatEmailAddresses.call', { 40 | data: data, 41 | result: resultName 42 | }); 43 | } 44 | }; 45 | 46 | // the service is making an oauth call, setup a result callback mechanism then make the call. 47 | // the service will already have oauth credentials from an early login process initiated by 48 | // our mediator 49 | unsafeWindow.navigator.wrappedJSObject.mozActivities.services.oauth = { 50 | call: function(svc, data, callback) { 51 | callid++; 52 | self.port.once("owa.service.oauth.call.result."+callid, function(result) { 53 | callback(result); 54 | }); 55 | self.port.emit('owa.service.oauth.call', { 56 | svc: svc, 57 | data: data, 58 | result: "owa.service.oauth.call.result."+callid 59 | }); 60 | } 61 | }; 62 | -------------------------------------------------------------------------------- /tests/Makefile.in: -------------------------------------------------------------------------------- 1 | # 2 | # ***** BEGIN LICENSE BLOCK ***** 3 | # Version: MPL 1.1/GPL 2.0/LGPL 2.1 4 | # 5 | # The contents of this file are subject to the Mozilla Public License Version 6 | # 1.1 (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # http://www.mozilla.org/MPL/ 9 | # 10 | # Software distributed under the License is distributed on an "AS IS" basis, 11 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 12 | # for the specific language governing rights and limitations under the 13 | # License. 14 | # 15 | # The Original Code is mozilla.org code. 16 | # 17 | # The Initial Developer of the Original Code is 18 | # the Mozilla Foundation. 19 | # Portions created by the Initial Developer are Copyright (C) 2010 20 | # the Initial Developer. All Rights Reserved. 21 | # 22 | # Contributor(s): 23 | # 24 | # Alternatively, the contents of this file may be used under the terms of 25 | # either of the GNU General Public License Version 2 or later (the "GPL"), 26 | # or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 27 | # in which case the provisions of the GPL or the LGPL are applicable instead 28 | # of those above. If you wish to allow use of your version of this file only 29 | # under the terms of either the GPL or the LGPL, and not to allow others to 30 | # use your version of this file under the terms of the MPL, indicate your 31 | # decision by deleting the provisions above and replace them with the notice 32 | # and other provisions required by the GPL or the LGPL. If you do not delete 33 | # the provisions above, a recipient may use your version of this file under 34 | # the terms of any one of the MPL, the GPL or the LGPL. 35 | # 36 | # ***** END LICENSE BLOCK ***** 37 | 38 | DEPTH = ../../.. 39 | topsrcdir = @top_srcdir@ 40 | srcdir = @srcdir@ 41 | VPATH = @srcdir@ 42 | relativesrcdir = services/share/tests 43 | 44 | include $(DEPTH)/config/autoconf.mk 45 | 46 | DIRS += browser 47 | 48 | MODULE = test_services_share 49 | XPCSHELL_TESTS = unit 50 | 51 | include $(topsrcdir)/config/rules.mk 52 | -------------------------------------------------------------------------------- /tests/browser/browser_errorNotification_newtab.js: -------------------------------------------------------------------------------- 1 | /* Any copyright is dedicated to the Public Domain. 2 | http://creativecommons.org/publicdomain/zero/1.0/ */ 3 | 4 | /** 5 | * test share state information 6 | */ 7 | const SHARE_STATUS = ["", "start", "", "finished"]; 8 | const SHARE_DONE = 0; 9 | const SHARE_START = 1; 10 | const SHARE_ERROR = 2; 11 | const SHARE_FINISHED = 3; 12 | 13 | 14 | function test() { 15 | waitForExplicitFinish(); 16 | 17 | openTab(PAGE_URL, function() { 18 | // Panel sends a "shareState" message in response to "getShareState" 19 | relayMessage({topic: "getShareState"}, function(event) { 20 | next(gShareWindow, "message", function(event) { 21 | let message = JSON.parse(event.data); 22 | is(message.topic, "shareState"); 23 | isnot(message.data, undefined); 24 | 25 | // 1. remove the tab 26 | // 2. set a error for the url of the closed tab 27 | // 3. make sure that a background tab was opened, and the error 28 | // notification has been set 29 | next(gBrowser.tabContainer, "TabClose", function (event) { 30 | // we have to wait for the tab to be properly removed from gBrowser 31 | // so that updateStatus fails to get the tab for the PAGE_URL 32 | setTimeout(function() { 33 | // calling updateStatus should now cause a new tab to open 34 | next(gBrowser.tabContainer, "TabOpen", function(event) { 35 | // again, we need to delay until the updateStatus call is actually 36 | // finished 37 | setTimeout(function() { 38 | var browser = gBrowser.getBrowserForTab(event.target); 39 | let nBox = gBrowser.getNotificationBox(browser); 40 | let notification = nBox.getNotificationWithValue("mozilla-f1-share-error"); 41 | isnot(notification, undefined, "notification-box-showing") 42 | nBox.removeNotification(notification); 43 | 44 | gBrowser.removeTab(event.target); 45 | cleanup(finish); 46 | }, 0); 47 | }); 48 | ffshare.sharePanel.updateStatus([SHARE_ERROR,,,PAGE_URL]); 49 | }, 0); 50 | }); 51 | gBrowser.removeCurrentTab(); 52 | }); 53 | }); 54 | ffshare.togglePanel(); 55 | }); 56 | 57 | } 58 | 59 | -------------------------------------------------------------------------------- /data/ui/scripts/blade/fn.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license blade/func Copyright (c) 2010, The Dojo Foundation All Rights Reserved. 3 | * Available via the MIT, GPL or new BSD license. 4 | * see: http://github.com/jrburke/blade for details 5 | */ 6 | /*jslint nomen: false, plusplus: false */ 7 | /*global define: false */ 8 | 9 | 'use strict'; 10 | 11 | define([], function () { 12 | var slice = Array.prototype.slice, 13 | ostring = Object.prototype.toString, 14 | 15 | fn = { 16 | /** 17 | * Determines if the input a function. 18 | * @param {Object} it whatever you want to test to see if it is a function. 19 | * @returns Boolean 20 | */ 21 | is: function (it) { 22 | return ostring.call(it) === '[object Function]'; 23 | }, 24 | 25 | /** 26 | * Different from Function.prototype.bind in ES5 -- 27 | * it has the "this" argument listed first. This is generally 28 | * more readable, since the "this" object is visible before 29 | * the function body, reducing chances for error by missing it. 30 | * If only obj has a real value then obj will be returned, 31 | * allowing this method to be called even if you are not aware 32 | * of the format of the obj and f types. 33 | * It also allows the function to be a string name, in which case, 34 | * obj[f] is used to find the function. 35 | * @param {Object||Function} obj the "this" object, or a function. 36 | * @param {Function||String} f the function of function name that 37 | * should be called with obj set as the "this" value. 38 | * @returns {Function} 39 | */ 40 | bind: function (obj, f) { 41 | //Do not bother if 42 | if (!f) { 43 | return obj; 44 | } 45 | 46 | //Make sure we have a function 47 | if (typeof f === 'string') { 48 | f = obj[f]; 49 | } 50 | var args = slice.call(arguments, 2); 51 | return function () { 52 | return f.apply(obj, args.concat(slice.call(arguments, 0))); 53 | }; 54 | } 55 | }; 56 | 57 | return fn; 58 | }); 59 | -------------------------------------------------------------------------------- /tests/test-location-change.js: -------------------------------------------------------------------------------- 1 | /* Any copyright is dedicated to the Public Domain. 2 | http://creativecommons.org/publicdomain/zero/1.0/ */ 3 | 4 | /** 5 | * test progress listener catching a location change and updating the share 6 | * button state 7 | */ 8 | 9 | const {Cc, Ci, Cm, Cu, components} = require("chrome"); 10 | 11 | let {getTestUrl, getShareButton} = require("./test_utils"); 12 | 13 | 14 | exports.testChangeInSameTab = function(test) { 15 | test.waitUntilDone(); 16 | let pageUrl = getTestUrl("page.html"); 17 | 18 | const tabs = require("tabs"); // From addon-kit 19 | tabs.open({ 20 | url: pageUrl, 21 | onReady: function(tab) { 22 | let shareButton = getShareButton(); 23 | // The button is visible now, but the panel is closed at first. 24 | if (tab.url === "about:blank") { 25 | // must be second time around - should be hidden. 26 | test.assert(shareButton.hidden); 27 | tab.close(function() { 28 | test.done(); 29 | }); 30 | } else { 31 | // first time around - on our test page, so should be visible. 32 | test.assert(!shareButton.hidden); 33 | // now change the URL to about:blank - this will cause our onReady 34 | // to be called again. 35 | tab.url = "about:blank"; 36 | } 37 | }}); 38 | } 39 | 40 | const { activateTab } = require("tabs/utils"); 41 | 42 | exports.testChangeInDifferentTab = function(test) { 43 | test.waitUntilDone(); 44 | let pageUrl = getTestUrl("page.html"); 45 | 46 | const tabs = require("tabs"); // From addon-kit 47 | tabs.open({ 48 | url: pageUrl, 49 | onReady: function(tabWithPage) { 50 | let shareButton = getShareButton(); 51 | test.assert(!shareButton.hidden); 52 | // now create a second tab with about:blank. 53 | tabs.open({ 54 | url: "about:blank", 55 | onReady: function(tabWithBlank) { 56 | test.assert(shareButton.hidden); 57 | // re-activate the first tab - should not be hidden. 58 | tabWithPage.activate(); 59 | test.waitUntil(function() {return !shareButton.hidden} 60 | ).then(function () { 61 | // re-activate about:blank - should go back to hidden. 62 | tabWithBlank.activate(); 63 | test.waitUntil(function() {return shareButton.hidden} 64 | ).then(function () { 65 | tabWithBlank.close(function() { 66 | tabWithPage.close(function () { 67 | test.done(); 68 | }) 69 | }); 70 | }) 71 | }); 72 | }}); 73 | } 74 | }); 75 | } 76 | -------------------------------------------------------------------------------- /data/ui/share/scripts/widgets/jigFuncs.js: -------------------------------------------------------------------------------- 1 | /* ***** BEGIN LICENSE BLOCK ***** 2 | * Version: MPL 1.1 3 | * 4 | * The contents of this file are subject to the Mozilla Public License Version 5 | * 1.1 (the "License"); you may not use this file except in compliance with 6 | * the License. You may obtain a copy of the License at 7 | * http://www.mozilla.org/MPL/ 8 | * 9 | * Software distributed under the License is distributed on an "AS IS" basis, 10 | * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | * for the specific language governing rights and limitations under the 12 | * License. 13 | * 14 | * The Original Code is Raindrop. 15 | * 16 | * The Initial Developer of the Original Code is 17 | * Mozilla Messaging, Inc.. 18 | * Portions created by the Initial Developer are Copyright (C) 2009 19 | * the Initial Developer. All Rights Reserved. 20 | * 21 | * Contributor(s): 22 | * */ 23 | 24 | /*jslint indent: 2, plusplus: false */ 25 | /*global define: false */ 26 | "use strict"; 27 | 28 | define(['blade/jig'], function (jig) { 29 | 30 | var funcs = { 31 | thumb: function (options) { 32 | var preview = options.previews && options.previews[0]; 33 | if (!preview) { 34 | return ""; 35 | } 36 | if (preview.http_url) { 37 | return jig.htmlEscape(preview.http_url); 38 | } 39 | // Return our data url, this is the thumbnail 40 | return preview.base64; 41 | }, 42 | preview: function (options) { 43 | var preview = options.previews && options.previews[0]; 44 | return preview && preview.http_url; 45 | }, 46 | preview_base64: function (options) { 47 | // Strip the URL down to just the base64 content 48 | var preview = options.previews && options.previews[0]; 49 | return preview && funcs.rawBase64(preview.base64); 50 | }, 51 | cleanLink: function (url) { 52 | return url ? url.replace(/^https?:\/\//, '').replace(/^www\./, '') : url; 53 | }, 54 | profilePic: function (photos) { 55 | //TODO: check for a thumbnail picture, hopefully one that is square. 56 | return photos && photos[0] && photos[0].value || 'i/face2.png'; 57 | }, 58 | lastToShareType: function (shareTypes) { 59 | var i, shareType; 60 | for (i = shareTypes.length - 1; (shareType = shareTypes[i]); i--) { 61 | if (shareType.toLabel) { 62 | return shareType; 63 | } 64 | } 65 | return null; 66 | }, 67 | rawBase64: function (dataUrl) { 68 | return dataUrl && dataUrl.replace("data:image/png;base64,", ""); 69 | } 70 | }; 71 | 72 | jig.addFn(funcs); 73 | 74 | return funcs; 75 | }); -------------------------------------------------------------------------------- /data/apps/common.js: -------------------------------------------------------------------------------- 1 | /* ***** BEGIN LICENSE BLOCK ***** 2 | * Version: MPL 1.1 3 | * 4 | * The contents of this file are subject to the Mozilla Public License Version 5 | * 1.1 (the "License"); you may not use this file except in compliance with 6 | * the License. You may obtain a copy of the License at 7 | * http://www.mozilla.org/MPL/ 8 | * 9 | * Software distributed under the License is distributed on an "AS IS" basis, 10 | * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | * for the specific language governing rights and limitations under the 12 | * License. 13 | * 14 | * The Original Code is Raindrop. 15 | * 16 | * The Initial Developer of the Original Code is 17 | * Mozilla Messaging, Inc.. 18 | * Portions created by the Initial Developer are Copyright (C) 2009 19 | * the Initial Developer. All Rights Reserved. 20 | * 21 | * Contributor(s): 22 | * */ 23 | 24 | /*jslint plusplus: false, indent: 2 */ 25 | /*global define: false, location: true, window: false, alert: false, 26 | document: false, setTimeout: false, localStorage: false */ 27 | "use strict"; 28 | 29 | define([ "require"], 30 | function (require) { 31 | 32 | var common = function() { 33 | 34 | }; 35 | common.prototype = { 36 | 37 | getLogin: function(domain, activity, credentials) { 38 | var key = "ff-share-" + domain; 39 | var strval = window.localStorage.getItem(key); 40 | var result = {}; 41 | try { 42 | if (strval) { 43 | var raw = JSON.parse(strval); 44 | // Turn the nested object into a flat one with profile and info all in one, 45 | // as required by the OWA APIs. 46 | var acct = raw.profile.accounts[0]; 47 | var retUser = {}; 48 | for (var attr in raw.profile) { 49 | if (raw.profile.hasOwnProperty(attr)) retUser[attr] = raw.profile[attr]; 50 | } 51 | for (var attr in acct) { 52 | if (acct.hasOwnProperty(attr)) retUser[attr] = acct[attr]; 53 | } 54 | result.user = retUser; 55 | } 56 | } catch(e) { 57 | dump("common.getLogin error "+e+"\n"); 58 | // some error, logout 59 | activity.postException({code: "get.login", message: e.toString(), data: { key: key, value: strval}}); 60 | window.localStorage.removeItem(key); 61 | return; 62 | } 63 | activity.postResult(result); 64 | }, 65 | 66 | logout: function(domain, activity, credentials) { 67 | var key = "ff-share-" + domain; 68 | window.localStorage.removeItem(key); 69 | activity.postResult({status: "ok"}); 70 | } 71 | } 72 | 73 | return new common(); 74 | }); 75 | -------------------------------------------------------------------------------- /tests/browser/head.js: -------------------------------------------------------------------------------- 1 | Components.utils.import("resource://services-share/store.js"); 2 | 3 | const CHROME_PREFIX = "chrome://mochitests/content/browser/services/share/tests/browser/"; 4 | const PREFIX = "http://mochi.test:8888/browser/services/share/tests/browser/"; 5 | const SHARE_URL = PREFIX + "share.html"; 6 | const PAGE_URL = PREFIX + "page.html"; 7 | 8 | Services.prefs.setCharPref("services.share.shareURL", SHARE_URL); 9 | Services.prefs.setCharPref("services.share.settingsURL", SHARE_URL); 10 | 11 | // gShareWindow is gShareBrowser's window object. It's dynamic because 12 | // it can change. 13 | let gShareBrowser = document.getElementById("share-browser"); 14 | this.__defineGetter__("gShareWindow", function() { 15 | return gShareBrowser.contentWindow.wrappedJSObject; 16 | }); 17 | 18 | /** 19 | * Register a one-time event handler. 20 | */ 21 | function next(element, event, callback) { 22 | element.addEventListener(event, function handler() { 23 | element.removeEventListener(event, handler, false); 24 | callback.apply(this, arguments); 25 | }, false); 26 | } 27 | 28 | /** 29 | * Register a one-time event handler with 'useCapture = true'. 30 | */ 31 | function nextCaptured(element, event, callback) { 32 | element.addEventListener(event, function handler() { 33 | element.removeEventListener(event, handler, true); 34 | callback.apply(this, arguments); 35 | }, true); 36 | } 37 | 38 | /** 39 | * Relay a message to the share window. 40 | */ 41 | function relayMessage(message, callback) { 42 | next(gShareWindow, "message", callback); 43 | gShareWindow.relayMessage(message); 44 | } 45 | 46 | /** 47 | * Open a new tab. 48 | */ 49 | function openTab(url, callback) { 50 | gBrowser.selectedTab = gBrowser.addTab(); 51 | next(gBrowser.selectedBrowser, "DOMContentLoaded", callback); 52 | content.location = url; 53 | } 54 | 55 | /** 56 | * Clean up test fixtures. 57 | */ 58 | function cleanup(callback) { 59 | ffshare.sharePanel.close(); 60 | try { 61 | Services.prefs.clearUserPref("services.share.shareURL"); 62 | Services.prefs.clearUserPref("services.share.settingsURL"); 63 | } catch (ex if ex.result == Cr.NS_ERROR_UNEXPECTED) { 64 | // The pref wasn't touched. 65 | } 66 | SecureFileStore.clear(); 67 | callback(); 68 | } 69 | 70 | /** 71 | * Chain asynchronous tests. 72 | */ 73 | let gTimer = Components.classes["@mozilla.org/timer;1"] 74 | .createInstance(Components.interfaces.nsITimer); 75 | let gTestIndex = 0; 76 | let gTests = []; 77 | 78 | function run_next_test() { 79 | let test = gTests[gTestIndex++]; 80 | gTimer.initWithCallback({notify: test}, 0, 81 | Components.interfaces.nsITimer.TYPE_ONE_SHOT); 82 | } 83 | -------------------------------------------------------------------------------- /extra/share.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Share Example 4 | 5 | 36 | 50 | 51 | 52 |
53 | 54 |

share demonstration

55 |

56 | This page demonstrates the use of the invokeService function. When you click the 57 | "Share" button, below, the page will ask your browser to connect it to an application 58 | that provides the "link.send" service. 59 |

60 | 61 |

62 | What happens next is up to your browser. If you are using a browser that understands the 63 | service discovery API, you may see a dialog box, "doorhanger" or other native user interface 64 | element. If you are not, you may see an HTML-native implementation of the service discovery 65 | API (depending on whether we've implemented that yet). 66 |

67 | 68 | 69 |
70 |
71 | 78 | 85 |
86 |
87 | 88 |
89 |
90 | 91 | 94 | 95 | -------------------------------------------------------------------------------- /data/ui/scripts/Select.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | /* TODO: some of these styles, like text sizing/box sizing and colors/borders 4 | need to be set in the app that includes a Select widget. */ 5 | .Select { 6 | position:relative; 7 | display: inline-block; 8 | padding-right: 15px; 9 | z-index: 100; 10 | border: 1px solid #303030; 11 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15); 12 | height: 24px; 13 | cursor: pointer; 14 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #767676), color-stop(100%, #545454)); 15 | background-image: -moz-linear-gradient(center top , #767676 0%, #545454 100%); 16 | text-align: left; 17 | border-radius: 4px; 18 | } 19 | 20 | .win .Select, 21 | .linux .Select { 22 | border: 1px solid #85818C; 23 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #FDFDFD), color-stop(100%, #DDDBDC)); 24 | background-image: -moz-linear-gradient(center top , #FDFDFD 0%, #DDDBDC 100%); 25 | } 26 | 27 | .Select.open { 28 | padding-right: 0; 29 | border: 0; 30 | } 31 | 32 | .Select ul { 33 | list-style: none; 34 | z-index: 100; 35 | } 36 | 37 | .Select.open ul { 38 | position: absolute; 39 | top: -30px; 40 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #767676), color-stop(100%, #545454)); 41 | background-image: -moz-linear-gradient(center top , #767676 0%, #545454 100%); 42 | border: 1px solid gray; 43 | } 44 | 45 | .win .Select.open ul, 46 | .linux .Select.open ul { 47 | background-image: none; 48 | background-color: white; 49 | } 50 | 51 | .Select li { 52 | display: none; 53 | height: 0; 54 | padding: 0 5px; 55 | white-space: nowrap; 56 | } 57 | 58 | .Select li.selected { 59 | display: block; 60 | height: 24px; 61 | line-height: 24px; 62 | } 63 | 64 | .Select.open li { 65 | display: block; 66 | height: 24px; 67 | line-height: 24px; 68 | padding-right: 20px; 69 | } 70 | 71 | .Select.open li.selected { 72 | /* background-color: grey; */ 73 | } 74 | 75 | .Select.open li.selected:hover, 76 | .Select.open li:hover { 77 | color:white; 78 | background-color: #2b2b2b; 79 | } 80 | 81 | .win .Select.open li.selected:hover, 82 | .win .Select.open li:hover, 83 | .linux .Select.open li.selected:hover, 84 | .linux .Select.open li:hover { 85 | color: inherit; 86 | background-color: #E1E9F6; 87 | } 88 | 89 | .Select .triangle { 90 | position: absolute; 91 | right: 0; 92 | top: 0; 93 | width: 18px; 94 | height: 24px; 95 | line-height: 24px; 96 | padding: 0 2px; 97 | z-index: 101; 98 | background-image: url("i/sprite2.png"); 99 | background-position: center -362px; 100 | background-repeat: no-repeat; 101 | } 102 | 103 | .Select.open .triangle { 104 | display: none; 105 | } 106 | -------------------------------------------------------------------------------- /tests/browser/Makefile.in: -------------------------------------------------------------------------------- 1 | # 2 | # ***** BEGIN LICENSE BLOCK ***** 3 | # Version: MPL 1.1/GPL 2.0/LGPL 2.1 4 | # 5 | # The contents of this file are subject to the Mozilla Public License Version 6 | # 1.1 (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # http://www.mozilla.org/MPL/ 9 | # 10 | # Software distributed under the License is distributed on an "AS IS" basis, 11 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 12 | # for the specific language governing rights and limitations under the 13 | # License. 14 | # 15 | # The Original Code is mozilla.org code. 16 | # 17 | # The Initial Developer of the Original Code is 18 | # the Mozilla Foundation. 19 | # Portions created by the Initial Developer are Copyright (C) 2011 20 | # the Initial Developer. All Rights Reserved. 21 | # 22 | # Contributor(s): 23 | # 24 | # Alternatively, the contents of this file may be used under the terms of 25 | # either of the GNU General Public License Version 2 or later (the "GPL"), 26 | # or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 27 | # in which case the provisions of the GPL or the LGPL are applicable instead 28 | # of those above. If you wish to allow use of your version of this file only 29 | # under the terms of either the GPL or the LGPL, and not to allow others to 30 | # use your version of this file under the terms of the MPL, indicate your 31 | # decision by deleting the provisions above and replace them with the notice 32 | # and other provisions required by the GPL or the LGPL. If you do not delete 33 | # the provisions above, a recipient may use your version of this file under 34 | # the terms of any one of the MPL, the GPL or the LGPL. 35 | # 36 | # ***** END LICENSE BLOCK ***** 37 | 38 | DEPTH = ../../../.. 39 | topsrcdir = @top_srcdir@ 40 | srcdir = @srcdir@ 41 | VPATH = @srcdir@ 42 | relativesrcdir = services/share/tests/browser 43 | 44 | include $(DEPTH)/config/autoconf.mk 45 | 46 | _BROWSER_TEST_FILES = \ 47 | corpus \ 48 | head.js \ 49 | keyvaluestore_tests.js \ 50 | share.html \ 51 | page.html \ 52 | browser_aaLoadPanel.js \ 53 | browser_bookmarkPage.js \ 54 | browser_errorNotification.js \ 55 | browser_errorNotification_newtab.js \ 56 | browser_escape_key.js \ 57 | browser_locationChange.js \ 58 | browser_messageWhitelist.js \ 59 | browser_networkDown.js \ 60 | browser_panelReady.js \ 61 | browser_prefs.js \ 62 | browser_shareOptions.js \ 63 | browser_store.js \ 64 | browser_togglePanel_button.js \ 65 | browser_togglePanel_key.js \ 66 | $(NULL) 67 | 68 | libs:: $(_BROWSER_TEST_FILES) 69 | $(PYTHON) $(topsrcdir)/config/nsinstall.py $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir) 70 | 71 | include $(topsrcdir)/config/rules.mk 72 | -------------------------------------------------------------------------------- /data/ui/scripts/shareOptions.js: -------------------------------------------------------------------------------- 1 | /* ***** BEGIN LICENSE BLOCK ***** 2 | * Version: MPL 1.1 3 | * 4 | * The contents of this file are subject to the Mozilla Public License Version 5 | * 1.1 (the "License"); you may not use this file except in compliance with 6 | * the License. You may obtain a copy of the License at 7 | * http://www.mozilla.org/MPL/ 8 | * 9 | * Software distributed under the License is distributed on an "AS IS" basis, 10 | * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | * for the specific language governing rights and limitations under the 12 | * License. 13 | * 14 | * The Original Code is Raindrop. 15 | * 16 | * The Initial Developer of the Original Code is 17 | * Mozilla Messaging, Inc.. 18 | * Portions created by the Initial Developer are Copyright (C) 2009 19 | * the Initial Developer. All Rights Reserved. 20 | * 21 | * Contributor(s): 22 | * */ 23 | 24 | /*jslint indent: 2 */ 25 | /*global define: false, location: false */ 26 | "use strict"; 27 | 28 | define(['blade/url'], function (url) { 29 | var cache = {}; 30 | 31 | function shareOptions(str) { 32 | str = str || 33 | (typeof location !== 'undefined' && location.href.split('#')[1]) || 34 | ''; 35 | 36 | //If a cached value is available, return that, since the cached 37 | //options value may have properties added/adjusted by the other modules. 38 | if (cache[str]) { 39 | return cache[str]; 40 | } 41 | 42 | var options = {}, 43 | vimeoCdnRegExp = /vimeocdn\.com\//, 44 | vimeoSourceRegExp = /clip_id=(\d+)/, 45 | urlArgs, source, videoId; 46 | 47 | if (str) { 48 | urlArgs = url.queryToObject(str); 49 | if (urlArgs.options) { 50 | options = JSON.parse(urlArgs.options); 51 | } 52 | } 53 | 54 | if (!options.title) { 55 | options.title = options.url; 56 | } 57 | 58 | source = options.source; 59 | 60 | //If the source is larger than ~4KB then it will exceed the GET size 61 | //limits in most browsers, so discard it. 62 | if (source && source.length > 4000) { 63 | source = ''; 64 | delete options.source; 65 | } 66 | 67 | //START domain-specific hacks 68 | // vimeo.com does not give a usable video embed, fix it up here. 69 | if (source && vimeoCdnRegExp.test(source)) { 70 | videoId = vimeoSourceRegExp.exec(source); 71 | videoId = videoId && videoId[1]; 72 | if (videoId) { 73 | options.source = 'http://vimeo.com/moogaloop.swf?clip_id=' + videoId + '&server=vimeo.com&show_title=1&show_byline=1&show_portrait=1&color=00dcdc&fullscreen=1&autoplay=0&loop=0'; 74 | } else { 75 | delete options.source; 76 | } 77 | } 78 | //END domain-specific hacks. 79 | 80 | cache[str] = options; 81 | 82 | return options; 83 | } 84 | 85 | return shareOptions; 86 | }); 87 | -------------------------------------------------------------------------------- /tests/test-panel-service-panel.js: -------------------------------------------------------------------------------- 1 | // Test the F1 "ServicePanel" 2 | const {getMediatorWithApp, testAppSequence} = require("./app_helpers"); 3 | 4 | exports.testAccountLoads = function(test) { 5 | test.waitUntilDone(); 6 | 7 | getMediatorWithApp(test, {}, function(appInfo) { 8 | let {jqAppWidget} = appInfo; 9 | let accountLoadingDiv = jqAppWidget.find(".accountLoading"); 10 | let accountLoginDiv = jqAppWidget.find(".accountLogin"); 11 | let accountPanelDiv = jqAppWidget.find(".accountPanel"); 12 | // app is "blocked" in getParameters, so only the "loading" div should be visible. 13 | test.assert(accountLoadingDiv.is(":visible")); 14 | test.assert(!accountLoginDiv.is(":visible")); 15 | test.assert(!accountPanelDiv.is(":visible")); 16 | 17 | // now kick off the sequence of "unblocking" calls and testing each state. 18 | let seq = [ 19 | {method: 'getParameters', 20 | successArgs: {shareTypes: [{type: "test", name: "test"}]}, 21 | callback: function(cbresume, results) { 22 | // only 'loading' should still be visible as getLogin is "blocked" 23 | test.assert(accountLoadingDiv.is(":visible")); 24 | test.assert(!accountLoginDiv.is(":visible")); 25 | test.assert(!accountPanelDiv.is(":visible")); 26 | cbresume(); 27 | } 28 | }, 29 | {method: 'getLogin', successArgs: {user: {displayName: 'test user'}}, 30 | callback: function(cbresume) { 31 | // We returned a user, so the account panel should become visible. 32 | test.waitUntil(function() {return accountPanelDiv.is(":visible");} 33 | ).then(function() { 34 | test.assert(!accountLoadingDiv.is(":visible")); 35 | test.assert(!accountLoginDiv.is(":visible")); 36 | cbresume(); 37 | }) 38 | } 39 | } 40 | ]; 41 | testAppSequence(test, appInfo, seq); 42 | }); 43 | }; 44 | 45 | exports.testLoginPanelShows = function(test) { 46 | test.waitUntilDone(); 47 | 48 | getMediatorWithApp(test, {}, function(appInfo) { 49 | let {jqAppWidget} = appInfo; 50 | let accountLoadingDiv = jqAppWidget.find(".accountLoading"); 51 | let accountLoginDiv = jqAppWidget.find(".accountLogin"); 52 | let accountPanelDiv = jqAppWidget.find(".accountPanel"); 53 | // app is "blocked" in getParameters - and we've tested this state 54 | // above - so just kick off the sequence of unblocks and tests. 55 | let seq = [ 56 | // no callback for getParameters - we've tested this above. 57 | {method: 'getParameters', 58 | successArgs: {shareTypes: [{type: "test", name: "test"}]} 59 | }, 60 | {method: 'getLogin', successArgs: {auth: "something"}, 61 | callback: function(cbresume) { 62 | // We returned no user but auth info - the login panel should become visible. 63 | test.waitUntil(function() {return accountLoginDiv.is(":visible");} 64 | ).then(function() { 65 | test.assert(!accountLoadingDiv.is(":visible")); 66 | test.assert(!accountPanelDiv.is(":visible")); 67 | cbresume(); 68 | }) 69 | } 70 | } 71 | ]; 72 | testAppSequence(test, appInfo, seq); 73 | }); 74 | }; 75 | -------------------------------------------------------------------------------- /data/ui/scripts/mediator.js: -------------------------------------------------------------------------------- 1 | /* ***** BEGIN LICENSE BLOCK ***** 2 | * Version: MPL 1.1 3 | * 4 | * The contents of this file are subject to the Mozilla Public License Version 5 | * 1.1 (the "License"); you may not use this file except in compliance with 6 | * the License. You may obtain a copy of the License at 7 | * http://www.mozilla.org/MPL/ 8 | * 9 | * Software distributed under the License is distributed on an "AS IS" basis, 10 | * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | * for the specific language governing rights and limitations under the 12 | * License. 13 | * 14 | * The Original Code is Raindrop. 15 | * 16 | * The Initial Developer of the Original Code is 17 | * Mozilla Messaging, Inc.. 18 | * Portions created by the Initial Developer are Copyright (C) 2009 19 | * the Initial Developer. All Rights Reserved. 20 | * 21 | * Contributor(s): 22 | * */ 23 | 24 | /*jslint plusplus: false, indent: 2, nomen: false */ 25 | /*global require: false, define: false, location: true, window: false, alert: false, 26 | document: false, setTimeout: false, localStorage: false, parent: false, 27 | console: false */ 28 | "use strict"; 29 | 30 | /* 31 | * mediator 32 | * 33 | * implements all api's that need to call INTO chrome from the panel content 34 | * via postmessage or port calls 35 | */ 36 | 37 | define(function () { 38 | var port = window.navigator.mozActivities.mediation.port; 39 | var m = { 40 | on: port.on, 41 | removeListener: port.removeListener, 42 | 43 | /** 44 | * checkBase64Preview 45 | * the current result will be sent via a post message to base64Preview 46 | */ 47 | checkBase64Preview: function(options) { 48 | //Ask extension to generate base64 data if none available. 49 | //Useful for sending previews in email. 50 | var preview = options.previews && options.previews[0]; 51 | if (preview && preview.http_url && !preview.base64) { 52 | port.emit('fxshare.generateBase64Preview', preview.http_url); 53 | } 54 | }, 55 | 56 | /** 57 | * hide 58 | * 59 | * hide the mediator panel 60 | */ 61 | hide: function() { 62 | port.emit('hide'); 63 | }, 64 | 65 | /** 66 | * XXX prefs panel has been removed 67 | */ 68 | openPrefs: function() { 69 | port.emit('openPrefs'); 70 | }, 71 | 72 | sizeToContent: function() { 73 | // use our wrapper to get the right size, using the body element 74 | // for sizing is not working since body.style.overflow=hidden. 75 | var wrapper = document.getElementById("wrapper"); 76 | var args = { 77 | width: wrapper.scrollWidth +4, 78 | height: wrapper.scrollHeight +4 79 | }; 80 | port.emit('owa.mediation.sizeToContent', args); 81 | }, 82 | 83 | updateChromeStatus: function(status) { 84 | port.emit('fxshare.updateStatus', status); 85 | }, 86 | 87 | // This is the 'success' notification defined by OWA. 88 | result: function(resultInfo) { 89 | port.emit('owa.success', resultInfo); 90 | }, 91 | 92 | error: function(message) { 93 | port.emit('owa.failure', {message: message}); 94 | } 95 | } 96 | return m; 97 | 98 | }); 99 | -------------------------------------------------------------------------------- /tests/apps/basic/basic.js: -------------------------------------------------------------------------------- 1 | // A simple, testable web-app implementing the link share service. 2 | 3 | // Most of the calls in this app "block" until told by the test framework to 4 | // "resume" (where "block" means the callback isn't called). 5 | // pendingCalls is calls made into the service where the 'result' of the call 6 | // hasn't yet been specified by the test framework. 7 | var pendingCalls = {}; 8 | var isFinished = false; 9 | 10 | // A 'test' service used by the tests to control this app. 11 | navigator.mozActivities.services.registerHandler('test', 'resume', function(activity, credentials) { 12 | // resume from a 'blocked' call. 13 | var args = activity.data; 14 | var attemptNum = 0; 15 | function doit() { 16 | if (isFinished) { 17 | activity.postException({code: "test_suite_error", message: "this app has been finalized"}); 18 | return; 19 | } 20 | if (!(args.method in pendingCalls)) { 21 | // so the call hasn't yet been made to the app - sleep and retry later. 22 | if (++attemptNum > 100) { 23 | activity.postException({code: "test_error", message: "gave up waiting for a call to '" + args.method + "'"}); 24 | return; 25 | } 26 | setTimeout(doit, 100); 27 | return; 28 | } 29 | var pendingRec = pendingCalls[args.method]; 30 | delete pendingCalls[args.method]; 31 | 32 | // make the pending callback with the args supplied by the test suite 33 | if (args.successArgs) { 34 | pendingRec.postResult(args.successArgs); 35 | } else { 36 | pendingRec.postException({code: args.errorType, message: args.errorValue}); 37 | } 38 | // and return ther original args presented to the method back to the test 39 | // suite so it can validate if necessary. 40 | activity.postResult(pendingRec.data); 41 | } 42 | doit(); 43 | }); 44 | 45 | navigator.mozActivities.services.registerHandler('test', 'finish', function(activity, credentials) { 46 | isFinished = true; 47 | if (pendingCalls.length) { 48 | activity.postException({code:"test_suite_error", message:"finalized while pending calls are available"}) 49 | } else { 50 | activity.postResult(); 51 | } 52 | }); 53 | 54 | // The helper for all the "real" methods to wait for instructions from the 55 | // test suite. 56 | function waitForResumeInstructions(methodName, activity, credentials) { 57 | if (isFinished) { 58 | activity.postException({code:"test_suite_error", message:"this app has been finalized"}); 59 | return; 60 | } 61 | if (methodName in pendingCalls) { 62 | activity.postException({code:"test_suite_error", message:"already a pending call for '" + methodName + "'\n"}); 63 | return; 64 | } 65 | pendingCalls[methodName] = activity; 66 | }; 67 | 68 | 69 | // The 'link.send' service used by F1 while under test. 70 | navigator.mozActivities.services.registerHandler('link.send', 'getParameters', function(activity, credentials) { 71 | waitForResumeInstructions('getParameters', activity, credentials); 72 | }); 73 | 74 | navigator.mozActivities.services.registerHandler('link.send', 'getLogin', function(activity, credentials) { 75 | waitForResumeInstructions('getLogin', activity, credentials); 76 | }); 77 | 78 | navigator.mozActivities.services.ready(); 79 | -------------------------------------------------------------------------------- /data/ui/scripts/TextCounter.js: -------------------------------------------------------------------------------- 1 | /* ***** BEGIN LICENSE BLOCK ***** 2 | * Version: MPL 1.1 3 | * 4 | * The contents of this file are subject to the Mozilla Public License Version 5 | * 1.1 (the "License"); you may not use this file except in compliance with 6 | * the License. You may obtain a copy of the License at 7 | * http://www.mozilla.org/MPL/ 8 | * 9 | * Software distributed under the License is distributed on an "AS IS" basis, 10 | * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | * for the specific language governing rights and limitations under the 12 | * License. 13 | * 14 | * The Original Code is Raindrop. 15 | * 16 | * The Initial Developer of the Original Code is 17 | * Mozilla Messaging, Inc.. 18 | * Portions created by the Initial Developer are Copyright (C) 2009 19 | * the Initial Developer. All Rights Reserved. 20 | * 21 | * Contributor(s): 22 | * */ 23 | 24 | /*jslint plusplus: false */ 25 | /*global require: false, define: false */ 26 | "use strict"; 27 | 28 | define([ 'jquery', 'blade/object', 'blade/fn'], 29 | function ($, object, fn) { 30 | // Note this is very similar to the regex in panel.js but with a few 31 | // tweaks to better handle '?', '#' etc chars, allow a URL to finish on 32 | // whitespace and the global flag. 33 | var urlRegex = /\w+?:\/\/\w+(\.\w+)*(:\d+)?\S+?(\s|$)/g; 34 | 35 | return object(null, null, { 36 | init: function (node, countNode, parameters) { 37 | this.dom = $(node); 38 | this.countDom = $(countNode); 39 | this.parameters = parameters; 40 | this.dom.bind('keyup', fn.bind(this, 'checkCount')); 41 | this.checkCount(); 42 | }, 43 | 44 | checkCount: function () { 45 | var value = this.dom[0].value, 46 | limit = this.parameters.constraints.textLimit, 47 | effectiveLen = value.length, 48 | remaining; 49 | 50 | if (this.parameters.constraints.shortURLLength && value) { 51 | // we must find all URLs in the message and assume they will only 52 | // actually take up shortURLLength chars. 53 | var urlsInMsg = value.match(urlRegex); 54 | if (urlsInMsg) { 55 | for (var i=0; i this.limit; 78 | } 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /data/ui/scripts/dispatch.js: -------------------------------------------------------------------------------- 1 | /* ***** BEGIN LICENSE BLOCK ***** 2 | * Version: MPL 1.1 3 | * 4 | * The contents of this file are subject to the Mozilla Public License Version 5 | * 1.1 (the "License"); you may not use this file except in compliance with 6 | * the License. You may obtain a copy of the License at 7 | * http://www.mozilla.org/MPL/ 8 | * 9 | * Software distributed under the License is distributed on an "AS IS" basis, 10 | * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | * for the specific language governing rights and limitations under the 12 | * License. 13 | * 14 | * The Original Code is Raindrop. 15 | * 16 | * The Initial Developer of the Original Code is 17 | * Mozilla Messaging, Inc.. 18 | * Portions created by the Initial Developer are Copyright (C) 2009 19 | * the Initial Developer. All Rights Reserved. 20 | * 21 | * Contributor(s): 22 | * */ 23 | 24 | /*jslint plusplus: false, indent: 2 */ 25 | /*global require: false, define: false, location: false, window: false */ 26 | "use strict"; 27 | 28 | /** 29 | * A module that handles dispatching pub/sub topics, where the underlying 30 | * dispatch is done by postMessage. This allows for chrome extensions 31 | * to participate in the pub/sub via postMessage without having to 32 | * participate in this particular module. 33 | */ 34 | define(['jquery'], function ($) { 35 | 36 | var origin = location.protocol + "//" + location.host, 37 | wins = []; 38 | 39 | return { 40 | sub: function (topic, callback, win, targetOrigin) { 41 | win = win || window; 42 | targetOrigin = targetOrigin || origin; 43 | var func = function (evt) { 44 | //Make sure message is from this page, or from the browser extension 45 | //that wants to communicate information back to the page. 46 | if (evt.origin === targetOrigin || evt.origin === 'resource://openwebapps/service') { 47 | //Assume pub/sub has JSON data with properties named 48 | //'topic' and 'data'. 49 | try { 50 | var message = JSON.parse(evt.data), 51 | pubTopic = message.cmd; 52 | if (pubTopic && pubTopic === topic) { 53 | try { 54 | callback(message.data); 55 | //dump("dispatch handled ["+pubTopic+"] with data "+evt.data+"\n"); 56 | } catch (e) { 57 | dump("Error in dispatch.sub callback for topic '" + pubTopic + "': " + e.toString() + "\n"); 58 | dump(e.stack); 59 | } 60 | } 61 | } catch (e) { 62 | //Just ignore messages that are not JSON. There are some, like 63 | //the oauth_success messages 64 | } 65 | } 66 | }; 67 | 68 | win.addEventListener('message', func, false); 69 | 70 | //return the created function to allow unsubscribing 71 | return func; 72 | }, 73 | 74 | unsub: function (func, win) { 75 | win = win || window; 76 | win.removeEventListener('message', func, false); 77 | }, 78 | 79 | pub: function (topic, data, win) { 80 | win = win || window; 81 | var text = JSON.stringify({ 82 | cmd: topic, 83 | data: data 84 | }), 85 | i, otherWin; 86 | 87 | // Notify primary target. 88 | win.postMessage(text, origin); 89 | } 90 | }; 91 | }); 92 | -------------------------------------------------------------------------------- /tests/unit/test_securefilestore.js: -------------------------------------------------------------------------------- 1 | /* Any copyright is dedicated to the Public Domain. 2 | http://creativecommons.org/publicdomain/zero/1.0/ */ 3 | 4 | Cu.import("resource://services-share/store.js"); 5 | Cu.import("resource://gre/modules/NetUtil.jsm"); 6 | 7 | const STORE_FILENAME = "fx_share_accounts"; 8 | const FILE_PERMS = 0600; 9 | const PR_RDONLY = 0x01; 10 | const gProfD = do_get_profile(); 11 | 12 | const storeFile = gProfD.clone(); 13 | storeFile.append(STORE_FILENAME); 14 | 15 | let loginManagerCrypto = Cc["@mozilla.org/login-manager/crypto/SDR;1"] 16 | .getService(Ci.nsILoginManagerCrypto); 17 | 18 | const TEST_DATA = "contains \u00fcnic\u00f6de characters"; 19 | const TEST_DATA2 = "different data"; 20 | 21 | let gTests = []; 22 | 23 | function readFile(file, callback) { 24 | NetUtil.asyncFetch(storeFile, function(stream) { 25 | callback(NetUtil.readInputStreamToString(stream, stream.available())); 26 | }); 27 | } 28 | 29 | // First try to read when the file doesn't exist yet. 30 | gTests.push(function test_read_nonexistent() { 31 | SecureFileStore.fetch(function (status, data) { 32 | do_check_eq(status, Cr.NS_ERROR_FILE_NOT_FOUND); 33 | do_check_eq(data, null); 34 | run_next_test(); 35 | }); 36 | }); 37 | 38 | // Test the write functionality. The data is encrypted. 39 | gTests.push(function test_store() { 40 | SecureFileStore.store(TEST_DATA, function (status) { 41 | do_check_true(Components.isSuccessCode(status)); 42 | readFile(storeFile, function(fileContents) { 43 | do_check_eq(loginManagerCrypto.decrypt(fileContents), TEST_DATA); 44 | run_next_test(); 45 | }); 46 | }); 47 | }); 48 | 49 | // Test the read functionality. 50 | gTests.push(function test_fetch() { 51 | SecureFileStore._data = null; // make sure we actually read from file 52 | SecureFileStore.fetch(function (status, data) { 53 | do_check_true(Components.isSuccessCode(status)); 54 | do_check_eq(data, TEST_DATA); 55 | run_next_test(); 56 | }); 57 | }); 58 | 59 | // Test the caching functionality. 60 | gTests.push(function test_cache() { 61 | // Remove the file: we still get data. 62 | storeFile.remove(false); 63 | SecureFileStore.fetch(function (status, data) { 64 | do_check_true(Components.isSuccessCode(status)); 65 | do_check_eq(data, TEST_DATA); 66 | run_next_test(); 67 | }); 68 | }); 69 | 70 | // Write different data. 71 | gTests.push(function test_store_again() { 72 | SecureFileStore.store(TEST_DATA2, function (status) { 73 | do_check_true(Components.isSuccessCode(status)); 74 | readFile(storeFile, function(fileContents) { 75 | do_check_eq(loginManagerCrypto.decrypt(fileContents), TEST_DATA2); 76 | 77 | SecureFileStore.fetch(function (status, data) { 78 | do_check_true(Components.isSuccessCode(status)); 79 | do_check_eq(data, TEST_DATA2); 80 | run_next_test(); 81 | }); 82 | }); 83 | }); 84 | }); 85 | 86 | gTests.push(function test_clear() { 87 | SecureFileStore.clear(); 88 | SecureFileStore.fetch(function (status, data) { 89 | do_check_eq(status, Cr.NS_ERROR_FILE_NOT_FOUND); 90 | do_check_eq(data, null); 91 | 92 | // Clearing again won't do any harm. 93 | SecureFileStore.clear(); 94 | run_next_test(); 95 | }); 96 | }); 97 | 98 | function run_test() { 99 | run_next_test(); 100 | } 101 | -------------------------------------------------------------------------------- /tests/test_utils.js: -------------------------------------------------------------------------------- 1 | const {Cc, Ci} = require("chrome"); 2 | const URL = require("url"); 3 | const tabs = require("tabs"); 4 | // implicitly run our main() entry-point 5 | require("fx-share-addon/main").main(); 6 | 7 | exports.finalize = function(test, fn) { 8 | let onDone = test.onDone; 9 | test.onDone = function(test) { 10 | fn(function() { 11 | onDone(test); 12 | }); 13 | } 14 | } 15 | 16 | exports.getContentWindow = function() { 17 | let wm = Cc["@mozilla.org/appshell/window-mediator;1"] 18 | .getService(Ci.nsIWindowMediator); 19 | return wm.getMostRecentWindow("navigator:browser").document.commandDispatcher.focusedWindow; 20 | } 21 | 22 | // Return the URL of content in our 'test' directory. 23 | exports.getTestUrl = function(testPage) { 24 | let lastSlash = this.module.uri.lastIndexOf("/"); 25 | let resourceUrl = this.module.uri.substr(0, lastSlash+1) + testPage; 26 | // return the file:// as F1 disables itself for resource:// urls. 27 | return URL.fromFilename(URL.toFilename(resourceUrl)); 28 | } 29 | 30 | exports.createTab = function(url, callback) { 31 | tabs.open({ 32 | url: url, 33 | onOpen: function onOpen(tab) { 34 | tab.on('ready', function(tab){ 35 | callback(tab); 36 | }); 37 | } 38 | }); 39 | } 40 | 41 | exports.removeCurrentTab = function(callback) { 42 | let tab = tabs.activeTab; 43 | tab.on('close', callback); 44 | tab.close(); 45 | } 46 | 47 | function getMediator(args, readyCallback) { 48 | require("activities/main"); // for the side effect of injecting window.apps. 49 | let wm = Cc["@mozilla.org/appshell/window-mediator;1"] 50 | .getService(Ci.nsIWindowMediator); 51 | 52 | let topWindow = wm.getMostRecentWindow("navigator:browser"); 53 | let tab = topWindow.gBrowser.selectedTab; 54 | let browser = topWindow.gBrowser.getBrowserForTab(tab); 55 | // instead of constructing the object explicitly, we go via the services API 56 | // so it knows the created panel is associated with the contentWindow/service. 57 | let services = topWindow.serviceInvocationHandler; 58 | let activity = { 59 | action: "link.send", 60 | type: "link.send", // fixme 61 | data: args || {} 62 | } 63 | // This is a bit yucky - if the mediator already exists, we will never 64 | // get the "ready" notification from it, so dig inside the impl to work 65 | // out if the mediator is new or will be reused. 66 | let reused = false; 67 | for each (let panel in services._popups) { 68 | if (panel.panelWindow && activity.action == panel.methodName) { 69 | reused = true; 70 | break; 71 | } 72 | } 73 | mediator = services.get(activity, function () {;}); 74 | if (readyCallback) { 75 | if (reused) { 76 | mediator.panel.once("show", function() { 77 | readyCallback(mediator); 78 | }); 79 | } else { 80 | mediator.panel.port.once("owa.mediation.ready", function() { 81 | readyCallback(mediator) 82 | }); 83 | } 84 | } 85 | return mediator; 86 | } 87 | exports.getMediator = getMediator; 88 | 89 | exports.createSharePanel = function(contentWindow) { 90 | let panel = getMediator(); 91 | panel.show(); 92 | return panel; 93 | }; 94 | 95 | exports.getShareButton = function(topWindow) { 96 | if (!topWindow) { 97 | let wm = Cc["@mozilla.org/appshell/window-mediator;1"] 98 | .getService(Ci.nsIWindowMediator); 99 | 100 | topWindow = wm.getMostRecentWindow("navigator:browser"); 101 | } 102 | return topWindow.document.getElementById("share-button"); 103 | } 104 | -------------------------------------------------------------------------------- /data/apps/google/google.html: -------------------------------------------------------------------------------- 1 | 23 | 24 | 25 | 26 | GMail via F1 OWA 27 | 28 | 29 | 34 | 35 | 36 | 37 | 38 | 55 | 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /data/ui/share/scripts/widgets/AccountPanel.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | {displayName} 14 | logout 15 |
16 |
17 | {lt(svc.shareTypes.length, 2)[} 18 | {svc.features.direct [} 19 | 20 | {]} 21 | 22 | {]} 23 | {svc.features.subjectLabel [} 24 | 25 | {]} 26 | 27 | 28 | {svc.constraints.shortURLLength [} 29 |
Links will appear shortened.
30 | {]} 31 | 32 |
33 |
34 | {svc.features.picture [} 35 |
36 |
37 |
38 | {]} 39 |
40 | {svc.features.title [} 41 | 42 | {]} 43 | {/ We only show the URL in the page-info box if we haven't put it in the message} 44 | {!svc.constraints.editableURLInMessage [} 45 |
{cleanLink(options.url)}
46 | {]} 47 | {svc.features.description [} 48 | 49 | {]} 50 | 52 |
53 |
54 |
55 | 56 | {gt(svc.shareTypes.length, 1) [} 57 |
58 |
59 |
60 |
61 | {. lastType lastToShareType(svc.shareTypes)} 62 | 63 |
64 |
65 | {]} 66 |
67 |
68 |
69 | Please specify a recipient 70 |
71 |
72 | Cannot share with all recipients 73 |
74 | {svc.constraints.textLimit [} 75 |
76 | {]} 77 |
78 | 79 |
80 |
81 |
82 |
83 | -------------------------------------------------------------------------------- /data/ui/scripts/blade/url.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license blade/url Copyright (c) 2010, The Dojo Foundation All Rights Reserved. 3 | * Available via the MIT, GPL or new BSD license. 4 | * see: http://github.com/jrburke/blade for details 5 | */ 6 | /*jslint nomen: false, plusplus: false */ 7 | /*global define: false */ 8 | 9 | 'use strict'; 10 | 11 | define(['./array'], function (array) { 12 | var ostring = Object.prototype.toString; 13 | 14 | return { 15 | objectToQuery: function (/*Object*/ map) { 16 | // summary: 17 | // takes a name/value mapping object and returns a string representing 18 | // a URL-encoded version of that object. 19 | // example: 20 | // this object: 21 | // 22 | // | { 23 | // | blah: "blah", 24 | // | multi: [ 25 | // | "thud", 26 | // | "thonk" 27 | // | ] 28 | // | }; 29 | // 30 | // yields the following query string: 31 | // 32 | // | "blah=blah&multi=thud&multi=thonk" 33 | 34 | // FIXME: need to implement encodeAscii!! 35 | var enc = encodeURIComponent, 36 | pairs = [], 37 | backstop = {}, 38 | name, value, assign, i; 39 | for (name in map) { 40 | if (map.hasOwnProperty(name)) { 41 | value = map[name]; 42 | if (value !== backstop[name]) { 43 | assign = enc(name) + "="; 44 | if (array.is(value)) { 45 | for (i = 0; i < value.length; i++) { 46 | pairs.push(assign + enc(value[i])); 47 | } 48 | } else { 49 | pairs.push(assign + enc(value)); 50 | } 51 | } 52 | } 53 | } 54 | return pairs.join("&"); // String 55 | }, 56 | 57 | queryToObject: function (/*String*/ str) { 58 | // summary: 59 | // Create an object representing a de-serialized query section of a 60 | // URL. Query keys with multiple values are returned in an array. 61 | // 62 | // example: 63 | // This string: 64 | // 65 | // | "foo=bar&foo=baz&thinger=%20spaces%20=blah&zonk=blarg&" 66 | // 67 | // results in this object structure: 68 | // 69 | // | { 70 | // | foo: [ "bar", "baz" ], 71 | // | thinger: " spaces =blah", 72 | // | zonk: "blarg" 73 | // | } 74 | // 75 | // Note that spaces and other urlencoded entities are correctly 76 | // handled. 77 | var ret = {}, 78 | qp = str.split('&'), 79 | dec = decodeURIComponent, 80 | parts, name, val; 81 | 82 | qp.forEach(function (item) { 83 | if (item.length) { 84 | parts = item.split('='); 85 | name = dec(parts.shift()); 86 | val = dec(parts.join('=')); 87 | if (typeof ret[name] === 'string') { 88 | ret[name] = [ret[name]]; 89 | } 90 | 91 | if (ostring.call(ret[name]) === '[object Array]') { 92 | ret[name].push(val); 93 | } else { 94 | ret[name] = val; 95 | } 96 | } 97 | }); 98 | return ret; 99 | } 100 | }; 101 | }); 102 | -------------------------------------------------------------------------------- /lib/addonutils.js: -------------------------------------------------------------------------------- 1 | /* -*- Mode: JavaScript; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 | /* vim: set ts=2 et sw=2 tw=80: */ 3 | /* ***** BEGIN LICENSE BLOCK ***** 4 | * Version: MPL 1.1/GPL 2.0/LGPL 2.1 5 | * 6 | * The contents of this file are subject to the Mozilla Public License Version 7 | * 1.1 (the "License"); you may not use this file except in compliance with 8 | * the License. You may obtain a copy of the License at 9 | * http://www.mozilla.org/MPL/ 10 | * 11 | * Software distributed under the License is distributed on an "AS IS" basis, 12 | * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 13 | * for the specific language governing rights and limitations under the 14 | * License. 15 | * 16 | * The Original Code is Raindrop. 17 | * 18 | * The Initial Developer of the Original Code is 19 | * the Mozilla Foundation. 20 | * Portions created by the Initial Developer are Copyright (C) 2011 21 | * the Initial Developer. All Rights Reserved. 22 | * 23 | * Contributor(s): 24 | * Anant Narayanan 25 | * Shane Caraveo 26 | * 27 | * Alternatively, the contents of this file may be used under the terms of 28 | * either the GNU General Public License Version 2 or later (the "GPL"), or 29 | * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 30 | * in which case the provisions of the GPL or the LGPL are applicable instead 31 | * of those above. If you wish to allow use of your version of this file only 32 | * under the terms of either the GPL or the LGPL, and not to allow others to 33 | * use your version of this file under the terms of the MPL, indicate your 34 | * decision by deleting the provisions above and replace them with the notice 35 | * and other provisions required by the GPL or the LGPL. If you do not delete 36 | * the provisions above, a recipient may use your version of this file under 37 | * the terms of any one of the MPL, the GPL or the LGPL. 38 | * 39 | * ***** END LICENSE BLOCK ***** */ 40 | 41 | const { Cc, Ci, Cm, Cu } = require("chrome"); 42 | 43 | let tmp = {} 44 | Cu.import("resource://gre/modules/Services.jsm", tmp); 45 | let { Services } = tmp; 46 | 47 | function loadStylesheet(win, uritail) { 48 | var uri = require("self").data.url(uritail); 49 | let document = win.document; 50 | let pi = document.createProcessingInstruction("xml-stylesheet", "href=\"" + uri + "\" type=\"text/css\""); 51 | document.insertBefore(pi, document.firstChild); 52 | return pi; 53 | } 54 | 55 | /* l10n support. See https://github.com/Mardak/restartless/examples/l10nDialogs */ 56 | 57 | function getString(name, args, plural) { 58 | let str; 59 | try { 60 | str = getString.bundle.GetStringFromName(name); 61 | } catch (ex) { 62 | str = getString.fallback.GetStringFromName(name); 63 | } 64 | if (args != null) { 65 | if (typeof args == "string" || args.length == null) args = [args]; 66 | str = str.replace(/%s/gi, args[0]); 67 | Array.forEach(args, function(replacement, index) { 68 | str = str.replace(RegExp("%" + (index + 1) + "\\$S", "gi"), replacement); 69 | }); 70 | } 71 | return str; 72 | } 73 | getString.init = function(getAlternate) { 74 | if (typeof getAlternate != "function") getAlternate = function()"en-US"; 75 | 76 | function getBundle(locale) { 77 | let propertyPath = "locale/" + locale + ".properties"; 78 | let propertyFile = require("self").data.url(propertyPath); 79 | try { 80 | let uniqueFileSpec = propertyFile + "#" + Math.random(); 81 | let bundle = Services.strings.createBundle(uniqueFileSpec); 82 | bundle.getSimpleEnumeration(); 83 | return bundle; 84 | } catch (ex) {} 85 | return null; 86 | } 87 | 88 | let locale = Cc["@mozilla.org/chrome/chrome-registry;1"]. 89 | getService(Ci.nsIXULChromeRegistry).getSelectedLocale("global"); 90 | getString.bundle = getBundle(locale) || getBundle(getAlternate(locale)); 91 | getString.fallback = getBundle("en-US"); 92 | } 93 | 94 | exports.loadStylesheet = loadStylesheet; 95 | exports.getString = getString; 96 | -------------------------------------------------------------------------------- /tests/browser/keyvaluestore_tests.js: -------------------------------------------------------------------------------- 1 | /* Any copyright is dedicated to the Public Domain. 2 | http://creativecommons.org/publicdomain/zero/1.0/ */ 3 | 4 | gTests.push(function test_storeGet_empty() { 5 | let message = {topic: "storeGet", data: "hypnotoad"}; 6 | relayMessage(message, function() { 7 | next(gShareWindow, "message", function(event) { 8 | let message = JSON.parse(event.data); 9 | is(message.topic, "storeGetReturn"); 10 | // Store is empty so the data is null. 11 | is(message.data.key, "hypnotoad"); 12 | is(message.data.value, null); 13 | run_next_test(); 14 | }); 15 | }); 16 | }); 17 | 18 | gTests.push(function test_storeSet() { 19 | let message = {topic: "storeSet", 20 | data: {key: "hypnotoad", 21 | value: "Everybody loves Hypnotoad!"}}; 22 | relayMessage(message, function() { 23 | // We expect a notification about the change. 24 | next(gShareWindow, "message", function(event) { 25 | let message = JSON.parse(event.data); 26 | is(message.topic, "storeNotifyChange"); 27 | is(message.data.key, "hypnotoad"); 28 | is(message.data.value, "Everybody loves Hypnotoad!"); 29 | run_next_test(); 30 | }); 31 | }); 32 | }); 33 | 34 | gTests.push(function test_storeGet_notEmpty() { 35 | let message = {topic: "storeGet", data: "hypnotoad"}; 36 | relayMessage(message, function() { 37 | next(gShareWindow, "message", function(event) { 38 | let message = JSON.parse(event.data); 39 | is(message.topic, "storeGetReturn"); 40 | is(message.data.key, "hypnotoad"); 41 | is(message.data.value, "Everybody loves Hypnotoad!"); 42 | run_next_test(); 43 | }); 44 | }); 45 | }); 46 | 47 | gTests.push(function test_storeSet_anotherOne() { 48 | let message = {topic: "storeSet", 49 | data: {key: "calculon", 50 | value: "I don't do two takes. Amateurs do two takes."}}; 51 | relayMessage(message, function() { 52 | // We expect a notification about the change. 53 | next(gShareWindow, "message", function(event) { 54 | let message = JSON.parse(event.data); 55 | is(message.topic, "storeNotifyChange"); 56 | is(message.data.key, "calculon"); 57 | is(message.data.value, "I don't do two takes. Amateurs do two takes."); 58 | run_next_test(); 59 | }); 60 | }); 61 | }); 62 | 63 | gTests.push(function test_storeRemove() { 64 | let message = {topic: "storeRemove", data: "hypnotoad"}; 65 | relayMessage(message, function() { 66 | next(gShareWindow, "message", function(event) { 67 | let message = JSON.parse(event.data); 68 | is(message.topic, "storeNotifyChange"); 69 | is(message.data.key, "hypnotoad"); 70 | is(message.data.value, null); 71 | run_next_test(); 72 | }); 73 | }); 74 | }); 75 | 76 | gTests.push(function test_storeGet_removed() { 77 | let message = {topic: "storeGet", data: "hypnotoad"}; 78 | relayMessage(message, function() { 79 | next(gShareWindow, "message", function(event) { 80 | let message = JSON.parse(event.data); 81 | is(message.topic, "storeGetReturn"); 82 | // Store is empty so the data is null. 83 | is(message.data.key, "hypnotoad"); 84 | is(message.data.value, null); 85 | run_next_test(); 86 | }); 87 | }); 88 | }); 89 | 90 | gTests.push(function test_storeRemove_nonexistent() { 91 | let message = {topic: "storeRemove", data: "nonexistent"}; 92 | relayMessage(message, function() { 93 | next(gShareWindow, "message", function(event) { 94 | let message = JSON.parse(event.data); 95 | is(message.topic, "storeNotifyChange"); 96 | is(message.data.key, "nonexistent"); 97 | is(message.data.value, null); 98 | run_next_test(); 99 | }); 100 | }); 101 | }); 102 | 103 | gTests.push(function test_storeRemoveAll() { 104 | let message = {topic: "storeRemoveAll"}; 105 | relayMessage(message, function() { 106 | next(gShareWindow, "message", function(event) { 107 | let message = JSON.parse(event.data); 108 | is(message.topic, "storeNotifyRemoveAll"); 109 | run_next_test(); 110 | }); 111 | }); 112 | }); 113 | -------------------------------------------------------------------------------- /tests/test-email-addresses.js: -------------------------------------------------------------------------------- 1 | // Tests extracted from the Python email package's test_email.py. 2 | 3 | let {parseaddrlist, parseaddr, formataddr} = require("email/addressutils"); 4 | 5 | function assertAddrsEqual(test, ad1, ad2) { 6 | // each elt should be an array of [name, address] 7 | if (ad1.length != 2) { 8 | return test.fail("Each addresslist item should be an array of 2 items: " + ad1); 9 | } 10 | if (ad2.length != 2) { 11 | return test.fail("Each addresslist item should be an array of 2 items: " + ad2); 12 | } 13 | if (ad1[0] != ad2[0] || ad1[1] != ad2[1]) { 14 | return test.fail("Address list elements are different: " + ad1 + " != " + ad2); 15 | } 16 | test.pass(ad1 + " == " + ad2); 17 | return true; 18 | } 19 | 20 | function assertAddrListsEqual(test, al1, al2) { 21 | if (al1.length != al2.length) { 22 | return test.fail("Different address lengths: " + al1 + "/" + al2); 23 | } 24 | for (let i=0; i'), [['', '']]); 37 | }; 38 | 39 | exports.testNoQuote = function(test) { 40 | test.assertEqual(formataddr(['A Silly Person', 'person@dom.ain']), 41 | 'A Silly Person '); 42 | }; 43 | 44 | exports.testQuote = function(test) { 45 | test.assertEqual(formataddr(['A (Very) Silly Person', 'person@dom.ain']), 46 | '"A \\(Very\\) Silly Person" '); 47 | let a = 'A \\(Special\\) Person' 48 | let b = 'person@dom.ain'; 49 | assertAddrsEqual(test, parseaddr(formataddr([a, b])), [a, b]); 50 | }; 51 | 52 | exports.testEscapeBackslashes = function(test) { 53 | test.assertEqual(formataddr(['Arthur \\Backslash\\ Foobar', 'person@dom.ain']), 54 | '"Arthur \\\\Backslash\\\\ Foobar" '); 55 | let a = 'Arthur \\Backslash\\ Foobar'; 56 | let b = 'person@dom.ain'; 57 | assertAddrsEqual(test, parseaddr(formataddr([a, b])), [a, b]); 58 | }; 59 | 60 | exports.testNameWithDot = function(test) { 61 | let x = 'John X. Doe '; 62 | let y = '"John X. Doe" '; 63 | let a = 'John X. Doe'; 64 | let b = 'jxd@example.com'; 65 | assertAddrsEqual(test, parseaddr(x), [a, b]); 66 | assertAddrsEqual(test, parseaddr(y), [a, b]); 67 | // formataddr() quotes the name if there's a dot in it 68 | test.assertEqual(formataddr([a, b]), y); 69 | }; 70 | 71 | exports.testNameWithComma = function(test) { 72 | let x = '"Doe, John X" '; 73 | let a = 'Doe, John X'; 74 | let b = 'jxd@example.com'; 75 | assertAddrsEqual(test, parseaddr(x), [a, b]); 76 | // formataddr() quotes the name if there's a comma in it 77 | test.assertEqual(formataddr([a, b]), x); 78 | }; 79 | 80 | exports.testMultiLine = function(test) { 81 | let x = "\r\nFoo\r\n\tBar "; 82 | assertAddrsEqual(test, parseaddr(x), ['Foo Bar', 'foo@example.com']); 83 | }; 84 | 85 | exports.testSemiColon = function(test) { 86 | test.assertEqual(formataddr(['A Silly; Person', 'person@dom.ain']), 87 | '"A Silly; Person" '); 88 | }; 89 | 90 | exports.testComments = function(test) { 91 | let addrs = parseaddr('User ((nested comment)) '); 92 | test.assertEqual(addrs[1], "foo@bar.com"); 93 | }; 94 | 95 | exports.testMultiple = function(test) { 96 | let addrline = '"Doe, John X" , user@dom.ain, "Arthur \\\\Backslash\\\\ Foobar" '; 97 | let addrs = parseaddrlist(addrline); 98 | let expected = [ 99 | ["Doe, John X", "jxd@example.com"], 100 | ["", "user@dom.ain"], 101 | ["Arthur \\Backslash\\ Foobar", "person@dom.ain"] 102 | ]; 103 | assertAddrListsEqual(test, addrs, expected); 104 | } 105 | 106 | exports.testNameOnly = function(test) { 107 | let addr = parseaddr('Just a name'); 108 | // this seems more an implementation accident, but we rely on it. 109 | assertAddrsEqual(test, parseaddr(addr), ['', 'Just a name']); 110 | } 111 | -------------------------------------------------------------------------------- /data/ui/scripts/jquery.textOverflow.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery Text Overflow v0.7 3 | * 4 | * Licensed under the new BSD License. 5 | * Copyright 2009-2010, Bram Stein 6 | * All rights reserved. 7 | */ 8 | /*global jQuery, document, setInterval*/ 9 | (function ($) { 10 | var style = document.documentElement.style, 11 | hasTextOverflow = ('textOverflow' in style || 'OTextOverflow' in style), 12 | 13 | domSplit = function (root, maxIndex) { 14 | var index = 0, result = [], 15 | domSplitAux = function (nodes) { 16 | var i = 0, tmp; 17 | 18 | if (index > maxIndex) { 19 | return; 20 | } 21 | 22 | for (i = 0; i < nodes.length; i += 1) { 23 | if (nodes[i].nodeType === 1) { 24 | tmp = nodes[i].cloneNode(false); 25 | result[result.length - 1].appendChild(tmp); 26 | result.push(tmp); 27 | domSplitAux(nodes[i].childNodes); 28 | result.pop(); 29 | } else if (nodes[i].nodeType === 3) { 30 | if (index + nodes[i].length < maxIndex) { 31 | result[result.length - 1].appendChild(nodes[i].cloneNode(false)); 32 | } else { 33 | tmp = nodes[i].cloneNode(false); 34 | tmp.textContent = $.trim(tmp.textContent.substring(0, maxIndex - index)); 35 | result[result.length - 1].appendChild(tmp); 36 | } 37 | index += nodes[i].length; 38 | } else { 39 | result.appendChild(nodes[i].cloneNode(false)); 40 | } 41 | } 42 | }; 43 | result.push(root.cloneNode(false)); 44 | domSplitAux(root.childNodes); 45 | return $(result.pop().childNodes); 46 | }; 47 | 48 | $.extend($.fn, { 49 | textOverflow: function (str, autoUpdate) { 50 | var more = str || '…'; 51 | 52 | if (!hasTextOverflow) { 53 | return this.each(function () { 54 | var element = $(this), 55 | 56 | // the clone element we modify to measure the width 57 | clone = element.clone(), 58 | 59 | // we save a copy so we can restore it if necessary 60 | originalElement = element.clone(), 61 | originalText = element.text(), 62 | originalWidth = element.width(), 63 | low = 0, mid = 0, 64 | high = originalText.length, 65 | reflow = function () { 66 | if (originalWidth !== element.width()) { 67 | element.replaceWith(originalElement); 68 | element = originalElement; 69 | originalElement = element.clone(); 70 | element.textOverflow(str, false); 71 | originalWidth = element.width(); 72 | } 73 | }; 74 | 75 | element.after(clone.hide().css({ 76 | 'position': 'absolute', 77 | 'width': 'auto', 78 | 'overflow': 'visible', 79 | 'max-width': 'inherit' 80 | })); 81 | 82 | if (clone.width() > originalWidth) { 83 | while (low < high) { 84 | mid = Math.floor(low + ((high - low) / 2)); 85 | clone.empty().append(domSplit(originalElement.get(0), mid)).append(more); 86 | if (clone.width() < originalWidth) { 87 | low = mid + 1; 88 | } else { 89 | high = mid; 90 | } 91 | } 92 | 93 | if (low < originalText.length) { 94 | element.empty().append(domSplit(originalElement.get(0), low - 1)).append(more); 95 | } 96 | } 97 | clone.remove(); 98 | 99 | if (autoUpdate) { 100 | setInterval(reflow, 200); 101 | } 102 | }); 103 | } else { 104 | return this; 105 | } 106 | } 107 | }); 108 | })(jQuery); 109 | -------------------------------------------------------------------------------- /tests/unit/test_keyvaluestore.js: -------------------------------------------------------------------------------- 1 | /* Any copyright is dedicated to the Public Domain. 2 | http://creativecommons.org/publicdomain/zero/1.0/ */ 3 | 4 | Cu.import("resource://services-share/store.js"); 5 | Cu.import("resource://gre/modules/NetUtil.jsm"); 6 | Cu.import("resource://gre/modules/Services.jsm"); 7 | 8 | const KEY_CHANGED_TOPIC = "services:share:store:key-changed"; 9 | const CLEARED_TOPIC = "services:share:store:cleared"; 10 | 11 | const gProfD = do_get_profile(); 12 | let loginManager = Cc["@mozilla.org/login-manager;1"] 13 | .getService(Ci.nsILoginManager); 14 | 15 | function onKeyChangedObserved(callback) { 16 | Services.obs.addObserver({ 17 | observe: function observe(subject, topic, data) { 18 | Services.obs.removeObserver(this, KEY_CHANGED_TOPIC); 19 | callback(subject.wrappedJSObject); 20 | } 21 | }, KEY_CHANGED_TOPIC, false); 22 | } 23 | 24 | function onStoreCleared(callback) { 25 | Services.obs.addObserver({ 26 | observe: function observe(subject, topic, data) { 27 | Services.obs.removeObserver(this, CLEARED_TOPIC); 28 | callback(); 29 | } 30 | }, CLEARED_TOPIC, false); 31 | } 32 | 33 | 34 | let gTests = []; 35 | 36 | gTests.push(function test_getAll_noData() { 37 | SecureKeyValueStore.getAll(function(data) { 38 | do_check_eq(data, null); 39 | run_next_test(); 40 | }); 41 | }); 42 | 43 | gTests.push(function test_get_noData() { 44 | const nonexistentKey = "nonexistentKey"; 45 | 46 | SecureKeyValueStore.get(nonexistentKey, function(value) { 47 | do_check_eq(value, null); 48 | run_next_test(); 49 | }); 50 | }); 51 | 52 | const testdata = { 53 | somekey: "somevalue", 54 | anotherkey: "another value" 55 | }; 56 | 57 | gTests.push(function test_set_noData() { 58 | onKeyChangedObserved(function(info) { 59 | do_check_eq(info.key, "somekey"); 60 | do_check_eq(info.value, testdata.somekey); 61 | run_next_test(); 62 | }); 63 | SecureKeyValueStore.set("somekey", testdata.somekey); 64 | }); 65 | 66 | gTests.push(function test_get_withData() { 67 | SecureKeyValueStore.get("somekey", function(value) { 68 | do_check_eq(value, testdata.somekey); 69 | run_next_test(); 70 | }); 71 | }); 72 | 73 | gTests.push(function test_set_another() { 74 | onKeyChangedObserved(function(info) { 75 | do_check_eq(info.key, "anotherkey"); 76 | do_check_eq(info.value, testdata.anotherkey); 77 | run_next_test(); 78 | }); 79 | SecureKeyValueStore.set("anotherkey", testdata.anotherkey); 80 | }); 81 | 82 | gTests.push(function test_getAll() { 83 | SecureKeyValueStore.getAll(function(data) { 84 | let expectedkeys = Object.keys(testdata).sort(); 85 | let keys = Object.keys(data).sort(); 86 | 87 | do_check_eq(keys.length, expectedkeys.length); 88 | for (let i = 0; i < keys.length; i++) { 89 | do_check_eq(keys[i], expectedkeys[i]); 90 | do_check_eq(data[keys[i]], testdata[keys[i]]); 91 | } 92 | 93 | run_next_test(); 94 | }); 95 | }); 96 | 97 | gTests.push(function test_remove() { 98 | onKeyChangedObserved(function(info) { 99 | do_check_eq(info.key, "somekey"); 100 | do_check_eq(info.value, null); 101 | run_next_test(); 102 | }); 103 | SecureKeyValueStore.remove("somekey"); 104 | }); 105 | 106 | gTests.push(function test_remove_nonexistent() { 107 | onKeyChangedObserved(function(info) { 108 | do_check_eq(info.key, "nonexistent"); 109 | do_check_eq(info.value, null); 110 | run_next_test(); 111 | }); 112 | SecureKeyValueStore.remove("nonexistent"); 113 | }); 114 | 115 | gTests.push(function test_removeAll() { 116 | onStoreCleared(function() { 117 | SecureKeyValueStore.getAll(function(data) { 118 | do_check_eq(data, null); 119 | 120 | // Removing all keys again won't do any harm. 121 | SecureKeyValueStore.removeAll(); 122 | run_next_test(); 123 | }); 124 | }); 125 | SecureKeyValueStore.removeAll(); 126 | }); 127 | 128 | gTests.push(function test_removeAllLogins() { 129 | // Add some data to the store first. 130 | onKeyChangedObserved(function(info) { 131 | // Wipe password storage. 132 | loginManager.removeAllLogins(); 133 | // Ensure stuff is really gone. 134 | SecureKeyValueStore.getAll(function(data) { 135 | do_check_eq(data, null); 136 | run_next_test(); 137 | }); 138 | }); 139 | SecureKeyValueStore.set("somekey", testdata.somekey); 140 | }); 141 | 142 | function run_test() { 143 | run_next_test(); 144 | } 145 | -------------------------------------------------------------------------------- /data/ui/share/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Share 5 | 6 | 16 | 17 | 18 | 19 | 23 | 24 |
25 | 26 |
27 |
28 |
29 |
30 |
31 | 32 | 33 | 34 | 36 | 37 | 38 | 39 | 48 | 49 | 59 | 60 | 71 | 72 | 84 | 85 | 100 | 101 | 111 | 112 | 122 | 123 | 132 | 133 | 134 | 135 | 136 | 137 | 146 | 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /tests/test-panel-focus.js: -------------------------------------------------------------------------------- 1 | const {getMediatorWithApp, testAppSequence} = require("./app_helpers"); 2 | 3 | testAccountFocusLoadHelper = function(test, parameters, expected_focus_name, cbdone) { 4 | test.waitUntilDone(); 5 | 6 | getMediatorWithApp(test, {}, function(appInfo) { 7 | let {jqAppWidget, panelContentWindow} = appInfo; 8 | let accountPanelDiv = jqAppWidget.find(".accountPanel"); 9 | let seq = [ 10 | {method: 'getParameters', successArgs: parameters}, 11 | {method: 'getLogin', successArgs: {user: {displayName: 'test user'}}, 12 | callback: function(cbresume) { 13 | // We returned a user, so the account panel should become visible. 14 | // Our parameters prevent 'to' etc fields, so the message field 15 | // should be Focused. 16 | test.waitUntil(function() { 17 | return accountPanelDiv.is(":visible") && 18 | panelContentWindow.document.activeElement.getAttribute("name") === expected_focus_name; 19 | }).then(function() { 20 | if (cbdone) { 21 | cbdone(appInfo, cbresume); 22 | } else { 23 | cbresume(); 24 | } 25 | }) 26 | } 27 | } 28 | ]; 29 | testAppSequence(test, appInfo, seq); 30 | }); 31 | } 32 | 33 | exports.testToFocused = function(test) { 34 | // these parameters mean "to" is shown so should get focus 35 | let params = { 36 | features: {direct: true}, 37 | shareTypes: [{type: 'somewhere', name: 'somewhere'}] 38 | }; 39 | testAccountFocusLoadHelper(test, params, "to"); 40 | }; 41 | 42 | exports.testToFocused2 = function(test) { 43 | // these parameters mean "to" and "subject" are shown - but "to" 44 | // should still win. 45 | let params = { 46 | features: {direct: true, subjectLabel: true}, 47 | shareTypes: [{type: 'somewhere', name: 'somewhere'}] 48 | }; 49 | testAccountFocusLoadHelper(test, params, "to"); 50 | }; 51 | 52 | exports.testSubjectFocused = function(test) { 53 | // these parameters mean "subject" is shown, so it should get focus 54 | let params = { 55 | features: {subjectLabel: true}, 56 | shareTypes: [{type: 'somewhere', name: 'somewhere'}] 57 | }; 58 | testAccountFocusLoadHelper(test, params, "subject"); 59 | }; 60 | 61 | exports.testMessageFocused = function(test) { 62 | // these parameters mean "to" and "subject" are both hidden, so 63 | // "message" should get the focus 64 | let params = {shareTypes: [{type: 'somewhere', name: 'somewhere'}]}; 65 | testAccountFocusLoadHelper(test, params, "message"); 66 | }; 67 | 68 | // Test that after hiding and reshowing the panel that focus is still where 69 | // we expect. 70 | exports.testFocusAfterHide = function(test) { 71 | // these parameters mean "to" and "subject" are both hidden, so 72 | // "message" should get the focus 73 | let params = {shareTypes: [{type: 'somewhere', name: 'somewhere'}]}; 74 | testAccountFocusLoadHelper(test, params, "message", function(appInfo, cbresume) { 75 | let {mediator, panelContentWindow, jqAppWidget} = appInfo; 76 | let accountPanelDiv = jqAppWidget.find(".accountPanel"); 77 | mediator.panel.hide(); 78 | test.waitUntil(function() !mediator.panel.isShowing 79 | ).then(function() { 80 | mediator.show(); 81 | test.waitUntil(function() mediator.panel.isShowing 82 | ).then(function() { 83 | // The div should already be visible. 84 | test.assert(accountPanelDiv.is(":visible")); 85 | // "message" should still have focus. 86 | test.waitUntil(function() { 87 | return panelContentWindow.document.activeElement.getAttribute("name") === "message"; 88 | }).then(function() { 89 | cbresume(); 90 | }) 91 | }) 92 | }) 93 | }); 94 | }; 95 | 96 | // Test that when the account is not logged in the "login" button has focus. 97 | exports.testLoginFocus = function(test) { 98 | test.waitUntilDone(); 99 | let params = {shareTypes: [{type: 'somewhere', name: 'somewhere'}]}; 100 | 101 | getMediatorWithApp(test, {}, function(appInfo) { 102 | let {jqAppWidget, panelContentWindow} = appInfo; 103 | let loginPanelDiv = jqAppWidget.find(".accountLogin"); 104 | let seq = [ 105 | {method: 'getParameters', successArgs: params}, 106 | {method: 'getLogin', successArgs: {auth: {something: 'something'}}, 107 | callback: function(cbresume) { 108 | // Not logged in so the login panel should become visible. 109 | test.waitUntil(function() { 110 | return loginPanelDiv.is(":visible") && 111 | panelContentWindow.document.activeElement.getAttribute("class") === "login"; 112 | }).then(function() { 113 | cbresume(); 114 | }) 115 | } 116 | } 117 | ]; 118 | testAppSequence(test, appInfo, seq); 119 | }); 120 | } 121 | -------------------------------------------------------------------------------- /data/ui/scripts/friendly.js: -------------------------------------------------------------------------------- 1 | /* ***** BEGIN LICENSE BLOCK ***** 2 | * Version: MPL 1.1 3 | * 4 | * The contents of this file are subject to the Mozilla Public License Version 5 | * 1.1 (the "License"); you may not use this file except in compliance with 6 | * the License. You may obtain a copy of the License at 7 | * http://www.mozilla.org/MPL/ 8 | * 9 | * Software distributed under the License is distributed on an "AS IS" basis, 10 | * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 11 | * for the specific language governing rights and limitations under the 12 | * License. 13 | * 14 | * The Original Code is Raindrop. 15 | * 16 | * The Initial Developer of the Original Code is 17 | * Mozilla Messaging, Inc.. 18 | * Portions created by the Initial Developer are Copyright (C) 2009 19 | * the Initial Developer. All Rights Reserved. 20 | * 21 | * Contributor(s): 22 | * */ 23 | 24 | /*jslint plusplus: false, nomen: false */ 25 | /*global define: false */ 26 | "use strict"; 27 | 28 | define(function () { 29 | var friendly = { 30 | timestamp: function (timestamp) { 31 | return friendly.date(new Date(timestamp * 1000)); 32 | }, 33 | 34 | date: function (date) { 35 | var diff = (((new Date()).getTime() - date.getTime()) / 1000), 36 | day_diff = Math.floor(diff / 86400), 37 | dObj = { "friendly" : date.toLocaleDateString(), 38 | "additional" : date.toLocaleTimeString(), 39 | "utc" : date.toUTCString(), 40 | "locale" : date.toLocaleString() }; 41 | /* some kind of error */ 42 | if (day_diff < 0) { 43 | dObj.friendly = "in the future"; 44 | return dObj; 45 | } else if (isNaN(day_diff)) { 46 | dObj.friendly = dObj.additional = "unknown"; 47 | return dObj; 48 | } 49 | 50 | if (day_diff === 0) { 51 | if (diff < 60) { 52 | dObj.friendly = "just now"; 53 | return dObj; 54 | } 55 | if (diff < 120 + 30) { /* 1 minute plus some fuzz */ 56 | dObj.friendly = "a minute ago"; 57 | return dObj; 58 | } 59 | if (diff < 3600) { 60 | dObj.friendly = Math.floor(diff / 60) + " minutes ago"; 61 | return dObj; 62 | } 63 | if (diff < (60 * 60) * 2) { 64 | dObj.friendly = "1 hour ago"; 65 | return dObj; 66 | } 67 | if (diff < 24 * 60 * 60) { 68 | dObj.friendly = Math.floor(diff / 3600) + " hours ago"; 69 | return dObj; 70 | } 71 | } 72 | if (day_diff === 1) { 73 | dObj.friendly = "yesterday"; 74 | return dObj; 75 | } 76 | if (day_diff < 7) { 77 | dObj.friendly = day_diff + " days ago"; 78 | return dObj; 79 | } 80 | if (day_diff < 8) { 81 | dObj.friendly = "last week"; 82 | return dObj; 83 | } 84 | /* for this scope: we want day of week and the date 85 | plus the month (if different) */ 86 | if (day_diff < 31) { 87 | dObj.friendly = Math.ceil(day_diff / 7) + " weeks ago"; 88 | return dObj; 89 | } 90 | 91 | /* for this scope: we want month + date */ 92 | if (day_diff < 62) { 93 | dObj.friendly = "a month ago"; 94 | return dObj; 95 | } 96 | if (day_diff < 365) { 97 | dObj.friendly = Math.ceil(day_diff / 31) + " months ago"; 98 | return dObj; 99 | } 100 | 101 | /* for this scope: we want month + year */ 102 | if (day_diff >= 365 && day_diff < 730) { 103 | dObj.additional = date.toLocaleDateString(); 104 | dObj.friendly = "a year ago"; 105 | return dObj; 106 | } 107 | if (day_diff >= 365) { 108 | dObj.additional = date.toLocaleDateString(); 109 | dObj.friendly = Math.ceil(day_diff / 365) + " years ago"; 110 | return dObj; 111 | } 112 | return dObj; 113 | }, 114 | 115 | name: function (name) { 116 | var firstName = name.split(' ')[0]; 117 | if (firstName.indexOf('@') !== -1) { 118 | firstName = firstName.split('@')[0]; 119 | } 120 | firstName = firstName.replace(" ", ""); 121 | firstName = firstName.replace("'", ""); 122 | firstName = firstName.replace('"', ""); 123 | return firstName; 124 | } 125 | }; 126 | 127 | return friendly; 128 | }); 129 | -------------------------------------------------------------------------------- /tests/test-share-options.js: -------------------------------------------------------------------------------- 1 | /* Any copyright is dedicated to the Public Domain. 2 | http://creativecommons.org/publicdomain/zero/1.0/ */ 3 | const {getMediator, getTestUrl, getShareButton, createTab, removeCurrentTab, getContentWindow} = require("./test_utils"); 4 | const Assert = require("test/assert").Assert; 5 | 6 | // each test object has the url and the expected options. we only include 7 | // options we want to compare, other options we receive are ignored. 8 | let tests = [ 9 | { 10 | // tests getShortUrl 11 | get url() { 12 | return getTestUrl("corpus/opengraph.html") 13 | }, 14 | options: { 15 | // og:title 16 | title: ">This is my title<", 17 | // og:description 18 | description: "A test corpus file for open graph tags we care about", 19 | //medium: this.getPageMedium(), 20 | //source: this.getSourceURL(), 21 | // og:url 22 | url: "https://f1.mozillamessaging.com/", 23 | //shortUrl: this.getShortURL(), 24 | // og:image 25 | previews: [{"http_url":"http://f1.mozillamessaging.com/favicon.png","base64":""}], 26 | // og:site_name 27 | siteName: ">My simple test page<" 28 | } 29 | }, 30 | { 31 | // tests getShortUrl 32 | get url() { 33 | return getTestUrl("corpus/og_invalid_url.html") 34 | }, 35 | options: { 36 | description: "A test corpus file for open graph tags passing a bad url", 37 | url: null, 38 | previews: [], 39 | siteName: "Evil chrome delivering website" 40 | } 41 | }, 42 | { 43 | // tests getShortUrl 44 | get url() { 45 | return getTestUrl("corpus/shorturl_link.html") 46 | }, 47 | options: { 48 | previews: [{"http_url":"http://farm5.static.flickr.com/4141/5411147304_9f3996ba27_m.jpg","base64":""}], 49 | url: "http://www.flickr.com/photos/mixedpuppy/5411147304/", 50 | shortUrl: "http://flic.kr/p/9faxzb" 51 | } 52 | }, 53 | { 54 | // tests getShortUrl 55 | get url() { 56 | return getTestUrl("corpus/shorturl_linkrel.html") 57 | }, 58 | options: { 59 | previews: [{"http_url":"http://farm5.static.flickr.com/4141/5411147304_9f3996ba27_m.jpg","base64":""}], 60 | url: "http://www.flickr.com/photos/mixedpuppy/5411147304/", 61 | shortUrl: "http://flic.kr/p/9faxzb" 62 | } 63 | }, 64 | { 65 | // tests getShortUrl 66 | get url() { 67 | return getTestUrl("corpus/shortlink_linkrel.html") 68 | }, 69 | options: { 70 | previews: [{"http_url":"http://farm5.static.flickr.com/4141/5411147304_9f3996ba27_m.jpg","base64":""}], 71 | url: "http://www.flickr.com/photos/mixedpuppy/5411147304/", 72 | shortUrl: "http://flic.kr/p/9faxzb" 73 | } 74 | }, 75 | // Selection related tests. 76 | // Simple selection of one element. 77 | { 78 | get url() { 79 | return getTestUrl("page.html"); 80 | }, 81 | options: { 82 | message: 'This is just another web page' 83 | }, 84 | cbSetupPage: function(cw) { 85 | let p1 = cw.document.getElementsByTagName("p")[0]; 86 | let range = cw.document.createRange(); 87 | range.selectNode(p1); 88 | cw.getSelection().addRange(range); 89 | } 90 | }, 91 | // Selection of 2

elements. 92 | { 93 | get url() { 94 | return getTestUrl("page.html"); 95 | }, 96 | options: { 97 | message: "This is just another web page with a couple of paragraphs" 98 | }, 99 | cbSetupPage: function(cw) { 100 | let [p1, p2] = cw.document.getElementsByTagName("p"); 101 | let range = cw.document.createRange(); 102 | range.setStartBefore(p1); 103 | range.setEndAfter(p2); 104 | cw.getSelection().addRange(range); 105 | } 106 | } 107 | ]; 108 | 109 | 110 | function hasoptions(test, testOptions, options) { 111 | let passed = true; 112 | let msg; 113 | for (let option in testOptions) { 114 | let data = testOptions[option]; 115 | let message_data = options[option]; 116 | if (Array.isArray(data)) { 117 | // the message may have more array elements than we are testing for, this 118 | // is ok since some of those are hard to test (e.g. base64 images). So we 119 | // just test that anything in our test data IS in the message. 120 | new Assert(test).deepEqual(data, message_data, "option "+option); 121 | } else { 122 | test.assertEqual(data, message_data, "option "+option); 123 | } 124 | } 125 | } 126 | 127 | 128 | function testOne(test, theTest) { 129 | if (typeof(theTest) == 'undefined') { 130 | test.done(); 131 | return; 132 | } 133 | 134 | createTab(theTest.url, function(tab) { 135 | if (theTest.cbSetupPage) { 136 | theTest.cbSetupPage(getContentWindow()); 137 | } 138 | let panel = getMediator(); 139 | let options = panel.updateargs(); 140 | hasoptions(test, theTest.options, options); 141 | 142 | removeCurrentTab(function() { 143 | // run the next test 144 | testOne(test, tests.shift()); 145 | }); 146 | }); 147 | } 148 | 149 | exports.testShareOptions = function(test) { 150 | test.waitUntilDone(); 151 | testOne(test, tests.shift()); 152 | } 153 | -------------------------------------------------------------------------------- /tests/test-smtp-send.js: -------------------------------------------------------------------------------- 1 | // Sadly this is hard to make as a regular unit-test - it would require 2 | // hard-coding an smtp server and credentials into the test suite. 3 | // So for now, this test is enabled when certain magic environment variables 4 | // are set. 5 | const {Cc, Ci} = require("chrome") 6 | const {SslSmtpClient} = require("email/smtp"); 7 | const {MimeMultipart, MimeText, MimeBinary} = require("email/mime"); 8 | 9 | var environ = Cc["@mozilla.org/process/environment;1"] 10 | .getService(Ci.nsIEnvironment); 11 | 12 | let smtpArgs = { 13 | server: environ.get("FXSHARE_TEST_SMTP_SERVER") || 'smtp.gmail.com', 14 | port: environ.get("FXSHARE_TEST_SMTP_PORT") || 587, 15 | connectionType: environ.get("FXSHARE_TEST_SMTP_CONNECTION_TYPE") || 'starttls', 16 | email: environ.get("FXSHARE_TEST_SMTP_EMAIL") // may be a full "Name

" string 17 | }; 18 | 19 | let authArgs = { 20 | plain: { 21 | username: environ.get("FXSHARE_TEST_SMTP_USERNAME"), 22 | password: environ.get("FXSHARE_TEST_SMTP_PASSWORD") 23 | }, 24 | xoauth: null // we build this manually... 25 | }; 26 | 27 | function sendEmail(test, payload) { 28 | // first sort out the auth stuff. 29 | if (environ.get("FXSHARE_TEST_OAUTH_TOKEN")) { 30 | // this matches the oauthConfig structure that is used in fx-share 31 | authArgs.xoauth = { 32 | consumerSecret: environ.get("FXSHARE_TEST_OAUTH_CONSUMER_SECRET") || 'anonymous', 33 | consumerKey: environ.get("FXSHARE_TEST_OAUTH_CONSUMER_KEY") || 'anonymous', 34 | tokenSecret: environ.get("FXSHARE_TEST_OAUTH_TOKEN_SECRET"), 35 | token: environ.get("FXSHARE_TEST_OAUTH_TOKEN"), 36 | serviceProvider: { 37 | signatureMethod: "HMAC-SHA1", 38 | emailUrl: "https://mail.google.com/mail/b/%s/smtp/" 39 | } 40 | }; 41 | } else if (authArgs.plain.username && authArgs.plain.password) { 42 | // the auth structure is ready to go for plain authentication 43 | ; 44 | } else { 45 | // no concept of skipping a test, so just say it passed. 46 | console.log("skipping SMTP test as required environment variables not configured"); 47 | test.pass("skipping test as required environment variables not configured"); 48 | return; 49 | } 50 | 51 | // smtp module uses a 15 second connection timeout, so we use a little more. 52 | test.waitUntilDone(20000); 53 | let finished = false; 54 | let on_disconnect = function() { 55 | if (!finished) { 56 | test.fail("premature disconnection"); 57 | } else { 58 | test.pass("apparently we worked!"); 59 | } 60 | } 61 | 62 | let client = new SslSmtpClient(on_disconnect); 63 | let on_connected = function() { 64 | console.log("connected - starting login"); 65 | client.authenticate(authArgs, 66 | function() { 67 | // now we can send the message. 68 | let to = [environ.get("FXSHARE_TEST_EMAIL_TO") || smtpArgs.email]; 69 | payload.addHeader('To', to); 70 | payload.addHeader('From', to); 71 | client.sendMessage(to, payload, 72 | function() { 73 | finished = true; 74 | test.pass("message sent"); 75 | test.done(); 76 | }, 77 | function(why) { 78 | test.fail("message delivery failed: " + why); 79 | test.done(); 80 | } 81 | ); 82 | }, 83 | function(err) { 84 | test.fail("authentication failed: " + err.reply); 85 | test.done(); 86 | } 87 | ) 88 | } 89 | let on_error = function(err) { 90 | test.fail("connection failed: " + err.type + "/" + err.message + "/" + err.reply); 91 | test.done(); 92 | } 93 | let logging = true; 94 | client.connect(smtpArgs, on_connected, on_error, logging); 95 | } 96 | 97 | exports.testSmtpSimpleSend = function(test) { 98 | let msg = new MimeMultipart('alternative'); 99 | msg.addHeader('Subject', "simple test message from fx-share with funny \u00a9 char"); 100 | 101 | let part1 = new MimeText("hello there funny \u00a9har", 'plain') 102 | let part2 = new MimeText("hello there funny \u00a9har", 'html') 103 | msg.attach(part1); 104 | msg.attach(part2); 105 | sendEmail(test, msg); 106 | } 107 | 108 | exports.testSmtpImageSend = function(test) { 109 | let msg = new MimeMultipart('alternative'); 110 | msg.addHeader('Subject', "image test message from fx-share with funny \u00a9har"); 111 | 112 | let part2 = new MimeMultipart('related') 113 | let html = new MimeText('hello', 'html') 114 | 115 | // a small red dot. 116 | let b64image = "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w\r\n38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="; 117 | let image = new MimeBinary("image", "png", b64image, "base64") 118 | image.addHeader('Content-Id', ''); 119 | image.addHeader('Content-Disposition', 'inline; filename=thumbnail.png'); 120 | part2.attach(html) 121 | part2.attach(image) 122 | 123 | let part1 = new MimeText("hello", 'plain') 124 | 125 | msg.attach(part1) 126 | msg.attach(part2) 127 | sendEmail(test, msg); 128 | } 129 | -------------------------------------------------------------------------------- /data/ui/scripts/blade/object.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license blade/object Copyright (c) 2010, The Dojo Foundation All Rights Reserved. 3 | * Available via the MIT, GPL or new BSD license. 4 | * see: http://github.com/jrburke/blade for details 5 | */ 6 | /*jslint plusplus: false */ 7 | /*global define: false */ 8 | 9 | 'use strict'; 10 | 11 | define(['./fn'], function (fn) { 12 | 13 | var empty = {}, 14 | 15 | /** 16 | * Creates a new constructor function for generating objects of a certain type. 17 | * 18 | * @param {Object} base the base object to inherit from in the 19 | * prototype chain. Pass null if no parent desired. 20 | * 21 | * @param {Array} mixins an array of objects to use to mix in their 22 | * properties into the new object. Pass null if no mixins desired. 23 | * 24 | * @param {Function} objPropertyFunc, a function that returns an object 25 | * whose properties should be part of this new object's prototype. 26 | * The function will be passed the function used to call methods 27 | * on the parent prototype used for this object. The function expects 28 | * three arguments: 29 | * - obj: pass the this object for this arg 30 | * - funcName: the function name to call on the prototype object (a string) 31 | * - args: an array of arguments. Normally just pass the arguments object. 32 | * The parent prototype will be a combination of the base object 33 | * with all mixins applied. 34 | * 35 | * @returns {Function} a constructor function. 36 | */ 37 | object = function (base, mixins, objPropertyFunc) { 38 | base = base || {}; 39 | var constructor, 40 | 41 | //Create the parent and its parentFunc calling wrapper. 42 | //The parent function just makes it easier to call the parent 43 | parent = object.create(base.prototype, mixins), 44 | parentFunc = function (obj, funcName, args) { 45 | return parent[funcName].apply(obj, args); 46 | }, 47 | 48 | //Create a different object for the prototype instead of using 49 | //parent, so that parent can still refer to parent object 50 | //without the curren object's properties mixed in 51 | //(via the objPropertyFunc) with the mixed in properties taking 52 | //priority over the parent's properties. 53 | proto = object.create(parent); 54 | 55 | object.mixin(proto, (fn.is(objPropertyFunc) ? objPropertyFunc(parentFunc) : objPropertyFunc), true); 56 | 57 | //Create the constructor function. Calls init if it is defined 58 | //on the prototype (proto) 59 | constructor = function () { 60 | //Protect against a missing new 61 | if (!(this instanceof constructor)) { 62 | throw new Error('blade/object: constructor function called without "new" in front'); 63 | } 64 | 65 | //Call initializer if present. 66 | if (this.init) { 67 | this.init.apply(this, arguments); 68 | } 69 | }; 70 | 71 | //Set the prototype for this constructor 72 | constructor.prototype = proto; 73 | 74 | return constructor; 75 | }; 76 | 77 | /** 78 | * Similar to ES5 create, but instead of setting property attributes 79 | * for the second arg, allow an array of mixins to mix in properties 80 | * to the newly created object. 81 | * A copy of dojo.delegate 82 | * @param {Object} parent the parent object to use as the prototype. 83 | * @param {Array} [mixins] array of mixin objects to mix in to the new object. 84 | */ 85 | function Temp() {} 86 | 87 | object.create = function (obj, mixins) { 88 | Temp.prototype = obj; 89 | var temp = new Temp(), i, mixin; 90 | 91 | //Avoid any extra memory hanging around 92 | Temp.prototype = null; 93 | 94 | if (mixins) { 95 | for (i = 0; (mixin = mixins[i]); i++) { 96 | object.mixin(temp, mixin); 97 | } 98 | } 99 | return temp; // Object 100 | }; 101 | 102 | /** 103 | * Simple function to mix in properties from source into target, 104 | * but only if target does not already have a property of the same name, 105 | * unless override is set to true. Borrowed from Dojo. 106 | * 107 | * To extend a prototype on a given object, pass in the prototype property 108 | * to mixin. For example: object.mixin(func.prototype, {a: 'b'}); 109 | * 110 | * @param {Object} target the object receiving the mixed in properties. 111 | * 112 | * @param {Object} source the object that contains the properties to mix in. 113 | * 114 | * @param {Boolean} [override] if set to true, then the source's properties 115 | * will be mixed in even if a property of the same name already exists on 116 | * the target. 117 | */ 118 | object.mixin = function (target, source, override) { 119 | //TODO: consider ES5 getters and setters in here. 120 | for (var prop in source) { 121 | if (!(prop in empty) && (!(prop in target) || override)) { 122 | target[prop] = source[prop]; 123 | } 124 | } 125 | }; 126 | 127 | return object; 128 | }); 129 | -------------------------------------------------------------------------------- /lib/email/mime.js: -------------------------------------------------------------------------------- 1 | // Mime utilities with an API stolen shamelessly from Python's email package. 2 | // Would require lots of work to make it complete, but it is good enough for 3 | // us (and for many different requirements) 4 | 5 | const {base64Encode, base64Decode} = require("api-utils/utils/data") 6 | 7 | function encode_utf8( s ) 8 | { 9 | return unescape( encodeURIComponent( s ) ); 10 | } 11 | 12 | function decode_utf8( s ) 13 | { 14 | return decodeURIComponent( escape( s ) ); 15 | } 16 | 17 | // Add quotes around a string. 18 | function quote(str) { 19 | return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"'); 20 | } 21 | 22 | function formatParam(name, value) { 23 | return name + '="' + quote(value) + '"'; 24 | } 25 | 26 | // Does the passed value require utf-8 encoding? 27 | // If so, return it. If not, return null (so the 28 | // original value can be used) 29 | function maybeMakeUtf8(val) { 30 | // hrmph - previously we tried using encode_utf8 and checking if the result 31 | // string was identical to the input string, but this failed for chars like 32 | // '@' etc. So for now we explicitly check each char in the string is 7bit. 33 | // A "real" utf-8 encoder could probably do better. 34 | val = val.toString(); 35 | for (let i = 0; i < val.length; i++) { 36 | if (val.charCodeAt(i) > 127) { 37 | return encode_utf8(val); 38 | } 39 | } 40 | // all chars are 7bit - no need to use utf-8 for it. 41 | return null; 42 | } 43 | 44 | function encodeHeaderValue(val) { 45 | let utf8 = maybeMakeUtf8(val); 46 | if (utf8===null) { 47 | // 7 bit - value is ok as-is 48 | return val; 49 | } 50 | // gotta do the encoded-word magic 51 | return "=?utf-8?B?" + base64Encode(utf8) + "?="; 52 | } 53 | 54 | function Message() { 55 | this._headers = []; 56 | } 57 | exports.Message = Message 58 | 59 | Message.prototype = { 60 | /* addHeader 61 | 62 | Normally the parameters will be added as key="value" unless 63 | value is null/undefined, in which case only the key will be added. 64 | 65 | eg: 66 | msg.addHeader('content-disposition', 'attachment', {filename: 'bud.gif'}) 67 | */ 68 | addHeader: function(name, value, params) { 69 | let parts = []; 70 | if (params) { 71 | for (let key in params) { 72 | let v = params[key]; 73 | if (v==null) { 74 | parts.push(key) 75 | } else { 76 | parts.push(formatParam(key, v)) 77 | } 78 | } 79 | } 80 | if (value != null) { 81 | parts.unshift(encodeHeaderValue(value)); 82 | } 83 | this._headers.push([name, parts.join("; ")]); 84 | }, 85 | 86 | // like addHeader but removes any existing headers with the same name. 87 | setHeader: function(name, value, params) { 88 | let newHeaders = []; 89 | for each (let [existingname, existingvalue] in this._headers) { 90 | if (name !== existingname) { 91 | newHeaders.push([existingname, existingvalue]); 92 | } 93 | } 94 | this._headers = newHeaders; 95 | this.addHeader(name, value, params); 96 | }, 97 | 98 | attach: function(message) { 99 | if (!this._payload) { 100 | this._payload = []; 101 | } 102 | this._payload.push(message); 103 | }, 104 | 105 | toString: function() { 106 | let bits = []; 107 | for each (let [name, value] in this._headers) { 108 | bits.push(name + ": " + value + "\r\n"); 109 | } 110 | bits.push('\r\n'); // blank line after the headers. 111 | if (typeof this._payload === "string") { 112 | bits.push(this._payload + "\r\n"); 113 | } else { 114 | // must be an array of parts 115 | bits.push('--' + this._boundary + "\r\n") 116 | for (let i=0; i