198 lines
5.2 KiB
JavaScript
198 lines
5.2 KiB
JavaScript
/*
|
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*/
|
|
'use strict';
|
|
|
|
import {
|
|
log as internalLogger,
|
|
} from './common.js';
|
|
import * as Permissions from './permissions.js';
|
|
|
|
function log(...args) {
|
|
internalLogger('common/retrieve-url', ...args);
|
|
}
|
|
|
|
export const kTYPE_PLAIN_TEXT = 'text/plain';
|
|
export const kTYPE_X_MOZ_URL = 'text/x-moz-url';
|
|
export const kTYPE_URI_LIST = 'text/uri-list';
|
|
export const kTYPE_MOZ_TEXT_INTERNAL = 'text/x-moz-text-internal';
|
|
const kBOOKMARK_FOLDER = 'x-moz-place:';
|
|
|
|
const ACCEPTABLE_DATA_TYPES = [
|
|
kTYPE_URI_LIST,
|
|
kTYPE_X_MOZ_URL,
|
|
kTYPE_MOZ_TEXT_INTERNAL,
|
|
kTYPE_PLAIN_TEXT,
|
|
];
|
|
|
|
let mFileURLResolver = null;
|
|
|
|
export async function registerFileURLResolver(resolver) {
|
|
mFileURLResolver = resolver;
|
|
}
|
|
|
|
export async function fromDragEvent(event) {
|
|
log('fromDragEvent ', event);
|
|
const dt = event.dataTransfer;
|
|
const urls = [];
|
|
if (dt.files.length > 0) {
|
|
for (const file of dt.files) {
|
|
if (typeof mFileURLResolver == 'function') {
|
|
urls.push(await mFileURLResolver(file));
|
|
}
|
|
else {
|
|
// Created object URLs need to be revoked by tryRevokeObjectURL()
|
|
// in common/common.js, after they are loaded.
|
|
urls.push(URL.createObjectURL(file));
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
for (const type of ACCEPTABLE_DATA_TYPES) {
|
|
const urlData = dt.getData(type);
|
|
if (urlData)
|
|
urls.push(...fromData(urlData, type));
|
|
if (urls.length)
|
|
break;
|
|
}
|
|
for (const type of dt.types) {
|
|
if (!/^application\/x-ws-drag-data;(.+)$/.test(type))
|
|
continue;
|
|
const params = RegExp.$1;
|
|
const providerId = /provider=([^;&]+)/.test(params) && RegExp.$1;
|
|
const dataId = /id=([^;&]+)/.test(params) && RegExp.$1;
|
|
try {
|
|
const dragData = await browser.runtime.sendMessage(providerId, {
|
|
type: 'get-drag-data',
|
|
id: dataId
|
|
});
|
|
if (!dragData || typeof dragData != 'object')
|
|
break;
|
|
for (const type of ACCEPTABLE_DATA_TYPES) {
|
|
const urlData = dragData[type];
|
|
if (urlData)
|
|
urls.push(...fromData(urlData, type));
|
|
if (urls.length)
|
|
break;
|
|
}
|
|
}
|
|
catch(_error) {
|
|
}
|
|
}
|
|
}
|
|
log(' => retrieved: ', urls);
|
|
return sanitizeURLs(urls);
|
|
}
|
|
|
|
let mSelectionClipboardProvider = null;
|
|
|
|
/* provider should have two methods:
|
|
isAvailable(): returns boolean which indicates the selection clipboard is available or not.
|
|
getTextData(): returns a string from the selection clipboard.
|
|
*/
|
|
export function registerSelectionClipboardProvider(provider) {
|
|
mSelectionClipboardProvider = provider;
|
|
}
|
|
|
|
export async function fromClipboard({ selection } = {}) {
|
|
const urls = [];
|
|
|
|
if (selection && mSelectionClipboardProvider) {
|
|
if (await mSelectionClipboardProvider.isAvailable()) {
|
|
const maybeUrlString = await mSelectionClipboardProvider.getTextData();
|
|
if (maybeUrlString)
|
|
urls.push(...fromData(maybeUrlString, kTYPE_PLAIN_TEXT));
|
|
return sanitizeURLs(urls);
|
|
}
|
|
}
|
|
|
|
if (!(await Permissions.isGranted(Permissions.CLIPBOARD_READ)) ||
|
|
typeof navigator.clipboard.read != 'function')
|
|
return urls;
|
|
|
|
const clipboardContents = await navigator.clipboard.read();
|
|
log('fromClipboard ', clipboardContents);
|
|
|
|
for (const item of clipboardContents) {
|
|
for (const type of item.types) {
|
|
const maybeUrlBlob = await item.getType(type);
|
|
const maybeUrlString = await maybeUrlBlob.text();
|
|
if (maybeUrlString)
|
|
urls.push(...fromData(maybeUrlString, type));
|
|
if (urls.length)
|
|
break;
|
|
}
|
|
}
|
|
log(' => retrieved: ', urls);
|
|
return sanitizeURLs(urls);
|
|
}
|
|
|
|
function sanitizeURLs(urls) {
|
|
const filteredUrls = urls.filter(url =>
|
|
url &&
|
|
url.length &&
|
|
url.indexOf(kBOOKMARK_FOLDER) == 0 ||
|
|
!/^\s*(javascript|data):/.test(url)
|
|
);
|
|
log('sanitizeURLs filtered: ', filteredUrls);
|
|
|
|
const fixedUrls = filteredUrls.map(fixupURIFromText);
|
|
log('sanitizeURLs fixed: ', fixedUrls);
|
|
|
|
return fixedUrls;
|
|
}
|
|
|
|
function fromData(data, type) {
|
|
log('fromData: ', type, data);
|
|
switch (type) {
|
|
case kTYPE_URI_LIST:
|
|
return data
|
|
.replace(/\r/g, '\n')
|
|
.replace(/\n\n+/g, '\n')
|
|
.split('\n')
|
|
.filter(line => {
|
|
return line.charAt(0) != '#';
|
|
});
|
|
|
|
case kTYPE_X_MOZ_URL:
|
|
return data
|
|
.trim()
|
|
.replace(/\r/g, '\n')
|
|
.replace(/\n\n+/g, '\n')
|
|
.split('\n')
|
|
.filter((_line, index) => {
|
|
return index % 2 == 0;
|
|
});
|
|
|
|
case kTYPE_MOZ_TEXT_INTERNAL:
|
|
return data
|
|
.replace(/\r/g, '\n')
|
|
.replace(/\n\n+/g, '\n')
|
|
.trim()
|
|
.split('\n');
|
|
|
|
case kTYPE_PLAIN_TEXT:
|
|
return data
|
|
.replace(/\r/g, '\n')
|
|
.replace(/\n\n+/g, '\n')
|
|
.trim()
|
|
.split('\n')
|
|
.map(line => {
|
|
return /^\w+:\/\/.+/.test(line) ? line : `ext+ws:search:${line}`;
|
|
});
|
|
}
|
|
return [];
|
|
}
|
|
|
|
function fixupURIFromText(maybeURI) {
|
|
if (/^(ext\+)?\w+:/.test(maybeURI))
|
|
return maybeURI;
|
|
|
|
if (/^([^\.\s]+\.)+[^\.\s]{2}/.test(maybeURI))
|
|
return `http://${maybeURI}`;
|
|
|
|
return maybeURI;
|
|
}
|