Posted
over 9 years
ago
by
[email protected] (Matt Brubeck)
I’m returning at last to my series on building a simple HTML rendering engine:
... [More]
Part 1: Getting started
Part 2: HTML
Part 3: CSS
Part 4: Style
Part 5: Boxes
Part 6: Block layout
Part 7: Painting 101
In this article, I will add very basic painting code. This
code takes the tree of boxes from the layout module and turns them into an
array of pixels. This process is also known as “rasterization.”
Browsers usually implement rasterization with the help of graphics APIs and
libraries like Skia, Cairo, Direct2D, and so on. These APIs provide functions
for painting polygons, lines, curves, gradients, and text. For now, I’m going
to write my own rasterizer that can only paint one thing: rectangles.
Eventually I want to implement text rendering. At that point, I may
throw away this toy painting code and switch to a “real” 2D graphics library.
But for now, rectangles are sufficient to turn the output of my block layout
algorithm into pictures.
Catching Up
Since my last post, I’ve made some small changes to the code from previous
articles. These includes some minor refactoring, and some updates to keep the
code compatible with the latest Rust nightly builds. None of these changes
are vital to understanding the code, but if you’re curious, check the commit
history.
Building the Display List
Before painting, we will walk through the layout tree and build a display
list. This is a list of graphics operations like “draw a circle” or
“draw a string of text.” Or in our case, just “draw a rectangle.”
Why put commands into a display list, rather than execute them immediately?
The display list is useful for a several reasons. You can search it for items
that will be completely covered up by later operations, and remove them to
eliminate wasted painting. You can modify and re-use the display list in
cases where you know only certain items have changed. And you can use the
same display list to generate different types of output: for example, pixels
for displaying on a screen, or vector graphics for sending to a printer.
Robinson’s display list is a vector of DisplayCommands. For now there is only
one type of DisplayCommand, a solid-color rectangle:
type DisplayList = Vec<DisplayCommand>;
enum DisplayCommand {
SolidColor(Color, Rect),
// insert more commands here
}
To build the display list, we walk through the layout tree and generate a
series of commands for each box. First we draw the box’s background, then we
draw its borders and content on top of the background.
fn build_display_list(layout_root: &LayoutBox) -> DisplayList {
let mut list = Vec::new();
render_layout_box(&mut list, layout_root);
return list;
}
fn render_layout_box(list: &mut DisplayList, layout_box: &LayoutBox) {
render_background(list, layout_box);
render_borders(list, layout_box);
// TODO: render text
for child in layout_box.children.iter() {
render_layout_box(list, child);
}
}
By default, HTML elements are stacked in the order they appear: If two
elements overlap, the later one is drawn on top of the earlier one. This is
reflected in our display list, which will draw the elements in the same order
they appear in the DOM tree. If this code supported the z-index
property, then individual elements would be able to override this stacking
order, and we’d need to sort the display list accordingly.
The background is easy. It’s just a solid rectangle. If no background color
is specified, then the background is transparent and we don’t need to generate
a display command.
fn render_background(list: &mut DisplayList, layout_box: &LayoutBox) {
get_color(layout_box, "background").map(|color|
list.push(SolidColor(color, layout_box.dimensions.border_box())));
}
/// Return the specified color for CSS property `name`, or None if no color was specified.
fn get_color(layout_box: &LayoutBox, name: &str) -> Option<Color> {
match layout_box.box_type {
BlockNode(style) | InlineNode(style) => match style.value(name) {
Some(ColorValue(color)) => Some(color),
_ => None
},
AnonymousBlock => None
}
}
The borders are similar, but instead of a single rectangle we draw
four—one for each edge of the box.
fn render_borders(list: &mut DisplayList, layout_box: &LayoutBox) {
let color = match get_color(layout_box, "border-color") {
Some(color) => color,
_ => return // bail out if no border-color is specified
};
let d = &layout_box.dimensions;
let border_box = d.border_box();
// Left border
list.push(SolidColor(color, Rect {
x: border_box.x,
y: border_box.y,
width: d.border.left,
height: border_box.height,
}));
// Right border
list.push(SolidColor(color, Rect {
x: border_box.x + border_box.width - d.border.right,
y: border_box.y,
width: d.border.right,
height: border_box.height,
}));
// Top border
list.push(SolidColor(color, Rect {
x: border_box.x,
y: border_box.y,
width: border_box.width,
height: d.border.top,
}));
// Bottom border
list.push(SolidColor(color, Rect {
x: border_box.x,
y: border_box.y + border_box.height - d.border.bottom,
width: border_box.width,
height: d.border.bottom,
}));
}
Next the rendering function will draw each of the box’s children, until the
entire layout tree has been translated into display commands.
Rasterization
Now that we’ve built the display list, we need to turn it into pixels by
executing each DisplayCommand. We’ll store the pixels in a Canvas:
struct Canvas {
pixels: Vec<Color>,
width: uint,
height: uint,
}
impl Canvas {
/// Create a blank canvas
fn new(width: uint, height: uint) -> Canvas {
let white = Color { r: 255, g: 255, b: 255, a: 255 };
return Canvas {
pixels: Vec::from_elem(width * height, white),
width: width,
height: height,
}
}
// ...
}
To paint a rectangle on the canvas, we just loop through its rows and columns,
using a helper method to make sure we don’t go outside the bounds of
our canvas.
fn paint_item(&mut self, item: &DisplayCommand) {
match item {
&SolidColor(color, rect) => {
// Clip the rectangle to the canvas boundaries.
let x0 = rect.x.clamp(0.0, self.width as f32) as uint;
let y0 = rect.y.clamp(0.0, self.height as f32) as uint;
let x1 = (rect.x + rect.width).clamp(0.0, self.width as f32) as uint;
let y1 = (rect.y + rect.height).clamp(0.0, self.height as f32) as uint;
for y in range(y0, y1) {
for x in range(x0, x1) {
// TODO: alpha compositing with existing pixel
self.pixels[x + y * self.width] = color;
}
}
}
}
}
Note that this code only works with opaque colors. If we added transparency
(by reading the opacity property, or adding support for rgba() values
in the CSS parser) then it would need to blend each new pixel with
whatever it’s drawn on top of.
Now we can put everything together in the paint function, which builds a
display list and then rasterizes it to a canvas:
/// Paint a tree of LayoutBoxes to an array of pixels.
fn paint(layout_root: &LayoutBox, bounds: Rect) -> Canvas {
let display_list = build_display_list(layout_root);
let mut canvas = Canvas::new(bounds.width as uint, bounds.height as uint);
for item in display_list.iter() {
canvas.paint_item(item);
}
return canvas;
}
Lastly, we can write a few lines of code using the Rust Image
library to save the array of pixels as a PNG file.
Pretty Pictures
At last, we’ve reached the end of our rendering pipeline. In under 1000 lines
of code, robinson can now parse this HTML file:
<div class="a">
<div class="b">
<div class="c">
<div class="d">
<div class="e">
<div class="f">
<div class="g">
</div>
</div>
</div>
</div>
</div>
</div>
</div>
…and this CSS file:
* { display: block; padding: 12px; }
.a { background: #ff0000; }
.b { background: #ffa500; }
.c { background: #ffff00; }
.d { background: #008000; }
.e { background: #0000ff; }
.f { background: #4b0082; }
.g { background: #800080; }
…to produce this:
Yay!
Exercises
If you’re playing along at home, here are some things you might want to try:
Write an alternate painting function that takes a display list and produces
vector output (for example, an SVG file) instead of a raster image.
Add support for opacity and alpha blending.
Write a function to optimize the display list by culling items that are
completely outside of the canvas bounds.
If you’re familiar with OpenGL, write a hardware-accelerated painting
function that uses GL shaders to draw the rectangles.
To Be Continued…
Now that we’ve got basic functionality for each stage in our rendering
pipeline, it’s time to go back and fill in some of the missing
features—in particular, inline layout and text rendering. Future
articles may also add additional stages, like networking and scripting.
I’m going to give a short “Let’s build a browser engine!” talk at this
month’s Bay Area Rust Meetup. The meetup is at 7pm tomorrow
(Thursday, November 6) at Mozilla’s San Francisco office, and it will also
feature talks on Servo by my fellow Servo developers. Video of the talks will
be streamed live on Air Mozilla, and recordings will be published
there later. [Less]
|
Posted
over 9 years
ago
by
John
The same great people who brought RelEng Conf 2013 did it again earlier this year with the sold-out-wait-listed RelEng Conf 2014. Hosted at Google’s HQ campus, it was a great combination of academic and battle-hardened down-to-earth no-holds-barred
... [More]
industry presentations and panel sessions.
Unlike my keynote last year, this year I had no presentations, so I was able to relax and soak up the great keynote presentations by Chuck Rossi (RelEng at Facebook), and Dinah McNutt (RelEng at Google), as well as others. These are online, and well worth the watch:
Chuck Rossi’s keynote is here:
Dinah McNutt’s keynote is here:
Closing panel discussion is here:
Two years in a row of greatness from Bram, Christian, Foutse, Kim, Stephany, Akos and Boris means that I’m already looking forward to RelEng Conf 2015. Watch the official conference website, follow @relengcon and book your spot immediately to avoid that sad “oh, I’m still on the wait list” feeling.
John. [Less]
|
Posted
over 9 years
ago
by
Chris Heilmann
Four years ago I announced that I will join Mozilla as principal evangelist and I was the happiest person alive. I exclaimed that I want Mozilla to be the “Switzerland of HTML5” and an independent player in the great browser wars around the early
... [More]
days of this new technology revolution. I also announced that I am excited to use my flat more as I can work from home.
Now I am here and I have hardly had the chance to do so as I was busy getting from event to event, training to training and meetup to meetup. And whilst this is exciting it is also superbly taxing. It is time to lean back a bit, relax and have some me time.
I feel the first signs of burnout and I think it is very important to not let a fast and seemingly glamourous lifestyle on the road as an official spokesperson get in the way of finding peace and tranquility. I spoke in my last TEDx talk about getting too excited about one’s own online personality and living for that one and how dangerous that is.
And here I am finding myself being excited about Chris on the road and forgot about Chris who just lets go and leaves things to sort themselves out.
This is why I am taking a break from Mozilla. I am going on a sabbatical for a while and be a spectator watching the rewards of all the work we put in the last years. Firefox’s 10th anniversary is coming and great things are afoot.
I think we’ve shown that we can be the “Switzerland of HTML5” and it is time for me to have some time for myself and see what my colleagues can do. That’s why I am stepping down as “the Mozilla guy” and be plain Chris for a while.
I want to thank all my colleagues over the years for the great time I had. It is amazing to see how many dedicated and gifted people can work together and create something open and beautiful – even in traditionally very closed environments like the mobile space.
I will of course be around and you will be able to meet me at selected events and online. People in London and Stockholm will also see more of me. I will only take it slower from now on until the new year and represent myself and not the large, amazing and wonderful world that is Mozilla. As it stated on one of our summits: it is one Mozilla and many voices. And I will lean back, listen, and enjoy. [Less]
|
Posted
over 9 years
ago
Just over a year ago I went to my first November Project San Francisco (NPSF) free workout. I'm not exactly sure why I chose that particular morning of 2013-10-30 to show up but a year later I'm very glad I did. It's the biggest physical
... [More]
fitness change I've made since I first started running in January 2011.
Newbie
Seeing #NPSF chalkings in Golden Gate Park late summer of 2013 and especially meeting Sam Livermore and reading her enthusiastic posts had made me want to check it out. Maybe I decided on a whim the night before.
Confession: I took a bus to my first November Project and it wasn't the only time. I woke up to a 6am alarm, made it to Haight & Masonic by 6:20, realized I wouldn't make it on time, and hopped on the 71 bus that was pulling up. Took it just a few blocks to Haight & Scott then jogged 2 blocks up to the park and ran out of breath on that slight incline. More on that later.
I hiked up to the middle of Alamo Square, barely in time for introductions in the predawn darkness (just-before-PDT-to-PST-changeover).
Standing on a rock on the edge of a circle of grass, dressed in a full-body penguin suit, NPSF founder and leader Laura McCloskey told us to hug someone we didn't know, and then introduce ourselves.
Betny Townsend cheerily hugged me as a newbie, the first person I met at November Project. I saw Sam Livermore too. The open kindness of strangers and a familiar face was enough to make a strong positive impression. This was a workout group like no other.
Laura explained the workout, which turned out to be a PR (personal record) Wednesday workout (as I've blogged previously, except thankfully in 2013 without the first lap around Alamo Square). It took me ~45-50 minutes by my watch and pretty much destroyed me. Exhausted and humbled I walked home.
It was way out of my league.
Yearbook Photos & The Huffman
Two weeks later I noticed NPSF was taking yearbook photos so I decided to try it one more time. Same morning timing, took the bus again, ran out of breath again.
That's me in the back near the left, with the white cap, red t-shirt, and white shorts, starting my second NPSF workout.
This time we did what Laura called the "Huffman" partner workout, named after its inventor, Jessica Huffman. One person continuously does an exercise like pushups / sit-ups / lunges while the other runs a short downhill/uphill loop in the park as fast as they can, then they tag-off and swap places. We alternated for ~25 minutes working our way through two sets of four exercises if I remember correctly.
Laura had us partner up with someone we didn't know, and that was how I met Erin Hallett, who also warmly welcomed me. I was starting to understand what NP was about. Partner workouts are very different, especially the Huffman. There's something about knowing that your partner is doing exercises non-stop while you're running that makes you push yourself paticularly hard, because you don't want to keep them waiting. And after we finished our sets, we did a final lap around Alamo Square lined up for yearbook photos.
These photos turned out amazing. Rebecca Daniels photographed us in our fiercest post-workout faces, edited them and posted epic black and white headshots of everyone that showed up that day. I'm still using mine on my site and other sites too.
(Reminder: NovemberProject 2014 Yearbook Photos Are Tomorrow!)
Impressed and Scared By Hills
One of the great things about NPSF is all the work the organizers put into not only the workouts themselves, but in documenting them, with group photos and blog posts. When the photos of the hills workouts started showing up, the incredible vistas, the small group of super athletes that participated, it was impressive and inspiring. I knew I could never do that.
I can't run hills. When I started November Project, on all our runs around Alamo Square, downhill: no problem; uphill: I'd jog a few steps, and then have to walk the rest. Hills are scary because not being able to breathe is scary.
Why I Ran Out Of Breath
As embarrassed as I feel admitting to having taken the bus a few times to November Project, that's nothing compared to what I've told very few people about, which is that I grew up with asthma, and still wrestle with exertion induced asthma. In short that means if I start running from a cold start, after a dozen or so steps, my lungs feel anxious, my bronchial tubes start constricting, eventually each breath makes a louder audible wheezing sound, and I have to stop while I can still breathe standing up.
That's why I ran out of breath after two blocks up a slight incline. I live on a reasonably steep street and could not run half a block up without having an attack. The way asthma attacks work for most people, either you have to get medication (i.e. use an inhaler), or you might also be able to rest, calm yourself down (if you've practiced various techniques beforehand), and recover in about 15-30 minutes.
I tend to be fairly stubborn. I also hate admitting to weakness. There's an element of shame to it (even if there shouldn't be), and there's also an element of hey, everybody has issues they're dealing with, mine aren't special, don't look for any sympathy, just do your best. I also refused to run with my inhaler, because I'd rather learn my limits, and build self-confidence within those limits. I knew I could walk home if I had to.
Secret Solo Hill Practice
Going to November Project changed this for me. After participating a couple of times and being frustrated that I (was the only one who) couldn't run up the hills in Alamo Square, I decided to try practicing running uphill by myself even if it was only 25-50 feet at a time.
When you've lived with asthma you learn to recognize what it feels like just before it happens. Hills workouts were out of the question, yet I knew if I very deliberately paced myself, breathed, and listened to that anxious feeling that builds in your lungs, I could push myself to that edge, and back down before an attack manifested. I wondered if repeatedly pushing to that edge might make a difference.
From out my door I counted every house I could run up to before I had to stop and walk. One week I made it up a few houses to the green house. Next week I made it one more to the blue one. Another week the grey one. Then the tan one. Finally I was able to jog to the top of my block, just barely without losing my breath. I stopped and cried. I had run half a block uphill. I felt almost normal.
Why Now
It's something invisible that I live with. I'm not looking for sympathy or any special consideration; perhaps just understanding, and a broader understanding that you never know what anyone is going through, personally, privately, invisibly. We all have our struggles.
I chose to write about this publicly for three reasons:
Laura asked me what's my story. I couldn't tell it without this. It's part of who I am.
Inspiration from Andrew and Shannon's posts of their personal stories & struggles.
Most importantly, if I can help just one more person with asthma believe more in themselves then it's worth it. That they have more potential than they think they do, and to dare, to face the fear, to try, even in small steps, to find their limitations, persist, and maybe even grow beyond them.
My First NPSF Hills Workout
I kept practicing my own personal mini-hills workouts in secret. I kept running up my block, and beyond, up into Buena Vista Park, continuing my progress. Then NPSF announced a hills workout in Dolores Park on January 17th.
Less than three months from my first time at NPSF, I decided to #justshowup to hills. Despite being familiar with Dolores Park, I was scared. I didn't care. I would run what I could, then walk if I had to. Judgment be damned. But of course there was none, no judgment. Everyone was nothing but encouraging.
Yes I took the bus again that morning. It was a much smaller group than Wednesday. I met several NPSF regulars whose consistency had inspired me since I started: Josh Zipin (AKA "Zip"), Greg, Jorge , Pete Kruse, Adrienne, and more.
I ran and made it most of the way up the Church street hill from 18th to 21st streets. I think I walked the last block. Then I ran down and up again. I finished four repeats before our 25 minutes were up. Apparently I could now do hills.
Half Marathon, Running to Hills, and Track
16 days later, emboldened by the progress I'd made at NP, I ran my first half marathon (Kaiser) in 2:22. That particular cold, wet, solitary, painful experience is a story for another blog post. Suffice it to say it's still my PR, and I've been training hard to beat it, hopefully this Sunday at the Berkeley Half.
I started going regularly to hills workouts, getting a ride, driving, carpooling, whatever it took. Finally a little over a month after that first time at hills, I ran with our "rungang" to the first NPSF Corona Heights hills workout.
A week and a half after that I braved our informal trackattack workout and couldn't even keep up on the warmup laps. Didn't care. Just kept showing up and running nearly every week, twice a week at NP, and most Tuesdays at track. In just under 5 months I finally completed a trackattack workout.
Positive Community — Just Show Up
Despite all these personal triumphs, what November Project means to me is positive community: from smiles and eager hugs, to the coast-to-coast friendships, to last-minute Sunday long runs, to our informal #nopasoparungang which now consistently gets people to NPSF at least twice a week.
My friend, one of the first people I met at NPSF, Natalie O'Connor asked me why I run.
I told her, I run because I can. Everytime I walk outside in my running clothes, I know I've broken through limitations I thought I had, thanks to a supportive positive community like no other.
[Less]
|
Posted
over 9 years
ago
by
Amy
Every app submitted to Firefox Marketplace and every add-on submitted to AMO is reviewed by a person; 60-80% of the time, that person is a volunteer.
Each year, the AMMO team endeavors to meet a few of the top volunteer reviewers in person to talk
... [More]
about the past year, get feedback, plan for the coming year, and have a pint or two. This year, we arranged a meetup at MozFest in London.
We kicked things off with a welcome dinner and drinks, then spent the following day at the London MozSpace having more in-depth discussions. The group was extremely diverse—ten countries were represented among the 12 people who attended. Some reviewers joined in the past year, others have been reviewing for nearly a decade.
One piece of feedback that really stuck with me was that many people think being an app reviewer is the only way to contribute to Marketplace. This means we need to do more to get the word out about the myriad ways one can get involved. But it also means the reviewer program is strong, and widely known, and these are reasons themselves for celebration.
Notes from the meeting are available on this etherpad. We got some great feedback, and I am really looking forward to tackling some of the action items in the coming weeks.
Afterwards, we attended the MozFest science fair kick-off together, then dispersed over the weekend to explore the event, letting serendipity and our own unique interests guide us.
My interests and chance meetings guided me to make an LED robot, learn how to pick locks, and have fascinating discussions about communities and web literacy. Though I was weakened by flu at the conclusion of the weekend, I came away feeling invigorated by the spirit of Mozilla. [Less]
|
Posted
over 9 years
ago
Last week was the 2014 W3C TPAC. For those that don't know, TPAC is a conference where a number of W3C working groups get together in the same venue. This allows for a great amount of discussions between groups and also allows people to see what is
... [More]
coming in the future of the web.
The WebDriver Working Group was part of TPAC this year, like previous years, and there was some really great discussions.
The main topics that were discussed were:
We are going to be moving more discussions to the mailing list. This is to prevent people waiting for a Face to face to discuss things
The Data model of how data is sent over the wire between the remote and local ends
Attributes versus properties -This old chestnut
An approach to moving some of the manual tests that are used for W3C specs to automated ones with WebDriver - This is exciting
The meeting minutes for Thursday and Friday [Less]
|
Posted
over 9 years
ago
If you've come to any NovemberProject anywhere, make plans to be at the nearest one this Wednesday to get your yearbook photo. You've earned it.
If you're a runner of any kind or have been curious about NovemberProject, check it out this
... [More]
Wednesday and get your photo taken. Join us.
If I've ever bugged you to come to NovemberProject, and you haven't yet, this is the day to do it. Trust me.
I went to last year's Yearbook Photos day, had a great time (more on that in another post very soon!), and got a great photo that I'm still using for my site icon and profile photo.
Facebook events - all Wednesday morning at ~6:15am:
Los Angeles - Hollywood Bowl
Minneapolis - Mill City Museum
San Francisco - Alta Plaza Park
Plus thirteen more cities (Check out November-Project.com for the full list). I'll add more direct city-event links as I find them. It looks like there's going to be a beautiful sunrise.
[Less]
|
Posted
over 9 years
ago
by
Mike Kaply
If you haven't checked out the CCK2 lately, you should.
One of the coolest features I've added recently is the ability to hide things on any arbitrary window that is opened by Firefox. For instance, if you want to hide the bottom box in the about
... [More]
dialog, you can add "#aboutDialog #bottomBox" to the hidden UI section. You can also use it to hide arbitrary content in about:addons. I've also done major work on the clipboard capabilities API, so it should be more robust. There have also been quite a few bug fixes. You can keep up on all the latest changes here.
Download the latest CCK2 by clicking here.
If you want to request a feature, you can do so on the CCK2 support site. Priority for any requests is given to paying customers.
And if the CCK2 saves you time and money, please consider getting a support contract. It ensures that I'll be able to keep working on the CCK2. [Less]
|
Posted
over 9 years
ago
42 changesets
98 files changed
2460 insertions
385 deletions
ExtensionOccurrences
js18
cpp16
h15
jsm7
java6
py3
ini3
txt2
in2
xml1
xhtml1
sjs1
mm1
json1
idl1
css1
cc1
c1
build1
ModuleOccurrences
toolkit19
content11
mobile10
services9
... [More]
browser9
js5
layout4
dom4
ipc3
gfx3
widget1
testing1
modules1
media1
List of changesets:
Richard NewmanBug 1090385 - More robust handling of external intents. r=snorp, a=sledru - 65515de095b8
Mark FinkleBug 895775 - Correctly handle lifecycle in GeckoNetworkManager. r=rnewman a=lmandel - ae19708887ef
Richard NewmanBug 1090385 - Follow-up: fix GeckoAppShell. a=bustage - 0dd6a59ed6a5
Richard NewmanBug 1090385 - Follow-up: fix GeckoApp. a=bustage - 693b7d0c9b36
Richard NewmanBug 1090385 - Follow-up: fix yet more bustage in GeckoApp. a=bustage - 72bdce765298
Andrew McCreightBug 1089833 - Delete reply in MessageChannel::DispatchSyncMessage and DispatchInterruptMessage if channel isn't connected. r=billm, a=lsblakk - 926c3f3f1f3a
Randell JesupBug 1087605 - Don't try to set the priority of the CCApp thread (which doesn't exist). r=bwc, a=lsblakk - e35984b580fb
Boris ZbarskyBug 1087801 - Don't assume the global is a Window in the DOM CSS object. r=bholley, a=lsblakk - ac59c74b9386
Jonathan KewBug 1090869 - Don't collect output glyphs when checking for features involving . r=jdaggett, a=lsblakk - 17d3079dc41f
Mike HommeyBug 1091118 - Also export RANLIB to unbust android builds on mac. r=gps, a=lmandel - 12a8a2d96453
Robert O'CallahanBug 1052900 - Restore -moz-win-exclude-glass handling to the way it worked before. r=tn, a=lsblakk - 73905ff57286
Drew WillcoxonBug 1083167 - Fix FormHistory error in ContentSearch by not passing an empty string to FormHistory.update. r=MattN, a=lmandel - cadb1112c8fb
Doug TurnerBug 1073134 - Be more permissive on OSX 10.9.5 when parental control is on. r=jdm, a=lmandel - 340cfd2affa7
J. Ryan StinnettBug 1090450 - Properly check add-on update state during update interval. r=Mossop, a=lmandel - 06d2090db817
Michael WuBug 1081926 - Fallback on a simple image lookup when the normal lookup fails. r=mattwoodrow, a=lmandel - 546105a6d5c0
Jonathan WattBug 1076910 - Don't use gfxPlatform::GetPlatform() off the main thread. r=Bas, a=sledru - 8977f5061773
Jonathan WattBug 1076910 - Add some error checks to gfxUtils::EncodeSourceSurface. r=Bas, a=sledru - 3c329a6fd0cb
Brian HackettBug 1084280 - Use a local RegExpStack when running the RegExp interpreter. r=jandem, a=dveditz - 631a73cdbc91
Brian HackettBug 1077514 - Execute regexps in the bytecode interpreter if the initial JIT execution was interrupted. r=jandem, a=lmandel - 5238acab8176
Mike de BoerBug 1089011: make sure to only import contacts that are part of the default contacts group. r=MattN a=lmandel - 8b1b897ca39c
Bas SchoutenBug 1064864. Ensure the copying bounds are sane. r=jrmuizel a=sylvestre - d4ad7d727dd6
Georg FritzscheBug 1079341 - Missing yield on async makeDir in FHR state init. r=gps, a=lmandel - d9b49c7ee7fe
Georg FritzscheBug 1064333 - Migrate the FHR client id to the datareporting service. r=gps, a=lmandel - 8fbc0d8bb83d
Georg FritzscheBug 1064333 - Add the stable client id to the telemetry ping. r=froydnj, a=lmandel - ad6d502a38c9
Georg FritzscheBug 1064333 - Only add the stable user id to the ping when FHR upload is enabled. r=froydnj, a=lmandel - ec67776fc5e3
Georg FritzscheBug 1064333 - Init TelemetryPing in tests even if MOZILLA_OFFICIAL is not set. r=froydnj, a=lmandel - efb3c956dfef
Georg FritzscheBug 1086252 - Show stable client id in about:telemetry. r=froydnj, a=lmandel - bda711062d08
Georg FritzscheBug 1069873 - Add counter histogram type. r=froydnj, ba=lmandel - a4db8f39f372
Georg FritzscheBug 1069953 - Part 1: Make min/max/bucket_count optional for nsITelemetry newHistogram(). r=froydnj, ba=lmandel - 56b3e37832b9
Georg FritzscheBug 1069874 - Add keyed histogram types. r=froydnj, ba=lmandel - 3fe1e43c97b8
Georg FritzscheBug 1092219 - Fix keyedHistogram.add() passing the wrong argument to Histogram::Add(). r=froydnj, a=lmandel - aa11e337b8e3
Georg FritzscheBug 1092176 - Add keyed histogram section in about:telemetry. r=froydnj, a=lmandel - e6db2f014e26
Georg FritzscheBug 1089670 - Record searches in Telemetry. r=bwinton, ba=lmandel - 1ca39da5df9d
Paul AdenotBug 1085356 - Better handling of OSX audio output devices switching when SourceMediaStream are present in the MSG. r=jesup a=lmandel - 80b1fc2042df
Randell JesupBug 1085356: Fix Mac audio output changes on older/different macs r=padenot a=lmandel - ddc951a77894
Randell JesupBug 1090415: Whitelist room.co for screensharing rs=mreavy a=lmandel - bf50cf09506c
Randell JesupBug 1091031: Whitelist talky.io & beta.talky.io for screensharing rs=mreavy a=lmandel - 08876d848dcf
Randell JesupBug 1085356: Bustage fix (missing include from merge) r=bustage a=bustage - f6a4136fe0af
Nick AlexanderBug 1068051 - Add high-res device drawables. r=trivial, a=lmandel - 9f2160ac83d5
Richard NewmanBug 1084522 - Don't redefine layout attribute in IconTabWidget. r=lucasr, a=lmandel - 66297e95dc47
Richard NewmanBug 1084516 - Wrap Build.CPU_ABI access in deprecation annotation. r=snorp, a=lmandel - a02835abdd00
Olli PettayBug 1087633 - Filter out XPConnect wrapped input streams. r=bz, a=lmandel - 72938afdf993
[Less]
|
Posted
over 9 years
ago
by
Robert Nyman
Knowing your editor is important, and if it’s open source and you can add functionality, even better! Therefore, I dug into Atom from GitHub (which is open source!) to add something I like: a Hyperlink Helper.
As mentioned in my recent posts on Vim
... [More]
and favorite editors, I think it’s great for any developer to experiment and tweak their editor(s) of choice.
Using Atom a bit on the side, I thought I’d look into how to create a package for it. It offers a lot of them already, browsable through the packages web site or through the within the app itself.
Introducing the Hyperlink Helper
Back in the day when I started using TextMate, one of my favorite functions was to have a keyboard shortcut to wrap the selected text with an <a> element and set its href attribute to what’s in the system clipboard. Then when I moved to Sublime Text, someone created the Hyperlink Helper package for Sublime Text.
So, now with Atom, the next natural evolution would be for me to create one for it.
How to install it
Go to Settings in Atom > Packages: Search for Hyperlink Helper.
Functionality
Wraps selected text with an anchor element, e.g. Hello becomes <a href="http://robertnyman.com">Hello</a>
Sets the href attribute of that anchor to what’s currently in the system clipboard
How to trigger it
Select some text in the current document
Hit its keyboard shortcut:Ctrl + Cmd + L (Mac)Ctrl + Alt + L (Windows/Linux)or through Packages > hyperlink-helper > link in the Atom top menu
Source code and improvements
The source code is on GitHub – feel free to issue pull requests with improvements!
Getting started building packages for Atom
Does all this sound interesting and you want to build your own package? Then I recommend reading Creating Packages and Create Your First Package in the Atom docs. Atom also offers a way to generate a template package for you through the Package Generator: Generate Package in the Command Palette.
This is triggered with:
Cmd + Shift + p (Mac)
Ctrl + Shift + p (Windows/Linux)
Developer pro tip: in Atom, hit Cmd + . (Mac)/Ctrl + . (Windows/Linux) to show the Keybinding Resolver: great for seeing which command is connected with any keyboard shortcut combination you can come up with! (found via What are the keyboard shortcuts of the Atom editor? – keyboard shortcuts in Atom can also be found via Settings > Keybindings)
Bonus: Hyperlink Helper for Vim
Vim user and want a Hyperlink Helper? Just add this to your .vimrc file and call it via Space + l in Visual mode:
vmap <Space>l c<a href="<C-r>+"><C-r>"</a>
[Less]
|