Commit c571bfe8 authored by Andrea Giammarchi's avatar Andrea Giammarchi Committed by Winsley

Issue #26 - Issue reporter screenshot feature

parent 50cf657b
node_modules/
screenshots/
smoke/
desktop-options.js
first-run.js
skin/desktop-options.css
skin/issue-reporter.css
skin/first-run.css
skin/web.css
issue-reporter.js
desktop-options.js
first-run.js
package-lock.json
node_modules/
screenshots/
smoke/
^desktop-options.js
^first-run.js
skin/desktop-options.css
skin/issue-reporter.css
skin/first-run.css
skin/web.css
^issue-reporter.js
^desktop-options.js
^first-run.js
^package-lock.json
/*
* This file is part of Adblock Plus <https://adblockplus.org/>,
* Copyright (C) 2006-present eyeo GmbH
*
* Adblock Plus is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* Adblock Plus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>.
*/
html:not([dir="rtl"]) io-highlighter .options button,
html[dir="rtl"] io-highlighter .options button
{
margin: 0;
}
\ No newline at end of file
......@@ -16,6 +16,7 @@
*/
io-highlighter,
io-highlighter *,
io-highlighter *::before,
io-highlighter *::after
{
......@@ -37,6 +38,7 @@ io-highlighter .split
io-highlighter .options
{
width: 95px;
padding: 12px;
color: #4A4A4A;
background-color: #f1f1f1;
......
......@@ -30,7 +30,7 @@ io-steps::before
position: absolute;
z-index: -1;
content: ' ';
top: calc(0.6rem + 12px);
top: 12px;
width: 100%;
height: 1px;
font-size: 1px;
......@@ -38,10 +38,21 @@ io-steps::before
background-color: #bcbcbc;
}
html:not([dir="rtl"]) io-steps button,
io-steps button
{
margin: initial;
padding: initial;
}
io-steps button
{
min-width: 80px;
border: 0;
outline: none;
font-size: small;
font-weight: initial;
text-transform: inherit;
color: #077ca7;
background: #f3f3f3;
}
......@@ -52,6 +63,7 @@ io-steps button::before
width: 24px;
height: 24px;
margin: auto;
margin-bottom: 8px;
content: attr(data-value);
font-weight: normal;
font-size: 0.8rem;
......
......@@ -15,6 +15,10 @@
* along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>.
*/
@import "io-steps.scss";
@import "io-highlighter.scss";
@import "io-highlighter-fixes.scss";
html
{
font-size: 16px;
......@@ -32,43 +36,46 @@ body
background-color: #F3F3F3;
}
html:not([dir="rtl"]) header
{
text-align: right;
margin-right: 2rem;
}
html[dir="rtl"] header
header,
main,
footer
{
text-align: left;
margin-left: 2rem;
width: 46.3rem;
}
header
{
display: flex;
flex-direction: row;
flex-shrink: 0;
align-items: flex-end;
align-items: flex-start;
margin-bottom: 2rem;
margin-top: 1.2rem;
}
header > .logo
{
display: flex;
flex-direction: row;
margin-right: 3.5rem;
}
#logo
{
width: 4rem;
width: 3.5rem;
height: 3.5rem;
margin-right: 1rem;
}
header > .title
.title
{
display: flex;
flex-direction: column;
}
.title > h1
{
margin: 0rem;
font-size: 1.5rem;
margin: 0;
line-height: 1.2rem;
font-size: 1.2rem;
font-weight: 300;
}
......@@ -79,8 +86,15 @@ header > .title
.title > p
{
margin: 0rem;
font-size: 2.4rem;
margin: 0;
padding: 0;
font-size: 1.8rem;
line-height: 1.8rem;
}
header io-steps
{
flex-grow: 1;
}
main
......@@ -89,7 +103,6 @@ main
display: flex;
flex-direction: column;
box-sizing: border-box;
width: 46.3rem;
padding: 1.4rem;
background-color: #FFFFFF;
border: 1px solid #CDCDCD;
......@@ -102,18 +115,21 @@ main
flex-direction: column;
}
.page > p
{
font-size: 0.9rem;
}
main h1
{
padding: 0;
margin: 1.4rem 0rem;
margin: 0;
font-size: 1.5rem;
}
#dataCollectorProgressContainer
#typeSelectorGroup input[type="radio"]
{
display: flex;
flex-direction: row;
justify-content: center;
margin-top: 2rem;
transform: translateY(2px);
}
#typeSelectorGroup > p
......@@ -139,7 +155,6 @@ html[dir="rtl"] #typeSelectorGroup > p
}
#anonymousSubmissionWarning,
#commentLengthWarning,
#error
{
margin-top: 0.5em;
......@@ -184,7 +199,6 @@ footer
display: flex;
flex-direction: row;
box-sizing: border-box;
width: 46.3rem;
margin-top: 2rem;
}
......@@ -302,14 +316,33 @@ input[type="radio"]
-webkit-appearance: none;
-moz-appearance: none;
background-color: transparent;
background-image: url(icons/checkbox.png);
background-repeat: no-repeat;
display: inline-block;
}
input[type="checkbox"]:checked,
input[type="checkbox"]
{
background-image: url(icons/checkbox.svg?off#off);
}
input[type="checkbox"]:checked
{
background-image: url(icons/checkbox.svg?on#on);
}
input[type="radio"]
{
background-image: url(icons/radio.svg?normal#normal);
}
input[type="radio"]:hover
{
background-image: url(icons/radio.svg?hover#hover);
}
input[type="radio"]:checked
{
background-position: 0px 18px;
background-image: url(icons/radio.svg?selected#selected);
}
.modal:not([hidden])
......@@ -344,7 +377,7 @@ input[type="radio"]:checked
#notification
{
display: flex;
padding: 1rem 1.9rem;
padding: 2px;
width: 100%;
box-sizing: border-box;
opacity: 0.8;
......@@ -395,8 +428,7 @@ input[type="radio"]:checked
background-image: url(icons/delete.svg?tertiary-hover#tertiary-hover);
}
body[data-page="commentPage"] #continue,
body:not([data-page="commentPage"]) #send
body[data-page="commentPage"] #continue
{
display: none;
}
......@@ -19,11 +19,11 @@
<html>
<head>
<meta charset="utf-8">
<script src="polyfill.js"></script>
<script src="ext/common.js"></script>
<script src="ext/content.js"></script>
<script src="issue-reporter.js"></script>
<script src="i18n.js"></script>
<script defer src="polyfill.js"></script>
<script defer src="ext/common.js"></script>
<script defer src="ext/content.js"></script>
<script defer src="i18n.js"></script>
<script defer src="issue-reporter.js"></script>
<link rel="stylesheet" type="text/css" href="skin/common.css">
<link rel="stylesheet" type="text/css" href="skin/fonts.css">
<link rel="stylesheet" type="text/css" href="skin/issue-reporter.css">
......@@ -38,26 +38,26 @@
</button>
</div>
<header>
<img id="logo" alt="Adblock Plus logo" src="skin/abp-logo.svg">
<div class="title">
<h1>
Adblock <strong>Plus</strong>
</h1>
<p class="i18n_issueReporter_page_title"></p>
<div class="logo">
<img id="logo" alt="" src="skin/abp-logo.svg">
<div class="title">
<h1>
Adblock <strong>Plus</strong>
</h1>
<p class="i18n_issueReporter_page_title"></p>
</div>
</div>
<io-steps i18n-labels="
issueReporter_selectIssueButton_label
issueReporter_markIssueButton_label
issueReporter_detailsButton_label
issueReporter_doneButton_label
"></io-steps>
</header>
<main>
<div class="page" id="dataCollectorPage">
<h1 class="i18n_issueReporter_dataCollector_heading"></h1>
<p class="i18n_issueReporter_dataCollector_description"></p>
<div id="dataCollectorProgressContainer">
<progress id="dataCollectorProgress"></progress>
</div>
</div>
<div class="page" id="typeSelectorPage" hidden>
<div class="page" id="typeSelectorPage">
<h1 class="i18n_issueReporter_typeSelector_heading"></h1>
<p class="i18n_issueReporter_typeSelector_description"></p>
<div id="typeSelectorGroup">
......@@ -70,6 +70,10 @@
</div>
</div>
<div class="page" id="highlighterPage" hidden>
<h1 class="i18n_issueReporter_highlighter_heading"></h1>
<p class="i18n_issueReporter_highlighter_description"></p>
</div>
<div class="page" id="commentPage" hidden>
<h1 class="i18n_issueReporter_commentPage_heading"></h1>
......@@ -84,11 +88,9 @@
<div id="anonymousSubmissionWarning" data-invisible="true" class="i18n_issueReporter_anonymousSubmission_warning"></div>
</div>
<p class="i18n_issueReporter_comment_description"></p>
<label for="comment" class="i18n_issueReporter_comment_label"></label>
<p class="i18n_issueReporter_comment_description"></p>
<textarea id="comment"></textarea>
<p id="commentLengthWarning" data-invisible="true" class="i18n_issueReporter_comment_lengthWarning"></p>
<p>
<button id="showData" class="i18n_issueReporter_showData_label link" disabled></button>
</p>
......@@ -120,7 +122,7 @@
<a id="privacyPolicy" target="_blank" class="i18n_issueReporter_privacyPolicy"></a>
<button id="cancel" class="i18n_cancel secondary"></button>
<button id="continue" class="i18n_continue primary" disabled></button>
<button id="send" class="i18n_issueReporter_sendButton_label primary" disabled></button>
<button id="send" class="i18n_issueReporter_sendButton_label primary" disabled hidden></button>
</footer>
</body>
</html>
/*
* This file is part of Adblock Plus <https://adblockplus.org/>,
* Copyright (C) 2006-present eyeo GmbH
*
* Adblock Plus is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* Adblock Plus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>.
*/
"use strict";
module.exports = {
$: (selector, container = document) => container.querySelector(selector),
$$: (selector, container = document) => container.querySelectorAll(selector)
};
......@@ -38,6 +38,11 @@ module.exports = class DrawingHandler
canvas.width = canvasRect.width;
canvas.height = canvasRect.height;
// define a ratio that will produce an image with at least
// 800px (maxSize) width and multiply by the device pixel ratio
// to preserve the image quality on HiDPi screens.
this.ratio = (maxSize / canvas.width) * (window.devicePixelRatio || 1);
// it also needs to intercept all events
if ("onpointerup" in canvas)
{
......@@ -69,29 +74,38 @@ module.exports = class DrawingHandler
changeColorDepth(image)
{
this.clear();
const startW = image.naturalWidth;
const startH = image.naturalHeight;
const ratioW = Math.min(this.canvas.width, this.maxSize) / startW;
const ratioH = Math.min(this.canvas.height, this.maxSize) / startH;
const ratio = Math.min(ratioW, ratioH);
const endW = startW * ratio;
const endH = startH * ratio;
this.ctx.drawImage(image,
0, 0, startW, startH,
0, 0, endW, endH);
this.imageData = this.ctx.getImageData(
0, 0, this.canvas.width, this.canvas.height);
const {naturalWidth, naturalHeight} = image;
const canvasWidth = this.canvas.width * this.ratio;
const canvasHeight = (canvasWidth * naturalHeight) / naturalWidth;
// resize the canvas to the displayed image size
// to preserve HiDPi pixels
this.canvas.width = canvasWidth;
this.canvas.height = canvasHeight;
// force its computed size in normal CSS pixels
this.canvas.style.width = Math.round(canvasWidth / this.ratio) + "px";
this.canvas.style.height = Math.round(canvasHeight / this.ratio) + "px";
// draw resized image accordingly with new dimensions
this.ctx.drawImage(image, 0, 0, naturalWidth, naturalHeight,
0, 0, canvasWidth, canvasHeight);
// collect all info to process the iamge data
this.imageData = this.ctx.getImageData(0, 0, canvasWidth, canvasHeight);
const data = this.imageData.data;
const length = data.length;
const mapping = [0x00, 0x55, 0xAA, 0xFF];
// don't loop all pixels at once, assuming devices
// capable of HiDPi images have also enough power
// to handle all those pixels.
const avoidBlocking = Math.round(5000 * this.ratio);
return new Promise(resolve =>
{
const remap = i =>
{
for (; i < data.length; i++)
for (; i < length; i++)
{
data[i] = mapping[data[i] >> 6];
if (i > 0 && i % 5000 == 0)
if (i > 0 && i % avoidBlocking == 0)
{
notifyColorDepthChanges.call(this, i, length);
// faster when possible, otherwise less intrusive
// than a promise based on setTimeout as in legacy code
return requestIdleCallback(() =>
......@@ -101,6 +115,7 @@ module.exports = class DrawingHandler
});
}
}
notifyColorDepthChanges.call(this, i, length);
resolve();
};
remap(0);
......@@ -113,12 +128,12 @@ module.exports = class DrawingHandler
if (!this.ctx)
{
this.ctx = this.canvas.getContext("2d");
this.ctx.lineJoin = "round";
this.ctx.lineWidth = 4;
this.ctx.strokeStyle = "rgb(208, 1, 27)";
this.ctx.fillStyle = "rgb(0, 0, 0)";
}
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.ctx.lineJoin = "round";
this.ctx.strokeStyle = "rgb(208, 1, 27)";
this.ctx.fillStyle = "rgb(0, 0, 0)";
this.ctx.lineWidth = 4 * this.ratio;
}
// draw the image during or after it's being processed
......@@ -134,10 +149,10 @@ module.exports = class DrawingHandler
{
const method = `${rect.type}Rect`;
this.ctx[method](
rect.x,
rect.y,
rect.width,
rect.height
rect.x * this.ratio,
rect.y * this.ratio,
rect.width * this.ratio,
rect.height * this.ratio
);
}
}
......@@ -255,6 +270,13 @@ module.exports = class DrawingHandler
}
};
function notifyColorDepthChanges(value, max)
{
const info = {detail: {value, max}};
const ioHighlighter = this.canvas.closest("io-highlighter");
ioHighlighter.dispatchEvent(new CustomEvent("changecolordepth", info));
}
// helper to retrieve absolute coordinates
function getCoordinates(event)
{
......
......@@ -58,10 +58,19 @@ class IOSteps extends IOElement
return this._enabled;
}
// return true or false accordingly if an index/step
// has been already completed.
getCompleted(index)
{
return index < this._enabled;
}
// set an index completed state
// by default, completed is true
setCompleted(index, completed = true)
{
if (index < 0)
index = this.children.length + index;
this.children[index].classList.toggle("completed", completed);
if (
completed &&
......@@ -80,9 +89,10 @@ class IOSteps extends IOElement
{
event.preventDefault();
event.stopPropagation();
const indexOf = Array.prototype.indexOf;
this.dispatchEvent(new CustomEvent("step:click", {
bubbles: true,
detail: Array.prototype.call(this.children, event.currentTarget)
detail: indexOf.call(this.children, event.currentTarget)
}));
}
......
/*
* This file is part of Adblock Plus <https://adblockplus.org/>,
* Copyright (C) 2006-present eyeo GmbH
*
* Adblock Plus is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* Adblock Plus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>.
*/
"use strict";
const reportData = new DOMParser().parseFromString("<report></report>",
"text/xml");
let dataGatheringTabId = null;
let isMinimumTimeMet = false;
const port = browser.runtime.connect({name: "ui"});
module.exports = {
closeRequestsCollectingTab,
collectData()
{
const tabId = parseInt(location.search.replace(/^\?/, ""), 10);
if (!tabId)
return Promise.reject(new Error("invalid tab id"));
return Promise.all([
retrieveAddonInfo(),
retrieveApplicationInfo(),
retrievePlatformInfo(),
retrieveTabURL(tabId),
collectRequests(tabId),
retrieveSubscriptions()
]).then(() => reportData);
}
};
function collectRequests(tabId)
{
reportData.documentElement.appendChild(reportData.createElement("requests"));
reportData.documentElement.appendChild(reportData.createElement("filters"));
return browser.tabs.get(tabId).then(tab =>
{
return browser.tabs.create({active: false, url: tab.url});
}).then((tab) =>
{
dataGatheringTabId = tab.id;
port.postMessage({
type: "requests.listen",
filter: ["hits"],
tabId: dataGatheringTabId
});
function minimumTimeMet()
{
if (isMinimumTimeMet)
return;
isMinimumTimeMet = true;
document.getElementById("showData").disabled = false;
document.querySelector("io-steps")
.dispatchEvent(new CustomEvent("requestcollected"));
validateCommentsPage();
}
browser.tabs.onUpdated.addListener((updatedTabId, changeInfo) =>
{
if (updatedTabId == dataGatheringTabId && changeInfo.status == "complete")
minimumTimeMet();
});
window.setTimeout(minimumTimeMet, 5000);
window.addEventListener("beforeunload", (event) =>
{
closeRequestsCollectingTab();
});
});
}
function closeRequestsCollectingTab()
{
return browser.tabs.remove(dataGatheringTabId);
}
function retrieveAddonInfo()
{
const element = reportData.createElement("adblock-plus");
return browser.runtime.sendMessage({
type: "app.get",
what: "addonVersion"
}).then(addonVersion =>
{
element.setAttribute("version", addonVersion);
return browser.runtime.sendMessage({
type: "app.get",
what: "localeInfo"
});
}).then(({locale}) =>
{
element.setAttribute("locale", locale);
reportData.documentElement.appendChild(element);
});
}
function retrieveApplicationInfo()
{
const element = reportData.createElement("application");
return browser.runtime.sendMessage({
type: "app.get",
what: "application"
}).then(application =>
{
element.setAttribute("name", capitalize(application));
return browser.runtime.sendMessage({
type: "app.get",
what: "applicationVersion"
});
}).then(applicationVersion =>
{
element.setAttribute("version", applicationVersion);
element.setAttribute("vendor", navigator.vendor);
element.setAttribute("userAgent", navigator.userAgent);
reportData.documentElement.appendChild(element);
});
}
function retrievePlatformInfo()
{
const element = reportData.createElement("platform");
return browser.runtime.sendMessage({
type: "app.get",
what: "platform"
}).then(platform =>
{
element.setAttribute("name", capitalize(platform));
return browser.runtime.sendMessage({
type: "app.get",
what: "platformVersion"
});
}).then(platformVersion =>
{
element.setAttribute("version", platformVersion);
reportData.documentElement.appendChild(element);
});
}
function retrieveTabURL(tabId)
{
return browser.tabs.get(tabId).then(tab =>
{
const element = reportData.createElement("window");
if (tab.url)
element.setAttribute("url", censorURL(tab.url));
reportData.documentElement.appendChild(element);
});
}
function retrieveSubscriptions()
{
return browser.runtime.sendMessage({
type: "subscriptions.get",
ignoreDisabled: true,
downloadable: true,
disabledFilters: true
}).then(subscriptions =>
{
const element = reportData.createElement("subscriptions